diff --git a/deployment/ccip/changeset/cs_deploy_chain.go b/deployment/ccip/changeset/cs_deploy_chain.go index 1f985789c8d..334715d50b3 100644 --- a/deployment/ccip/changeset/cs_deploy_chain.go +++ b/deployment/ccip/changeset/cs_deploy_chain.go @@ -8,9 +8,16 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/gagliardetto/solana-go" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "golang.org/x/sync/errgroup" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" + + solBinary "github.com/gagliardetto/binary" + solRpc "github.com/gagliardetto/solana-go/rpc" + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" @@ -25,6 +32,10 @@ import ( var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts +var ( + EnableExecutionAfter = int64(1800) // 30min +) + // DeployChainContracts deploys all new CCIP v1.6 or later contracts for the given chains. // It returns the new addresses for the contracts. // DeployChainContracts is idempotent. If there is an error, it will return the successfully deployed addresses and the error so that the caller can call the @@ -68,17 +79,12 @@ func (c DeployChainContractsConfig) Validate() error { return nil } -func deployChainContractsForChains( - e deployment.Environment, - ab deployment.AddressBook, - homeChainSel uint64, - chainsToDeploy []uint64) error { +func validateHomeChainState(e deployment.Environment, homeChainSel uint64, existingState CCIPOnChainState) error { existingState, err := LoadOnchainState(e) if err != nil { e.Logger.Errorw("Failed to load existing onchain state", "err") return err } - capReg := existingState.Chains[homeChainSel].CapabilityRegistry if capReg == nil { e.Logger.Errorw("Failed to get capability registry") @@ -113,24 +119,67 @@ func deployChainContractsForChains( e.Logger.Errorw("Failed to get rmn home", "err", err) return errors.New("rmn home not found") } + return nil +} + +func deployChainContractsForChains( + e deployment.Environment, + ab deployment.AddressBook, + homeChainSel uint64, + chainsToDeploy []uint64) error { + existingEVMState, err := LoadOnchainState(e) + if err != nil { + e.Logger.Errorw("Failed to load existing onchain state", "err") + return err + } + + err = validateHomeChainState(e, homeChainSel, existingEVMState) + if err != nil { + return err + } + + err = deployment.ValidateSelectorsInEnvironment(e, chainsToDeploy) + if err != nil { + return err + } + + rmnHome := existingEVMState.Chains[homeChainSel].RMNHome + + existingSolState, err := LoadOnchainStateSolana(e) + if err != nil { + e.Logger.Errorw("Failed to load existing onchain solanastate", "err") + return err + } + deployGrp := errgroup.Group{} + for _, chainSel := range chainsToDeploy { - chain, ok := e.Chains[chainSel] - if !ok { - return fmt.Errorf("chain %d not found", chainSel) - } - if existingState.Chains[chainSel].LinkToken == nil || existingState.Chains[chainSel].Weth9 == nil { - return fmt.Errorf("fee tokens not found for chain %d", chainSel) + // already validated family + family, _ := chainsel.GetSelectorFamily(chainSel) + var deployFn func() error + switch family { + case chainsel.FamilyEVM: + chain := e.Chains[chainSel] + if existingEVMState.Chains[chainSel].LinkToken == nil || existingEVMState.Chains[chainSel].Weth9 == nil { + return fmt.Errorf("fee tokens not found for chain %d", chainSel) + } + deployFn = func() error { return deployChainContractsEVM(e, chain, ab, rmnHome) } + + case chainsel.FamilySolana: + chain := e.SolChains[chainSel] + if existingSolState.SolChains[chainSel].LinkToken.IsZero() { + return fmt.Errorf("fee tokens not found for chain %d", chainSel) + } + deployFn = func() error { return deployChainContractsSolana(e, chain, ab) } } - deployGrp.Go( - func() error { - err := deployChainContracts(e, chain, ab, rmnHome) - if err != nil { - e.Logger.Errorw("Failed to deploy chain contracts", "chain", chainSel, "err", err) - return fmt.Errorf("failed to deploy chain contracts for chain %d: %w", chainSel, err) - } - return nil - }) + deployGrp.Go(func() error { + err := deployFn() + if err != nil { + e.Logger.Errorw("Failed to deploy chain contracts", "chain", chainSel, "err", err) + return fmt.Errorf("failed to deploy chain contracts for chain %d: %w", chainSel, err) + } + return nil + }) } if err := deployGrp.Wait(); err != nil { e.Logger.Errorw("Failed to deploy chain contracts", "err", err) @@ -139,7 +188,7 @@ func deployChainContractsForChains( return nil } -func deployChainContracts( +func deployChainContractsEVM( e deployment.Environment, chain deployment.Chain, ab deployment.AddressBook, @@ -401,3 +450,123 @@ func deployChainContracts( e.Logger.Infow("Added nonce manager authorized callers", "chain", chain.String(), "callers", []common.Address{offRampContract.Address(), onRampContract.Address()}) return nil } + +func solRouterProgramData(e deployment.Environment, chain deployment.SolChain, ccipRouterProgram solana.PublicKey) (struct { + DataType uint32 + Address solana.PublicKey +}, error) { + var programData struct { + DataType uint32 + Address solana.PublicKey + } + data, err := chain.Client.GetAccountInfoWithOpts(e.GetContext(), ccipRouterProgram, &solRpc.GetAccountInfoOpts{ + Commitment: solRpc.CommitmentConfirmed, + }) + if err != nil { + return programData, fmt.Errorf("failed to deploy program: %w", err) + } + + err = solBinary.UnmarshalBorsh(&programData, data.Bytes()) + if err != nil { + return programData, fmt.Errorf("failed to unmarshal program data: %w", err) + } + return programData, nil +} + +func checkRouterInitialized(e deployment.Environment, chain deployment.SolChain, ccipRouterProgram solana.PublicKey) (bool, error) { + routerConfigPDA := GetRouterConfigPDA(ccipRouterProgram) + routerConfigInfo, err := chain.Client.GetAccountInfoWithOpts(e.GetContext(), routerConfigPDA, &solRpc.GetAccountInfoOpts{ + Commitment: solRpc.CommitmentConfirmed, + }) + if err != nil { + return false, nil + } + return routerConfigInfo != nil && len(routerConfigInfo.Value.Data.GetBinary()) > 0, nil +} + +func deployChainContractsSolana( + e deployment.Environment, + chain deployment.SolChain, + ab deployment.AddressBook, +) error { + state, err := LoadOnchainStateSolana(e) + if err != nil { + e.Logger.Errorw("Failed to load existing onchain state", "err") + return err + } + chainState, chainExists := state.SolChains[chain.Selector] + if !chainExists { + return fmt.Errorf("chain %s not found in existing state, deploy the prerequisites first", chain.String()) + } + linkTokenContract := chainState.LinkToken + e.Logger.Infow("link token", "addr", linkTokenContract.String()) + + var ccipRouterProgram solana.PublicKey + if chainState.SolCcipRouter.IsZero() { + //deploy router + programID, err := chain.DeployProgram(e.Logger, "ccip_router") + if err != nil { + return fmt.Errorf("failed to deploy program: %w", err) + } + + tv := deployment.NewTypeAndVersion("SolCcipRouter", deployment.Version1_0_0) + e.Logger.Infow("Deployed contract", "Contract", tv.String(), "addr", programID, "chain", chain.String()) + + ccipRouterProgram = solana.MustPublicKeyFromBase58(programID) + err = ab.Save(chain.Selector, programID, tv) + if err != nil { + return fmt.Errorf("failed to save address: %w", err) + } + } else { + e.Logger.Infow("Using existing router", "addr", chainState.SolCcipRouter.String()) + ccipRouterProgram = chainState.SolCcipRouter + } + ccip_router.SetProgramID(ccipRouterProgram) + + // check if solana router is initalised + initialized, err := checkRouterInitialized(e, chain, ccipRouterProgram) + if err != nil { + return err + } + if initialized { + e.Logger.Infow("Router already initialized, skipping initialization", "chain", chain.String()) + return nil + } + + programData, err := solRouterProgramData(e, chain, ccipRouterProgram) + if err != nil { + return fmt.Errorf("failed to get solana router program data: %w", err) + } + + defaultGasLimit := solBinary.Uint128{Lo: 3000, Hi: 0, Endianness: nil} + + instruction, err := ccip_router.NewInitializeInstruction( + chain.Selector, // chain selector + defaultGasLimit, // default gas limit + true, // allow out of order execution + EnableExecutionAfter, // period to wait before allowing manual execution + solana.PublicKey{}, + GetRouterConfigPDA(ccipRouterProgram), + GetRouterStatePDA(ccipRouterProgram), + chain.DeployerKey.PublicKey(), + solana.SystemProgramID, + ccipRouterProgram, + programData.Address, + GetExternalExecutionConfigPDA(ccipRouterProgram), + GetExternalTokenPoolsSignerPDA(ccipRouterProgram), + ).ValidateAndBuild() + + if err != nil { + return fmt.Errorf("failed to build instruction: %w", err) + } + err = chain.Confirm([]solana.Instruction{instruction}) + + if err != nil { + return fmt.Errorf("failed to confirm instructions: %w", err) + } + + //TODO: deploy token pool contract + //TODO: log errors + + return nil +} diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index a72b1b1568b..77a01e573c4 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -22,10 +22,15 @@ func TestDeployChainContractsChangeset(t *testing.T) { e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ Bootstraps: 1, Chains: 2, + SolChains: 1, Nodes: 4, }) - selectors := e.AllChainSelectors() - homeChainSel := selectors[0] + evmSelectors := e.AllChainSelectors() + homeChainSel := evmSelectors[0] + solChainSelectors := e.AllChainSelectorsSolana() + selectors := make([]uint64, 0, len(evmSelectors)+len(solChainSelectors)) + selectors = append(selectors, evmSelectors...) + selectors = append(selectors, solChainSelectors...) nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) require.NoError(t, err) p2pIds := nodes.NonBootstraps().PeerIDs() @@ -39,6 +44,8 @@ func TestDeployChainContractsChangeset(t *testing.T) { ChainSelector: chain, }) } + + SavePreloadedSolAddresses(e, solChainSelectors[0]) e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(DeployHomeChain), @@ -84,7 +91,7 @@ func TestDeployChainContractsChangeset(t *testing.T) { require.NotNil(t, state.Chains[homeChainSel].CapabilityRegistry) require.NotNil(t, state.Chains[homeChainSel].CCIPHome) require.NotNil(t, state.Chains[homeChainSel].RMNHome) - for _, sel := range selectors { + for _, sel := range evmSelectors { require.NotNil(t, state.Chains[sel].LinkToken) require.NotNil(t, state.Chains[sel].Weth9) require.NotNil(t, state.Chains[sel].TokenAdminRegistry) @@ -97,6 +104,14 @@ func TestDeployChainContractsChangeset(t *testing.T) { require.NotNil(t, state.Chains[sel].OffRamp) require.NotNil(t, state.Chains[sel].OnRamp) } + + solState, err := LoadOnchainStateSolana(e) + require.NoError(t, err) + for _, sel := range solChainSelectors { + require.NotNil(t, solState.SolChains[sel].LinkToken) + require.NotNil(t, solState.SolChains[sel].SolCcipRouter) + } + } func TestDeployCCIPContracts(t *testing.T) { diff --git a/deployment/ccip/changeset/internal/solana_contracts/ccip_router.so b/deployment/ccip/changeset/internal/solana_contracts/ccip_router.so new file mode 100755 index 00000000000..084318af144 Binary files /dev/null and b/deployment/ccip/changeset/internal/solana_contracts/ccip_router.so differ diff --git a/deployment/ccip/changeset/solana_state.go b/deployment/ccip/changeset/solana_state.go index 4e5507cfcd3..69aee5d481a 100644 --- a/deployment/ccip/changeset/solana_state.go +++ b/deployment/ccip/changeset/solana_state.go @@ -1,6 +1,147 @@ package changeset +import ( + "encoding/binary" + "errors" + "fmt" + + "github.com/gagliardetto/solana-go" + + "github.com/smartcontractkit/chainlink/deployment" +) + +var ( + LinkToken deployment.ContractType = "LinkToken" + SolCcipRouter deployment.ContractType = "SolCcipRouter" +) + // SolChainState holds a Go binding for all the currently deployed CCIP programs // on a chain. If a binding is nil, it means here is no such contract on the chain. type SolCCIPChainState struct { + LinkToken solana.PublicKey + SolCcipRouter solana.PublicKey +} + +func LoadOnchainStateSolana(e deployment.Environment) (CCIPOnChainState, error) { + state := CCIPOnChainState{ + SolChains: make(map[uint64]SolCCIPChainState), + } + for chainSelector, chain := range e.SolChains { + addresses, err := e.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + // Chain not found in address book, initialize empty + if !errors.Is(err, deployment.ErrChainNotFound) { + return state, err + } + addresses = make(map[string]deployment.TypeAndVersion) + } + chainState, err := LoadChainStateSolana(chain, addresses) + if err != nil { + return state, err + } + state.SolChains[chainSelector] = chainState + } + return state, nil +} + +// LoadChainStateSolana Loads all state for a SolChain into state +func LoadChainStateSolana(chain deployment.SolChain, addresses map[string]deployment.TypeAndVersion) (SolCCIPChainState, error) { + var state SolCCIPChainState + for address, tvStr := range addresses { + switch tvStr.String() { + case deployment.NewTypeAndVersion(LinkToken, deployment.Version1_0_0).String(): + pub := solana.MustPublicKeyFromBase58(address) + state.LinkToken = pub + case deployment.NewTypeAndVersion(SolCcipRouter, deployment.Version1_0_0).String(): + pub := solana.MustPublicKeyFromBase58(address) + state.SolCcipRouter = pub + default: + return state, fmt.Errorf("unknown contract %s", tvStr) + } + } + return state, nil +} + +// GetRouterConfigPDA returns the PDA for the "config" account. +func GetRouterConfigPDA(ccipRouterProgramId solana.PublicKey) solana.PublicKey { + pda, _, _ := solana.FindProgramAddress( + [][]byte{[]byte("config")}, + ccipRouterProgramId, + ) + return pda +} + +// GetRouterStatePDA returns the PDA for the "state" account. +func GetRouterStatePDA(ccipRouterProgramId solana.PublicKey) solana.PublicKey { + pda, _, _ := solana.FindProgramAddress( + [][]byte{[]byte("state")}, + ccipRouterProgramId, + ) + return pda +} + +// GetExternalExecutionConfigPDA returns the PDA for the "external_execution_config" account. +func GetExternalExecutionConfigPDA(ccipRouterProgramId solana.PublicKey) solana.PublicKey { + pda, _, _ := solana.FindProgramAddress( + [][]byte{[]byte("external_execution_config")}, + ccipRouterProgramId, + ) + return pda +} + +// GetExternalTokenPoolsSignerPDA returns the PDA for the "external_token_pools_signer" account. +func GetExternalTokenPoolsSignerPDA(ccipRouterProgramId solana.PublicKey) solana.PublicKey { + pda, _, _ := solana.FindProgramAddress( + [][]byte{[]byte("external_token_pools_signer")}, + ccipRouterProgramId, + ) + return pda +} + +// GetSolanaSourceChainStatePDA returns the PDA for the "source_chain_state" account for Solana. +func GetSolanaSourceChainStatePDA(ccipRouterProgramId solana.PublicKey, SolanaChainSelector uint64) solana.PublicKey { + pda, _, _ := solana.FindProgramAddress( + [][]byte{ + []byte("source_chain_state"), + binary.LittleEndian.AppendUint64([]byte{}, SolanaChainSelector), + }, + ccipRouterProgramId, + ) + return pda +} + +// GetSolanaDestChainStatePDA returns the PDA for the "dest_chain_state" account for Solana. +func GetSolanaDestChainStatePDA(ccipRouterProgramId solana.PublicKey, SolanaChainSelector uint64) solana.PublicKey { + pda, _, _ := solana.FindProgramAddress( + [][]byte{ + []byte("dest_chain_state"), + binary.LittleEndian.AppendUint64([]byte{}, SolanaChainSelector), + }, + ccipRouterProgramId, + ) + return pda +} + +// GetEvmSourceChainStatePDA returns the PDA for the "source_chain_state" account for EVM. +func GetEvmSourceChainStatePDA(ccipRouterProgramId solana.PublicKey, EvmChainSelector uint64) solana.PublicKey { + pda, _, _ := solana.FindProgramAddress( + [][]byte{ + []byte("source_chain_state"), + binary.LittleEndian.AppendUint64([]byte{}, EvmChainSelector), + }, + ccipRouterProgramId, + ) + return pda +} + +// GetEvmDestChainStatePDA returns the PDA for the "dest_chain_state" account for EVM. +func GetEvmDestChainStatePDA(ccipRouterProgramId solana.PublicKey, EvmChainSelector uint64) solana.PublicKey { + pda, _, _ := solana.FindProgramAddress( + [][]byte{ + []byte("dest_chain_state"), + binary.LittleEndian.AppendUint64([]byte{}, EvmChainSelector), + }, + ccipRouterProgramId, + ) + return pda } diff --git a/deployment/ccip/changeset/state_test.go b/deployment/ccip/changeset/state_test.go index 3587332fff2..75695f83282 100644 --- a/deployment/ccip/changeset/state_test.go +++ b/deployment/ccip/changeset/state_test.go @@ -13,3 +13,5 @@ func TestSmokeState(t *testing.T) { _, err = state.View(tenv.Env.AllChainSelectors()) require.NoError(t, err) } + +// TODO: add solana state test diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 03c3ffb175d..2958dc0a778 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -1257,3 +1257,9 @@ func DefaultRouterMessage(receiverAddress common.Address) router.ClientEVM2AnyMe ExtraArgs: nil, } } + +func SavePreloadedSolAddresses(e deployment.Environment, solChainSelector uint64) { + tv := deployment.NewTypeAndVersion("SolCcipRouter", deployment.Version1_0_0) + // TODO: this should be solTestConfig.CCIPRouterProgram + e.ExistingAddresses.Save(solChainSelector, "AmTB9SpwRjjKd3dHjFJiQoVt2bSzbzFnzBHCSpX4k9MW", tv) +} diff --git a/deployment/common/changeset/deploy_link_token.go b/deployment/common/changeset/deploy_link_token.go index 607c33fbeaa..648401289ff 100644 --- a/deployment/common/changeset/deploy_link_token.go +++ b/deployment/common/changeset/deploy_link_token.go @@ -2,7 +2,6 @@ package changeset import ( "context" - "fmt" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -25,12 +24,10 @@ const ( // DeployLinkToken deploys a link token contract to the chain identified by the ChainSelector. func DeployLinkToken(e deployment.Environment, chains []uint64) (deployment.ChangesetOutput, error) { - for _, chain := range chains { - _, evmOk := e.Chains[chain] - _, solOk := e.SolChains[chain] - if !evmOk && !solOk { - return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found in environment", chain) - } + + err := deployment.ValidateSelectorsInEnvironment(e, chains) + if err != nil { + return deployment.ChangesetOutput{}, err } newAddresses := deployment.NewMemoryAddressBook() for _, chain := range chains { diff --git a/deployment/environment/memory/chain.go b/deployment/environment/memory/chain.go index d3aa3a614f4..75e67546b04 100644 --- a/deployment/environment/memory/chain.go +++ b/deployment/environment/memory/chain.go @@ -1,10 +1,12 @@ package memory import ( + "context" "encoding/json" + "fmt" "math/big" "os" - "path" + "path/filepath" "strconv" "sync" "testing" @@ -18,17 +20,15 @@ import ( "github.com/gagliardetto/solana-go" solRpc "github.com/gagliardetto/solana-go/rpc" "github.com/hashicorp/consul/sdk/freeport" + "github.com/mr-tron/base58" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" - solTestUtil "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/testutils" - chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - chainselectors "github.com/smartcontractkit/chain-selectors" - "github.com/smartcontractkit/chainlink-testing-framework/framework" "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -42,9 +42,10 @@ type EVMChain struct { type SolanaChain struct { Client *solRpc.Client + DeployerKey solana.PrivateKey URL string WSURL string - DeployerKey solana.PrivateKey + KeypairPath string } func fundAddress(t *testing.T, from *bind.TransactOpts, to common.Address, amount *big.Int, backend *simulated.Backend) { @@ -86,6 +87,41 @@ func getTestSolanaChainSelectors() []uint64 { return result } +func generateSolanaKeypair(t testing.TB) (solana.PrivateKey, string, error) { + // Create a temporary directory that will be cleaned up after the test + tmpDir := t.TempDir() + + privateKey, err := solana.NewRandomPrivateKey() + if err != nil { + return solana.PrivateKey{}, "", fmt.Errorf("failed to generate private key: %w", err) + } + + // Convert private key bytes to JSON array + privateKeyBytes, err := base58.Decode(privateKey.String()) + if err != nil { + return solana.PrivateKey{}, "", fmt.Errorf("failed to decode private key: %w", err) + } + + // Convert bytes to array of integers for JSON + intArray := make([]int, len(privateKeyBytes)) + for i, b := range privateKeyBytes { + intArray[i] = int(b) + } + + keypairJSON, err := json.Marshal(intArray) + if err != nil { + return solana.PrivateKey{}, "", fmt.Errorf("failed to marshal keypair: %w", err) + } + + // Create the keypair file in the temporary directory + keypairPath := filepath.Join(tmpDir, "solana-keypair.json") + if err := os.WriteFile(keypairPath, keypairJSON, 0600); err != nil { + return solana.PrivateKey{}, "", fmt.Errorf("failed to write keypair to file: %w", err) + } + + return privateKey, keypairPath, nil +} + func GenerateChainsSol(t *testing.T, numChains int) map[uint64]SolanaChain { testSolanaChainSelectors := getTestSolanaChainSelectors() if len(testSolanaChainSelectors) < numChains { @@ -94,12 +130,20 @@ func GenerateChainsSol(t *testing.T, numChains int) map[uint64]SolanaChain { chains := make(map[uint64]SolanaChain) for i := 0; i < numChains; i++ { chainID := testSolanaChainSelectors[i] - solChain := solChain(t) - admin := solChain.DeployerKey - solTestUtil.FundTestAccounts(t, []solana.PublicKey{admin.PublicKey()}, solChain.URL) + admin, keypairPath, err := generateSolanaKeypair(t) + require.NoError(t, err) + url, wsURL, err := solChain(t, chainID, &admin) + require.NoError(t, err) + client := solRpc.New(url) + balance, err := client.GetBalance(context.Background(), admin.PublicKey(), solRpc.CommitmentConfirmed) + require.NoError(t, err) + require.NotEqual(t, 0, balance.Value) // auto funded 500000000.000000000 SOL chains[chainID] = SolanaChain{ - Client: solChain.Client, - DeployerKey: solChain.DeployerKey, + Client: client, + DeployerKey: admin, + URL: url, + WSURL: wsURL, + KeypairPath: keypairPath, } } return chains @@ -142,32 +186,26 @@ func evmChain(t *testing.T, numUsers int) EVMChain { var once = &sync.Once{} -func solChain(t *testing.T) SolanaChain { +func solChain(t *testing.T, chainID uint64, adminKey *solana.PrivateKey) (string, string, error) { t.Helper() // initialize the docker network used by CTF err := framework.DefaultNetwork(once) require.NoError(t, err) - deployerKey, err := solana.NewRandomPrivateKey() - require.NoError(t, err) - - t.TempDir() - // store the generated keypair somewhere - bytes, err := json.Marshal([]byte(deployerKey)) - require.NoError(t, err) - keypairPath := path.Join(t.TempDir(), "solana-keypair.json") - err = os.WriteFile(keypairPath, bytes, 0600) - require.NoError(t, err) - port := freeport.GetOne(t) bcInput := &blockchain.Input{ - Type: "solana", - ChainID: chainselectors.SOLANA_DEVNET.ChainID, - PublicKey: deployerKey.PublicKey().String(), - Port: strconv.Itoa(port), - // TODO: ContractsDir & SolanaPrograms via env vars + Type: "solana", + ChainID: strconv.FormatUint(chainID, 10), + PublicKey: adminKey.PublicKey().String(), + Port: strconv.Itoa(port), + ContractsDir: ProgramsPath, + // TODO: this should be solTestConfig.CCIPRouterProgram + // TODO: make this a function + SolanaPrograms: map[string]string{ + "ccip_router": "AmTB9SpwRjjKd3dHjFJiQoVt2bSzbzFnzBHCSpX4k9MW", + }, } output, err := blockchain.NewBlockchainNetwork(bcInput) require.NoError(t, err) @@ -195,10 +233,5 @@ func solChain(t *testing.T) SolanaChain { require.True(t, ready) t.Logf("solana-test-validator is ready at %s", url) - return SolanaChain{ - Client: client, - URL: url, - WSURL: wsURL, - DeployerKey: deployerKey, - } + return url, wsURL, nil } diff --git a/deployment/environment/memory/environment.go b/deployment/environment/memory/environment.go index c9044792834..fca738bee2c 100644 --- a/deployment/environment/memory/environment.go +++ b/deployment/environment/memory/environment.go @@ -3,6 +3,8 @@ package memory import ( "context" "fmt" + "path/filepath" + "runtime" "strconv" "testing" "time" @@ -30,6 +32,20 @@ const ( Memory = "memory" ) +var ( + // Instead of a relative path, use runtime.Caller or go-bindata + ProgramsPath = getProgramsPath() +) + +func getProgramsPath() string { + // Get the directory of the current file (environment.go) + _, currentFile, _, _ := runtime.Caller(0) + // Go up to the root of the deployment package + rootDir := filepath.Dir(filepath.Dir(filepath.Dir(currentFile))) + // Construct the absolute path + return filepath.Join(rootDir, "ccip/changeset/internal", "solana_contracts") +} + type MemoryEnvironmentConfig struct { Chains int SolChains int @@ -126,9 +142,13 @@ func generateMemoryChainSol(t *testing.T, inputs map[uint64]SolanaChain) map[uin for cid, chain := range inputs { chain := chain chains[cid] = deployment.SolChain{ - Selector: cid, - Client: chain.Client, - DeployerKey: &chain.DeployerKey, + Selector: cid, + Client: chain.Client, + DeployerKey: &chain.DeployerKey, + URL: chain.URL, + WSURL: chain.WSURL, + KeypairPath: chain.KeypairPath, + ProgramsPath: ProgramsPath, Confirm: func(instructions []solana.Instruction, opts ...solCommomUtil.TxModifier) error { _, err := solCommomUtil.SendAndConfirm( context.Background(), chain.Client, instructions, chain.DeployerKey, solRpc.CommitmentConfirmed, opts..., diff --git a/deployment/go.mod b/deployment/go.mod index f8875c64544..1fdd31a0d2e 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -18,11 +18,13 @@ require ( github.com/aws/aws-sdk-go v1.54.19 github.com/deckarep/golang-set/v2 v2.6.0 github.com/ethereum/go-ethereum v1.14.11 + github.com/gagliardetto/binary v0.8.0 github.com/gagliardetto/solana-go v1.12.0 github.com/go-resty/resty/v2 v2.15.3 github.com/google/uuid v1.6.0 github.com/hashicorp/consul/sdk v0.16.1 github.com/hashicorp/go-multierror v1.1.1 + github.com/mr-tron/base58 v1.2.0 github.com/pelletier/go-toml/v2 v2.2.3 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 @@ -191,7 +193,6 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.6 // indirect - github.com/gagliardetto/binary v0.8.0 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect @@ -357,7 +358,6 @@ require ( github.com/montanaflynn/stats v0.7.1 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect diff --git a/deployment/helpers.go b/deployment/helpers.go index d8e15d0200d..ed30a70de3f 100644 --- a/deployment/helpers.go +++ b/deployment/helpers.go @@ -205,3 +205,14 @@ func ChainInfo(cs uint64) (chain_selectors.ChainDetails, error) { } return info, nil } + +func ValidateSelectorsInEnvironment(e Environment, chains []uint64) error { + for _, chain := range chains { + _, evmOk := e.Chains[chain] + _, solOk := e.SolChains[chain] + if !evmOk && !solOk { + return fmt.Errorf("chain %d not found in environment", chain) + } + } + return nil +} diff --git a/deployment/solana_chain.go b/deployment/solana_chain.go index 34410c2d06a..825f88c4dcc 100644 --- a/deployment/solana_chain.go +++ b/deployment/solana_chain.go @@ -3,7 +3,9 @@ package deployment import ( "bytes" "fmt" + "os" "os/exec" + "path/filepath" "strconv" "strings" "time" @@ -13,6 +15,7 @@ import ( "github.com/pkg/errors" solCommomUtil "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" ) // SolChain represents a Solana chain. @@ -25,10 +28,11 @@ type SolChain struct { WSURL string // TODO: raw private key for now, need to replace with a more secure way DeployerKey *solana.PrivateKey + Confirm func(instructions []solana.Instruction, opts ...solCommomUtil.TxModifier) error + // deploy uses the solana CLI which needs a keyfile KeypairPath string ProgramsPath string - Confirm func(instructions []solana.Instruction, opts ...solCommomUtil.TxModifier) error } func (c SolChain) String() string { @@ -52,13 +56,31 @@ func (c SolChain) Name() string { return chainInfo.ChainName } -func (c SolChain) DeployProgram(programName string) (string, error) { - programFile := fmt.Sprintf("%s/%s.so", c.ProgramsPath, programName) - programKeyPair := fmt.Sprintf("%s/%s-keypair.json", c.ProgramsPath, programName) +func (c SolChain) DeployProgram(logger logger.Logger, programName string) (string, error) { + programFile := filepath.Join(c.ProgramsPath, programName+".so") + programKeyPair := filepath.Join(c.ProgramsPath, programName+"-keypair.json") + + // Base command with required args + baseArgs := []string{ + "program", "deploy", + programFile, //.so file + "--keypair", c.KeypairPath, //admin, upgradeAuthority + "--url", c.URL, //rpc url + } - // Construct the CLI command: solana program deploy - // TODO: @terry doing this on the fly - cmd := exec.Command("solana", "program", "deploy", programFile, "--keypair", c.KeypairPath, "--program-id", programKeyPair) + var cmd *exec.Cmd + if _, err := os.Stat(programKeyPair); err == nil { + // Keypair exists, include program-id + logger.Infow("Deploying program with existing keypair", + "programFile", programFile, + "programKeyPair", programKeyPair) + cmd = exec.Command("solana", append(baseArgs, "--program-id", programKeyPair)...) + } else { + // Keypairs wont be created for devenvs + logger.Infow("Deploying new program", + "programFile", programFile) + cmd = exec.Command("solana", baseArgs...) + } // Capture the command output var stdout, stderr bytes.Buffer @@ -73,7 +95,8 @@ func (c SolChain) DeployProgram(programName string) (string, error) { // Parse and return the program ID output := stdout.String() - time.Sleep(5 * time.Second) // obviously need to do this better + // TODO: obviously need to do this better + time.Sleep(5 * time.Second) return parseProgramID(output) } diff --git a/go.mod b/go.mod index eecb2533829..da485b9ac68 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/gin-contrib/expvar v0.0.1 github.com/gin-contrib/sessions v0.0.5 github.com/gin-contrib/size v0.0.0-20230212012657-e14a14094dc4 - github.com/gin-gonic/gin v1.9.1 + github.com/gin-gonic/gin v1.10.0 github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-viper/mapstructure/v2 v2.1.0 github.com/go-webauthn/webauthn v0.9.4 @@ -161,8 +161,8 @@ require ( github.com/blendle/zapdriver v1.3.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/bytecodealliance/wasmtime-go/v23 v23.0.0 // indirect - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/bytedance/sonic v1.12.3 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect @@ -188,7 +188,7 @@ require ( github.com/cosmos/ibc-go/v7 v7.5.1 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -205,7 +205,7 @@ require ( github.com/ethereum/c-kzg-4844 v1.0.0 // indirect github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.6 // indirect github.com/gagliardetto/binary v0.7.7 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect @@ -221,7 +221,7 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect @@ -287,7 +287,7 @@ require ( github.com/marcboeker/go-duckdb v1.8.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -313,7 +313,7 @@ require ( github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/cors v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect @@ -345,10 +345,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 // indirect - github.com/urfave/cli/v2 v2.25.7 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect github.com/valyala/fastjson v1.4.1 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect github.com/zondax/hid v0.9.2 // indirect diff --git a/go.sum b/go.sum index 0f5735df451..fa1b5771f7f 100644 --- a/go.sum +++ b/go.sum @@ -204,10 +204,11 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bytecodealliance/wasmtime-go/v23 v23.0.0 h1:NJvU4S8KEk1GnF6+FvlnzMD/8wXTj/mYJSG6Q4yu3Pw= github.com/bytecodealliance/wasmtime-go/v23 v23.0.0/go.mod h1:5YIL+Ouiww2zpO7u+iZ1U1G5NvmwQYaXdmCZQGjQM0U= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= +github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -309,8 +310,9 @@ github.com/cosmos/rosetta-sdk-go v0.10.0/go.mod h1:SImAZkb96YbwvoRkzSMQB6noNJXFg github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= @@ -400,8 +402,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= +github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= github.com/gagliardetto/binary v0.7.7 h1:QZpT38+sgoPg+TIQjH94sLbl/vX+nlIRA37pEyOsjfY= github.com/gagliardetto/binary v0.7.7/go.mod h1:mUuay5LL8wFVnIlecHakSZMvcdqfs+CsotR5n77kyjM= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= @@ -432,8 +434,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -476,8 +478,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -904,8 +906,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -1080,8 +1082,8 @@ github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1291,8 +1293,8 @@ github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFs github.com/unrolled/secure v1.13.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= -github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= -github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fastjson v1.4.1 h1:hrltpHpIpkaxll8QltMU8c3QZ5+qIiCL8yKqPFJI/yE= github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7EFWPsvP8o= @@ -1306,8 +1308,8 @@ github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23n github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= @@ -1434,7 +1436,6 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1978,7 +1979,6 @@ nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYm pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=