diff --git a/.github/actions/setup-solana/build-contracts/action.yml b/.github/actions/setup-solana/build-contracts/action.yml new file mode 100644 index 00000000000..b1c94345abf --- /dev/null +++ b/.github/actions/setup-solana/build-contracts/action.yml @@ -0,0 +1,73 @@ +name: Solana build contracts +description: Build Solana contracts + +runs: + using: composite + steps: + - name: Checkout chainlink-ccip + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + with: + repository: smartcontractkit/chainlink-ccip + path: chainlink-ccip + fetch-depth: 0 + - name: Checkout branch based on CCIP Revision + id: get_checkout_ccip_chains_solana_revision + shell: bash + run: | + # get the short revision of the chainlink-ccip solana contracts dependency from the go.mod file + short_revision=$(grep "github.com/smartcontractkit/chainlink-ccip/chains/solana" deployment/go.mod | awk '{print $2}' | cut -d'-' -f3) + + # since the github action checkout action doesn't support short revisions, we have to do it manually + cd chainlink-ccip + git checkout $short_revision + + echo "CHAINLINK_CCIP_COMMIT_SHORT=${short_revision}" >> $GITHUB_ENV + - name: Get Anchor Version + id: get_anchor_version + shell: bash + run: | + cd chainlink-ccip/chains/solana + anchor=$(make anchor_version) + echo "ANCHOR_VERSION=${anchor}" >> $GITHUB_ENV + - name: cache docker build image + id: cache-image + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + lookup-only: true + path: chains/solana/contracts/docker-build.tar + key: ${{ runner.os }}-solana-build-${{ env.ANCHOR_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo target dir + id: cache-target + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + lookup-only: true + path: chains/solana/contracts/target + key: ${{ runner.os }}-solana-contract-artifacts-${{ hashFiles('**/Cargo.lock') }} + - name: build & save image + if: steps.cache-image.outputs.cache-hit != 'true' + shell: bash + run: | + cd chainlink-ccip/chains/solana/contracts + docker buildx build . -t ccip-solana:build --build-arg ANCHOR_CLI=${{ env.ANCHOR_VERSION }} + docker save -o docker-build.tar ccip-solana + - name: build & save contract compilation artifacts + if: steps.cache-target.outputs.cache-hit != 'true' + shell: bash + run: | + cd chainlink-ccip/chains/solana + docker run -v "$(pwd)/contracts":/solana/contracts ccip-solana:build bash -c "\ + set -eoux pipefail &&\ + RUSTUP_HOME=\"/root/.rustup\" &&\ + FORCE_COLOR=1 &&\ + cd /solana/contracts &&\ + anchor build &&\ + chmod -R 755 ./target" + - name: move built contracts to test folder + shell: bash + run: | + # copy the built contracts so they can be used in the chainlink tests + mkdir -p /home/runner/work/chainlink/chainlink/deployment/ccip/changeset/internal/solana_contracts + cp chainlink-ccip/chains/solana/contracts/target/deploy/*.so /home/runner/work/chainlink/chainlink/deployment/ccip/changeset/internal/solana_contracts + + # save the revision of the built chainlink-ccip solana contracts + echo ${{ env.CHAINLINK_CCIP_COMMIT_SHORT }} > /home/runner/work/chainlink/chainlink/deployment/ccip/changeset/internal/solana_contracts/.solana_contracts_rev diff --git a/.github/workflows/ci-core-partial.yml b/.github/workflows/ci-core-partial.yml index e796a72c008..9b7a1835188 100644 --- a/.github/workflows/ci-core-partial.yml +++ b/.github/workflows/ci-core-partial.yml @@ -80,6 +80,9 @@ jobs: - name: Setup Solana uses: ./.github/actions/setup-solana + - name: Build Solana artifacts + uses: ./.github/actions/setup-solana/build-contracts + - name: Setup wasmd uses: ./.github/actions/setup-wasmd @@ -210,6 +213,9 @@ jobs: - name: Setup Solana uses: ./.github/actions/setup-solana + - name: Build Solana artifacts + uses: ./.github/actions/setup-solana/build-contracts + - name: Setup wasmd uses: ./.github/actions/setup-wasmd @@ -256,6 +262,9 @@ jobs: - name: Setup Solana uses: ./.github/actions/setup-solana + - name: Build Solana artifacts + uses: ./.github/actions/setup-solana/build-contracts + - name: Setup wasmd uses: ./.github/actions/setup-wasmd diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index ce624d7b27b..408b8557f60 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -240,6 +240,10 @@ jobs: if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} uses: ./.github/actions/setup-solana + - name: Build Solana artifacts + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} + uses: ./.github/actions/setup-solana/build-contracts + - name: Setup wasmd if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} uses: ./.github/actions/setup-wasmd diff --git a/.github/workflows/flakeguard.yml b/.github/workflows/flakeguard.yml index 27cce2f15c8..6aaa10d2444 100644 --- a/.github/workflows/flakeguard.yml +++ b/.github/workflows/flakeguard.yml @@ -257,6 +257,8 @@ jobs: restore-build-cache-only: "true" - name: Setup Solana uses: ./.github/actions/setup-solana + - name: Build Solana artifacts + uses: ./.github/actions/setup-solana/build-contracts - name: Setup wasmd uses: ./.github/actions/setup-wasmd - name: Setup Postgres diff --git a/deployment/ccip/changeset/cs_deploy_chain.go b/deployment/ccip/changeset/cs_deploy_chain.go index 68101fe4fea..ab67e70caa3 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,7 +32,11 @@ import ( var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContractsChangeset -// DeployChainContractsChangeset deploys all new CCIP v1.6 or later contracts for the given chains. +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. // DeployChainContractsChangeset is idempotent. If there is an error, it will return the successfully deployed addresses and the error so that the caller can call the // changeset again with the same input to retry the failed deployment. @@ -154,13 +165,12 @@ func DefaultOffRampParams() OffRampParams { } } -func deployChainContractsForChains(e deployment.Environment, ab deployment.AddressBook, homeChainSel uint64, contractParamsPerChain map[uint64]ChainContractParams) 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") + e.Logger.Errorw("Failed to load existing onchain state", "err", err) return err } - capReg := existingState.Chains[homeChainSel].CapabilityRegistry if capReg == nil { e.Logger.Errorw("Failed to get capability registry") @@ -195,30 +205,73 @@ func deployChainContractsForChains(e deployment.Environment, ab deployment.Addre 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, + contractParamsPerChain map[uint64]ChainContractParams) error { + existingEVMState, err := LoadOnchainState(e) + if err != nil { + e.Logger.Errorw("Failed to load existing onchain state", "err", err) + return err + } + + err = validateHomeChainState(e, homeChainSel, existingEVMState) + 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", err) + return err + } + deployGrp := errgroup.Group{} + for chainSel, contractParams := range contractParamsPerChain { - chain, ok := e.Chains[chainSel] - if !ok { - return fmt.Errorf("chain %d not found", chainSel) + if _, exists := existingEVMState.SupportedChains()[chainSel]; !exists { + return fmt.Errorf("chain %d not supported", chainSel) } + // already validated family + family, _ := chainsel.GetSelectorFamily(chainSel) + var deployFn func() error + switch family { + case chainsel.FamilyEVM: + staticLinkExists := existingEVMState.Chains[chainSel].StaticLinkToken != nil + linkExists := existingEVMState.Chains[chainSel].LinkToken != nil + weth9Exists := existingEVMState.Chains[chainSel].Weth9 != nil + feeTokensAreValid := weth9Exists && (linkExists != staticLinkExists) + if !feeTokensAreValid { + return fmt.Errorf("fee tokens not valid for chain %d, staticLinkExists: %t, linkExists: %t, weth9Exists: %t", chainSel, staticLinkExists, linkExists, weth9Exists) + } + 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, contractParams) } - staticLinkExists := existingState.Chains[chainSel].StaticLinkToken != nil - linkExists := existingState.Chains[chainSel].LinkToken != nil - weth9Exists := existingState.Chains[chainSel].Weth9 != nil - feeTokensAreValid := weth9Exists && (linkExists != staticLinkExists) - - if !feeTokensAreValid { - return fmt.Errorf("fee tokens not valid for chain %d, staticLinkExists: %t, linkExists: %t, weth9Exists: %t", chainSel, staticLinkExists, linkExists, weth9Exists) + 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, contractParams) - 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) @@ -227,11 +280,11 @@ func deployChainContractsForChains(e deployment.Environment, ab deployment.Addre return nil } -func deployChainContracts(e deployment.Environment, chain deployment.Chain, ab deployment.AddressBook, rmnHome *rmn_home.RMNHome, contractParams ChainContractParams) error { +func deployChainContractsEVM(e deployment.Environment, chain deployment.Chain, ab deployment.AddressBook, rmnHome *rmn_home.RMNHome, contractParams ChainContractParams) error { // check for existing contracts state, err := LoadOnchainState(e) if err != nil { - e.Logger.Errorw("Failed to load existing onchain state", "err") + e.Logger.Errorw("Failed to load existing onchain state", "err", err) return err } chainState, chainExists := state.Chains[chain.Selector] @@ -484,3 +537,123 @@ func deployChainContracts(e deployment.Environment, chain deployment.Chain, ab d 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", 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 initialised + 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 7c837b11937..7069cadfb3f 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -24,10 +24,16 @@ 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() + testhelpers.SavePreloadedSolAddresses(t, e, solChainSelectors[0]) + 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() @@ -40,6 +46,12 @@ func TestDeployChainContractsChangeset(t *testing.T) { OffRampParams: changeset.DefaultOffRampParams(), } } + for _, chain := range solChainSelectors { + contractParams[chain] = changeset.ChainContractParams{ + FeeQuoterParams: changeset.DefaultFeeQuoterParams(), + OffRampParams: changeset.DefaultOffRampParams(), + } + } prereqCfg := make([]changeset.DeployPrerequisiteConfigPerChain, 0) for _, chain := range e.AllChainSelectors() { prereqCfg = append(prereqCfg, changeset.DeployPrerequisiteConfigPerChain{ @@ -91,7 +103,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) @@ -104,6 +116,14 @@ func TestDeployChainContractsChangeset(t *testing.T) { require.NotNil(t, state.Chains[sel].OffRamp) require.NotNil(t, state.Chains[sel].OnRamp) } + + solState, err := changeset.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/solana_state.go b/deployment/ccip/changeset/solana_state.go index 4e5507cfcd3..68a326f471d 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.go b/deployment/ccip/changeset/state.go index 9844ee36e5c..e0c704aebf9 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -373,6 +373,9 @@ func (s CCIPOnChainState) SupportedChains() map[uint64]struct{} { for chain := range s.Chains { chains[chain] = struct{}{} } + for chain := range s.SolChains { + chains[chain] = struct{}{} + } return chains } @@ -401,8 +404,13 @@ func (s CCIPOnChainState) View(chains []uint64) (map[string]view.ChainView, erro } func LoadOnchainState(e deployment.Environment) (CCIPOnChainState, error) { + solState, err := LoadOnchainStateSolana(e) + if err != nil { + return CCIPOnChainState{}, err + } state := CCIPOnChainState{ - Chains: make(map[uint64]CCIPChainState), + Chains: make(map[uint64]CCIPChainState), + SolChains: solState.SolChains, } for chainSelector, chain := range e.Chains { addresses, err := e.ExistingAddresses.AddressesForChain(chainSelector) diff --git a/deployment/ccip/changeset/state_test.go b/deployment/ccip/changeset/state_test.go index c46df8b1dc7..0bde8bc48ab 100644 --- a/deployment/ccip/changeset/state_test.go +++ b/deployment/ccip/changeset/state_test.go @@ -16,3 +16,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/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index dcfba1fad9e..4edfc5ce4c3 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -43,6 +43,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment/environment/devenv" "github.com/smartcontractkit/chainlink/deployment/environment/memory" + solTestConfig "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/burn_mint_token_pool" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" @@ -1249,6 +1250,12 @@ func DefaultRouterMessage(receiverAddress common.Address) router.ClientEVM2AnyMe } } +func SavePreloadedSolAddresses(t *testing.T, e deployment.Environment, solChainSelector uint64) { + tv := deployment.NewTypeAndVersion("SolCcipRouter", deployment.Version1_0_0) + err := e.ExistingAddresses.Save(solChainSelector, solTestConfig.CcipRouterProgram.String(), tv) + require.NoError(t, err) +} + func GenTestTransferOwnershipConfig( e DeployedEnv, chains []uint64, 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..31ae9a30dc8 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,16 @@ 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" - + solTestConfig "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" "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 +43,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 +88,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 +131,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 +187,24 @@ 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, + SolanaPrograms: map[string]string{ + "ccip_router": solTestConfig.CcipRouterProgram.String(), + }, } output, err := blockchain.NewBlockchainNetwork(bcInput) require.NoError(t, err) @@ -195,10 +232,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..a739a1d0c7d 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 8dc07b4965e..79e5da03ece 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 @@ -194,7 +196,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/gagliardetto/utilz v0.1.1 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect @@ -364,7 +365,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 166dfa4ea6d..e3f7a78b901 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..5dd6e0918f6 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,42 @@ 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) +var allowedPrograms = map[string]bool{ + "ccip_router": true, + // Add other valid program names here +} + +func (c SolChain) DeployProgram(logger logger.Logger, programName string) (string, error) { + if !allowedPrograms[programName] { + return "", fmt.Errorf("program %s not in allowed list", programName) + } + programFile := filepath.Join(c.ProgramsPath, programName+".so") + if _, err := os.Stat(programFile); err != nil { + return "", fmt.Errorf("program file not found: %w", err) + } + programKeyPair := filepath.Join(c.ProgramsPath, programName+"-keypair.json") + + // Base command with required args + baseArgs := []string{ + "program", "deploy", + programFile, // .so file + "--keypair", c.KeypairPath, // program keypair + "--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)...) // #nosec G204 + } else { + // Keypairs wont be created for devenvs + logger.Infow("Deploying new program", + "programFile", programFile) + cmd = exec.Command("solana", baseArgs...) // #nosec G204 + } // Capture the command output var stdout, stderr bytes.Buffer @@ -73,7 +106,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 619c2b85e0e..f8a7ad3eae1 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 @@ -164,8 +164,8 @@ require ( github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 // 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 @@ -191,7 +191,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 @@ -208,7 +208,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/gagliardetto/utilz v0.1.1 // indirect @@ -225,7 +225,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 @@ -292,7 +292,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/miekg/dns v1.1.61 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect @@ -319,7 +319,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/ryanuber/go-glob v1.0.0 // indirect @@ -353,10 +353,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 b7416aa2fdc..a0c560f6067 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= @@ -402,8 +404,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= @@ -435,8 +437,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= @@ -479,8 +481,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= @@ -907,8 +909,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= @@ -1084,8 +1086,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= @@ -1297,8 +1299,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= @@ -1312,8 +1314,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= @@ -1440,7 +1442,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= @@ -1985,7 +1986,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=