From 3777a725d14d13c83eb07dea85d4f49b7c5cc8b7 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Wed, 24 Jun 2026 16:01:33 -0400 Subject: [PATCH 01/13] NONEVM-5441: implement tooling API for Sui lane connection --- deployment/lanes/adapter.go | 85 +++++++++++++++++++++++ deployment/lanes/adapter_test.go | 94 ++++++++++++++++++++++++++ deployment/lanes/addresses.go | 75 +++++++++++++++++++++ deployment/lanes/addresses_test.go | 104 +++++++++++++++++++++++++++++ deployment/lanes/defaults.go | 14 ++++ deployment/lanes/env.go | 50 ++++++++++++++ deployment/lanes/translate.go | 40 +++++++++++ 7 files changed, 462 insertions(+) create mode 100644 deployment/lanes/adapter.go create mode 100644 deployment/lanes/adapter_test.go create mode 100644 deployment/lanes/addresses.go create mode 100644 deployment/lanes/addresses_test.go create mode 100644 deployment/lanes/defaults.go create mode 100644 deployment/lanes/env.go create mode 100644 deployment/lanes/translate.go diff --git a/deployment/lanes/adapter.go b/deployment/lanes/adapter.go new file mode 100644 index 00000000..50a31d99 --- /dev/null +++ b/deployment/lanes/adapter.go @@ -0,0 +1,85 @@ +package lanes + +import ( + "encoding/binary" + "math/big" + + "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" +) + +// suiFamilySelector is bytes4(keccak256("CCIP ChainFamilySelector Sui")) = 0xc4e05953. +var suiFamilySelector = [4]byte{0xc4, 0xe0, 0x59, 0x53} + +// SuiAdapter implements laneapi.LaneAdapter for Sui CCIP 1.6.0 lanes. +// Package IDs are resolved from addresses.json via LoadOnchainStatesui; the ds +// parameter is unused. ConnectChains must run inside WithConnectChainsEnvironment. +type SuiAdapter struct{} + +func (a *SuiAdapter) GetOnRampAddress(_ datastore.DataStore, chainSelector uint64) ([]byte, error) { + e, err := connectChainsEnvironment() + if err != nil { + return nil, err + } + return resolveOnRampPackageID(e, chainSelector) +} + +func (a *SuiAdapter) GetOffRampAddress(_ datastore.DataStore, chainSelector uint64) ([]byte, error) { + e, err := connectChainsEnvironment() + if err != nil { + return nil, err + } + return resolveOffRampPackageID(e, chainSelector) +} + +func (a *SuiAdapter) GetFQAddress(_ datastore.DataStore, chainSelector uint64) ([]byte, error) { + e, err := connectChainsEnvironment() + if err != nil { + return nil, err + } + return resolveCCIPPackageID(e, chainSelector) +} + +func (a *SuiAdapter) GetRouterAddress(_ datastore.DataStore, chainSelector uint64) ([]byte, error) { + e, err := connectChainsEnvironment() + if err != nil { + return nil, err + } + return resolveRouterPackageID(e, chainSelector) +} + +// GetFeeQuoterDestChainConfig returns defaults applied on remote FeeQuoters when Sui is +// the destination chain. Values align with wired Sui testnet lanes (evm_feequoter_dest_configure) +// and Sui execution limits (MaxDataBytes 16_000). +func (a *SuiAdapter) GetFeeQuoterDestChainConfig() laneapi.FeeQuoterDestChainConfig { + return laneapi.FeeQuoterDestChainConfig{ + IsEnabled: true, + MaxNumberOfTokensPerMsg: 10, + MaxDataBytes: 16_000, + MaxPerMsgGasLimit: 3_000_000, + DestGasOverhead: 300_000, + DestGasPerPayloadByteBase: 16, + DestGasPerPayloadByteHigh: 40, + DestGasPerPayloadByteThreshold: 3_000, + DestDataAvailabilityOverheadGas: 100, + DestGasPerDataAvailabilityByte: 16, + DestDataAvailabilityMultiplierBps: 1, + ChainFamilySelector: binary.BigEndian.Uint32(suiFamilySelector[:]), + EnforceOutOfOrder: true, + DefaultTokenFeeUSDCents: 25, + DefaultTokenDestGasOverhead: 90_000, + DefaultTxGasLimit: 200_000, + GasMultiplierWeiPerEth: 11e17, + GasPriceStalenessThreshold: 0, + NetworkFeeUSDCents: 10, + } +} + +func (a *SuiAdapter) GetDefaultGasPrice() *big.Int { + return big.NewInt(15e11) +} + +func (a *SuiAdapter) GetChainFamilySelector() [4]byte { + return suiFamilySelector +} diff --git a/deployment/lanes/adapter_test.go b/deployment/lanes/adapter_test.go new file mode 100644 index 00000000..20c6a206 --- /dev/null +++ b/deployment/lanes/adapter_test.go @@ -0,0 +1,94 @@ +package lanes_test + +import ( + "encoding/binary" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + + "github.com/smartcontractkit/chainlink-sui/deployment/lanes" +) + +func TestSuiAdapter_GetFeeQuoterDestChainConfig(t *testing.T) { + t.Parallel() + + cfg := (&lanes.SuiAdapter{}).GetFeeQuoterDestChainConfig() + + require.True(t, cfg.IsEnabled) + require.Equal(t, uint16(10), cfg.MaxNumberOfTokensPerMsg) + require.Equal(t, uint32(16_000), cfg.MaxDataBytes) + require.Equal(t, uint32(3_000_000), cfg.MaxPerMsgGasLimit) + require.Equal(t, uint32(300_000), cfg.DestGasOverhead) + require.Equal(t, uint8(16), cfg.DestGasPerPayloadByteBase) + require.Equal(t, uint32(0xc4e05953), cfg.ChainFamilySelector) + require.Equal(t, uint16(25), cfg.DefaultTokenFeeUSDCents) + require.Equal(t, uint32(90_000), cfg.DefaultTokenDestGasOverhead) + require.Equal(t, uint32(200_000), cfg.DefaultTxGasLimit) + require.Equal(t, uint32(10), cfg.NetworkFeeUSDCents) + require.True(t, cfg.EnforceOutOfOrder) + require.Equal(t, uint64(11e17), cfg.GasMultiplierWeiPerEth) +} + +func TestSuiAdapter_GetDefaultGasPrice(t *testing.T) { + t.Parallel() + + require.Equal(t, big.NewInt(15e11), (&lanes.SuiAdapter{}).GetDefaultGasPrice()) +} + +func TestSuiAdapter_GetChainFamilySelector(t *testing.T) { + t.Parallel() + + selector := (&lanes.SuiAdapter{}).GetChainFamilySelector() + require.Equal(t, [4]byte{0xc4, 0xe0, 0x59, 0x53}, selector) + require.Equal(t, uint32(0xc4e05953), binary.BigEndian.Uint32(selector[:])) +} + +func TestTranslateDestChainConfig(t *testing.T) { + t.Parallel() + + const destSel = uint64(945045181441419236) + cfg := laneapi.FeeQuoterDestChainConfig{ + IsEnabled: true, + MaxNumberOfTokensPerMsg: 1, + MaxDataBytes: 30_000, + MaxPerMsgGasLimit: 3_000_000, + DestGasOverhead: 300_000, + DestGasPerPayloadByteBase: 16, + DestGasPerPayloadByteHigh: 40, + DestGasPerPayloadByteThreshold: 3_000, + DestDataAvailabilityOverheadGas: 100, + DestGasPerDataAvailabilityByte: 16, + DestDataAvailabilityMultiplierBps: 1, + ChainFamilySelector: 0x2812d52c, + EnforceOutOfOrder: true, + DefaultTokenFeeUSDCents: 25, + DefaultTokenDestGasOverhead: 90_000, + DefaultTxGasLimit: 200_000, + GasMultiplierWeiPerEth: 1e18, + GasPriceStalenessThreshold: 1_000_000, + NetworkFeeUSDCents: 10, + } + + got := lanes.TranslateDestChainConfig(cfg, destSel) + + require.Equal(t, destSel, got.DestChainSelector) + require.True(t, got.IsEnabled) + require.Equal(t, uint16(1), got.MaxNumberOfTokensPerMsg) + require.Equal(t, uint32(30_000), got.MaxDataBytes) + require.Equal(t, byte(16), got.DestGasPerPayloadByteBase) + require.Equal(t, []byte{0x28, 0x12, 0xd5, 0x2c}, got.ChainFamilySelector) + require.Equal(t, uint64(1e18), got.GasMultiplierWeiPerEth) + require.Equal(t, uint32(10), got.NetworkFeeUsdCents) +} + +func TestDefaultLinkTokenTransferFees(t *testing.T) { + t.Parallel() + + require.Equal(t, uint32(3000), lanes.DefaultLinkTokenTransferMinFeeUsdCents) + require.Equal(t, uint32(30000), lanes.DefaultLinkTokenTransferMaxFeeUsdCents) + require.Equal(t, uint16(1000), lanes.DefaultLinkTokenTransferDeciBps) + require.Equal(t, uint64(900_000_000_000_000_000), lanes.DefaultLinkPremiumMultiplierWeiPerEth) +} diff --git a/deployment/lanes/addresses.go b/deployment/lanes/addresses.go new file mode 100644 index 00000000..a7da96c2 --- /dev/null +++ b/deployment/lanes/addresses.go @@ -0,0 +1,75 @@ +package lanes + +import ( + "fmt" + + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + + suideploy "github.com/smartcontractkit/chainlink-sui/deployment" +) + +// packageIDToBytes encodes a Sui package ID as a 32-byte, left-padded address for +// cross-chain CCIP references (same convention as EVM offramp source onramp config). +func packageIDToBytes(address string) ([]byte, error) { + out, err := suideploy.StrTo32(address) + if err != nil { + return nil, fmt.Errorf("invalid Sui package id %q: %w", address, err) + } + return out, nil +} + +func loadChainState(env cldf.Environment, chainSelector uint64) (suideploy.CCIPChainState, error) { + stateMap, err := suideploy.LoadOnchainStatesui(env) + if err != nil { + return suideploy.CCIPChainState{}, fmt.Errorf("load sui onchain state: %w", err) + } + state, ok := stateMap[chainSelector] + if !ok { + return suideploy.CCIPChainState{}, fmt.Errorf("sui chain %d not found in address book state", chainSelector) + } + return state, nil +} + +func resolveOnRampPackageID(env cldf.Environment, chainSelector uint64) ([]byte, error) { + state, err := loadChainState(env, chainSelector) + if err != nil { + return nil, err + } + if state.OnRampAddress == "" { + return nil, fmt.Errorf("no SuiOnRamp package for chain %d in address book", chainSelector) + } + return packageIDToBytes(state.OnRampAddress) +} + +func resolveOffRampPackageID(env cldf.Environment, chainSelector uint64) ([]byte, error) { + state, err := loadChainState(env, chainSelector) + if err != nil { + return nil, err + } + if state.OffRampAddress == "" { + return nil, fmt.Errorf("no SuiOffRamp package for chain %d in address book", chainSelector) + } + return packageIDToBytes(state.OffRampAddress) +} + +func resolveCCIPPackageID(env cldf.Environment, chainSelector uint64) ([]byte, error) { + state, err := loadChainState(env, chainSelector) + if err != nil { + return nil, err + } + if state.CCIPAddress == "" { + return nil, fmt.Errorf("no SuiCCIP package for chain %d in address book", chainSelector) + } + return packageIDToBytes(state.CCIPAddress) +} + +func resolveRouterPackageID(env cldf.Environment, chainSelector uint64) ([]byte, error) { + state, err := loadChainState(env, chainSelector) + if err != nil { + return nil, err + } + if state.CCIPRouterAddress == "" { + return nil, fmt.Errorf("no SuiRouter package for chain %d in address book", chainSelector) + } + return packageIDToBytes(state.CCIPRouterAddress) +} diff --git a/deployment/lanes/addresses_test.go b/deployment/lanes/addresses_test.go new file mode 100644 index 00000000..f81e28ea --- /dev/null +++ b/deployment/lanes/addresses_test.go @@ -0,0 +1,104 @@ +package lanes_test + +import ( + "encoding/hex" + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-deployments-framework/chain" + "github.com/smartcontractkit/chainlink-deployments-framework/chain/sui" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + + "github.com/smartcontractkit/chainlink-sui/deployment/lanes" +) + +const suiTestnetSelector = uint64(9762610643973837292) + +func testEnvWithAddressBook(t *testing.T) cldf.Environment { + t.Helper() + + return cldf.Environment{ + Name: "test", + ExistingAddresses: loadTestAddressBook(t), + BlockChains: chain.NewBlockChains(map[uint64]chain.BlockChain{ + suiTestnetSelector: sui.Chain{}, + }), + } +} + +func loadTestAddressBook(t *testing.T) cldf.AddressBook { + t.Helper() + + b, err := os.ReadFile("../testdata/addresses.json") + require.NoError(t, err) + + addrsByChain := make(map[uint64]map[string]cldf.TypeAndVersion) + require.NoError(t, json.Unmarshal(b, &addrsByChain)) + + return cldf.NewMemoryAddressBookFromMap(addrsByChain) +} + +func TestSuiAdapter_AddressGettersFromAddressBook(t *testing.T) { + t.Parallel() + + env := testEnvWithAddressBook(t) + adapter := &lanes.SuiAdapter{} + + onRampPkg := "0xf87c6010be571a304f0d860857204bc66f037842156f0f6c9d80be265fd83752" + offRampPkg := "0x9438693fb18f5660aff9277240a2282be44dc01cdd7eed4e1d8de0591ad52c03" + ccipPkg := "0xece742a763bddf1e36629fa06b605497e413241afd14f05e558e80eef4f64e95" + routerPkg := "0xed4613bd35004954c07150c3e9b10230f5e23e3058bc2ca0e3e676cb43eb4dc1" + + err := lanes.WithConnectChainsEnvironment(env, func() error { + onRamp, err := adapter.GetOnRampAddress(nil, suiTestnetSelector) + require.NoError(t, err) + require.Equal(t, onRampPkg, "0x"+hex.EncodeToString(onRamp)) + + offRamp, err := adapter.GetOffRampAddress(nil, suiTestnetSelector) + require.NoError(t, err) + require.Equal(t, offRampPkg, "0x"+hex.EncodeToString(offRamp)) + + fq, err := adapter.GetFQAddress(nil, suiTestnetSelector) + require.NoError(t, err) + require.Equal(t, ccipPkg, "0x"+hex.EncodeToString(fq)) + + router, err := adapter.GetRouterAddress(nil, suiTestnetSelector) + require.NoError(t, err) + require.Equal(t, routerPkg, "0x"+hex.EncodeToString(router)) + + return nil + }) + require.NoError(t, err) +} + +func TestSuiAdapter_AddressGettersFromAddressBook_MissingRef(t *testing.T) { + t.Parallel() + + adapter := &lanes.SuiAdapter{} + env := cldf.Environment{ + ExistingAddresses: cldf.NewMemoryAddressBook(), + BlockChains: chain.NewBlockChains(map[uint64]chain.BlockChain{ + suiTestnetSelector: sui.Chain{}, + }), + } + + err := lanes.WithConnectChainsEnvironment(env, func() error { + _, err := adapter.GetOnRampAddress(nil, suiTestnetSelector) + require.Error(t, err) + require.Contains(t, err.Error(), "SuiOnRamp") + return nil + }) + require.NoError(t, err) +} + +func TestSuiAdapter_AddressGetters_RequireConnectChainsScope(t *testing.T) { + t.Parallel() + + adapter := &lanes.SuiAdapter{} + _, err := adapter.GetOnRampAddress(nil, suiTestnetSelector) + require.Error(t, err) + require.Contains(t, err.Error(), "WithConnectChainsEnvironment") +} diff --git a/deployment/lanes/defaults.go b/deployment/lanes/defaults.go new file mode 100644 index 00000000..647b2862 --- /dev/null +++ b/deployment/lanes/defaults.go @@ -0,0 +1,14 @@ +package lanes + +import suideploy "github.com/smartcontractkit/chainlink-sui/deployment" + +// Default token transfer fee fields for FeeQuoterApplyTokenTransferFeeConfigUpdatesOp on the +// Sui source leg. Values match deployment.DefaultCCIPSeqConfig / connect_sui_to_evm YAML. +var ( + DefaultLinkTokenTransferMinFeeUsdCents = suideploy.DefaultCCIPSeqConfig.AddMinFeeUsdCents[0] + DefaultLinkTokenTransferMaxFeeUsdCents = suideploy.DefaultCCIPSeqConfig.AddMaxFeeUsdCents[0] + DefaultLinkTokenTransferDeciBps = suideploy.DefaultCCIPSeqConfig.AddDeciBps[0] + DefaultLinkTokenTransferDestGasOverhead = suideploy.DefaultCCIPSeqConfig.AddDestGasOverhead[0] + DefaultLinkTokenTransferDestBytesOverhead = suideploy.DefaultCCIPSeqConfig.AddDestBytesOverhead[0] + DefaultLinkPremiumMultiplierWeiPerEth = suideploy.DefaultCCIPSeqConfig.PremiumMultiplierWeiPerEth[0] +) diff --git a/deployment/lanes/env.go b/deployment/lanes/env.go new file mode 100644 index 00000000..1242c570 --- /dev/null +++ b/deployment/lanes/env.go @@ -0,0 +1,50 @@ +package lanes + +import ( + "fmt" + "sync" + + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" +) + +// connectChainsEnv carries the active CLDF environment while ConnectChains runs. +// LaneAdapter address getters only receive datastore.DataStore, not Environment, so +// Sui resolves package IDs from addresses.json via LoadOnchainStatesui using this scope. +// +// CLD must invoke ConnectChains inside WithConnectChainsEnvironment. Other chain +// families are unaffected. +var connectChainsEnv struct { + mu sync.RWMutex + env cldf.Environment + active bool +} + +// WithConnectChainsEnvironment runs fn while SuiAdapter address getters can read +// ExistingAddresses from the given environment. +func WithConnectChainsEnvironment(e cldf.Environment, fn func() error) error { + connectChainsEnv.mu.Lock() + connectChainsEnv.env = e + connectChainsEnv.active = true + connectChainsEnv.mu.Unlock() + + defer func() { + connectChainsEnv.mu.Lock() + connectChainsEnv.active = false + connectChainsEnv.env = cldf.Environment{} + connectChainsEnv.mu.Unlock() + }() + + return fn() +} + +func connectChainsEnvironment() (cldf.Environment, error) { + connectChainsEnv.mu.RLock() + defer connectChainsEnv.mu.RUnlock() + + if !connectChainsEnv.active { + return cldf.Environment{}, fmt.Errorf( + "Sui lane address getters require ConnectChains to run inside lanes.WithConnectChainsEnvironment", + ) + } + return connectChainsEnv.env, nil +} diff --git a/deployment/lanes/translate.go b/deployment/lanes/translate.go new file mode 100644 index 00000000..5e051b10 --- /dev/null +++ b/deployment/lanes/translate.go @@ -0,0 +1,40 @@ +package lanes + +import ( + "encoding/binary" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + + ccip_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip" +) + +// TranslateDestChainConfig maps a product-level FeeQuoterDestChainConfig to the Sui +// FeeQuoter op input for configuring a remote destination on the Sui chain. +// CCIP package/state object IDs are filled by the caller from address book state. +func TranslateDestChainConfig( + cfg laneapi.FeeQuoterDestChainConfig, + destChainSelector uint64, +) ccip_ops.FeeQuoterApplyDestChainConfigUpdatesInput { + return ccip_ops.FeeQuoterApplyDestChainConfigUpdatesInput{ + DestChainSelector: destChainSelector, + IsEnabled: cfg.IsEnabled, + MaxNumberOfTokensPerMsg: cfg.MaxNumberOfTokensPerMsg, + MaxDataBytes: cfg.MaxDataBytes, + MaxPerMsgGasLimit: cfg.MaxPerMsgGasLimit, + DestGasOverhead: cfg.DestGasOverhead, + DestGasPerPayloadByteBase: cfg.DestGasPerPayloadByteBase, + DestGasPerPayloadByteHigh: cfg.DestGasPerPayloadByteHigh, + DestGasPerPayloadByteThreshold: cfg.DestGasPerPayloadByteThreshold, + DestDataAvailabilityOverheadGas: cfg.DestDataAvailabilityOverheadGas, + DestGasPerDataAvailabilityByte: cfg.DestGasPerDataAvailabilityByte, + DestDataAvailabilityMultiplierBps: cfg.DestDataAvailabilityMultiplierBps, + ChainFamilySelector: binary.BigEndian.AppendUint32(nil, cfg.ChainFamilySelector), + EnforceOutOfOrder: cfg.EnforceOutOfOrder, + DefaultTokenFeeUsdCents: cfg.DefaultTokenFeeUSDCents, + DefaultTokenDestGasOverhead: cfg.DefaultTokenDestGasOverhead, + DefaultTxGasLimit: cfg.DefaultTxGasLimit, + GasMultiplierWeiPerEth: cfg.GasMultiplierWeiPerEth, + GasPriceStalenessThreshold: cfg.GasPriceStalenessThreshold, + NetworkFeeUsdCents: cfg.NetworkFeeUSDCents, + } +} From 7c252dba51a49214f4a0be5b9ffff8274cc2b330 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Wed, 24 Jun 2026 18:10:51 -0400 Subject: [PATCH 02/13] more tooling API support --- deployment/lanes/batchop.go | 36 +++++ deployment/lanes/connect_chains.go | 77 +++++++++ deployment/lanes/connect_chains_source.go | 151 ++++++++++++++++++ deployment/lanes/connect_chains_test.go | 92 +++++++++++ deployment/lanes/disable_remote_chain.go | 24 +++ deployment/lanes/env.go | 55 +++++++ deployment/lanes/latest_package_ids.go | 19 +++ deployment/lanes/register.go | 13 ++ deployment/lanes/state.go | 87 ++++++++++ deployment/ops/ccip_offramp/op_deploy.go | 11 +- .../ops/ccip_offramp/op_offramp_mcms_test.go | 26 +++ deployment/utils/mcms.go | 33 ++++ deployment/utils/mcms_tx_test.go | 35 ++++ 13 files changed, 658 insertions(+), 1 deletion(-) create mode 100644 deployment/lanes/batchop.go create mode 100644 deployment/lanes/connect_chains.go create mode 100644 deployment/lanes/connect_chains_source.go create mode 100644 deployment/lanes/connect_chains_test.go create mode 100644 deployment/lanes/disable_remote_chain.go create mode 100644 deployment/lanes/latest_package_ids.go create mode 100644 deployment/lanes/register.go create mode 100644 deployment/lanes/state.go create mode 100644 deployment/utils/mcms_tx_test.go diff --git a/deployment/lanes/batchop.go b/deployment/lanes/batchop.go new file mode 100644 index 00000000..78aab016 --- /dev/null +++ b/deployment/lanes/batchop.go @@ -0,0 +1,36 @@ +package lanes + +import ( + "fmt" + + mcmstypes "github.com/smartcontractkit/mcms/types" + + "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" + sui_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops" + "github.com/smartcontractkit/chainlink-sui/deployment/utils" +) + +func appendMCMSBatchOpFromCall( + out *sequences.OnChainOutput, + chainSelector uint64, + call sui_ops.TransactionCall, + deps sui_ops.OpTxDeps, +) error { + if deps.Signer != nil { + return nil + } + if call.PackageID == "" { + return nil + } + + tx, err := utils.TransactionCallToMCMSTransaction(call) + if err != nil { + return fmt.Errorf("create MCMS transaction from op call: %w", err) + } + + out.BatchOps = append(out.BatchOps, mcmstypes.BatchOperation{ + ChainSelector: mcmstypes.ChainSelector(chainSelector), + Transactions: []mcmstypes.Transaction{tx}, + }) + return nil +} diff --git a/deployment/lanes/connect_chains.go b/deployment/lanes/connect_chains.go new file mode 100644 index 00000000..31de4e0e --- /dev/null +++ b/deployment/lanes/connect_chains.go @@ -0,0 +1,77 @@ +package lanes + +import ( + "fmt" + + "github.com/Masterminds/semver/v3" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" + cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain" + cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + + ccip_offramp_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip_offramp" +) + +// ConfigureLaneLegAsDest wires a remote source chain into the Sui OffRamp. +// Sui has no router offramp step for inbound lanes (unlike Solana). +var ConfigureLaneLegAsDest = cldf_ops.NewSequence( + "ConfigureLaneLegAsDest", + semver.MustParse("1.6.0"), + "Configures lane leg as destination on CCIP 1.6.0 for Sui", + func(b cldf_ops.Bundle, chains cldf_chain.BlockChains, input laneapi.UpdateLanesInput) (sequences.OnChainOutput, error) { + if input.Source == nil || input.Dest == nil { + return sequences.OnChainOutput{}, fmt.Errorf("ConfigureLaneLegAsDest requires Source and Dest chain definitions") + } + if len(input.Source.OnRamp) == 0 { + return sequences.OnChainOutput{}, fmt.Errorf("source OnRamp address required to configure Sui as destination for chain %d", input.Source.Selector) + } + + env, err := connectChainsEnvironment() + if err != nil { + return sequences.OnChainOutput{}, err + } + + state, err := loadOffRampState(env, input.Dest.Selector) + if err != nil { + return sequences.OnChainOutput{}, err + } + + deps, err := opTxDepsForChain(chains, input.Dest.Selector) + if err != nil { + return sequences.OnChainOutput{}, err + } + + latestIDs := resolveLatestPackageIDs(input.Dest.Selector) + + opInput := ccip_offramp_ops.ApplySourceChainConfigUpdateInput{ + CCIPObjectRef: state.CCIPObjectRef, + OffRampPackageId: state.OffRampAddress, + LatestPackageId: latestIDs.OffRamp, + OffRampStateId: state.OffRampStateObjectId, + OwnerCapObjectId: state.OffRampOwnerCapId, + SourceChainsSelectors: []uint64{input.Source.Selector}, + SourceChainsIsEnabled: []bool{!input.IsDisabled}, + SourceChainsIsRMNVerificationDisabled: []bool{!input.Source.RMNVerificationEnabled}, + SourceChainsOnRamp: [][]byte{append([]byte(nil), input.Source.OnRamp...)}, + } + + report, err := cldf_ops.ExecuteOperation(b, ccip_offramp_ops.ApplySourceChainConfigUpdatesOp, deps, opInput) + if err != nil { + return sequences.OnChainOutput{}, fmt.Errorf( + "failed to apply source chain config on Sui OffRamp for source %d: %w", + input.Source.Selector, err, + ) + } + + var out sequences.OnChainOutput + if err := appendMCMSBatchOpFromCall(&out, input.Dest.Selector, report.Output.Call, deps); err != nil { + return sequences.OnChainOutput{}, err + } + return out, nil + }, +) + +func (a *SuiAdapter) ConfigureLaneLegAsDest() *cldf_ops.Sequence[laneapi.UpdateLanesInput, sequences.OnChainOutput, cldf_chain.BlockChains] { + return ConfigureLaneLegAsDest +} diff --git a/deployment/lanes/connect_chains_source.go b/deployment/lanes/connect_chains_source.go new file mode 100644 index 00000000..b6d7580a --- /dev/null +++ b/deployment/lanes/connect_chains_source.go @@ -0,0 +1,151 @@ +package lanes + +import ( + "fmt" + + "github.com/Masterminds/semver/v3" + "github.com/ethereum/go-ethereum/common" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" + cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain" + cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + + ccip_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip" + ccip_onramp_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip_onramp" + ccip_router_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip_router" +) + +// ConfigureLaneLegAsSource wires FeeQuoter, OnRamp, and Router on Sui for outbound messages +// to a remote destination chain. Op order matches connect_sui_to_evm. +var ConfigureLaneLegAsSource = cldf_ops.NewSequence( + "ConfigureLaneLegAsSource", + semver.MustParse("1.6.0"), + "Configures lane leg as source on CCIP 1.6.0 for Sui", + func(b cldf_ops.Bundle, chains cldf_chain.BlockChains, input laneapi.UpdateLanesInput) (sequences.OnChainOutput, error) { + if input.Source == nil || input.Dest == nil { + return sequences.OnChainOutput{}, fmt.Errorf("ConfigureLaneLegAsSource requires Source and Dest chain definitions") + } + if len(input.Dest.Router) == 0 { + return sequences.OnChainOutput{}, fmt.Errorf("dest Router address required to configure Sui as source for chain %d", input.Dest.Selector) + } + + env, err := connectChainsEnvironment() + if err != nil { + return sequences.OnChainOutput{}, err + } + + state, err := loadSourceChainState(env, input.Source.Selector) + if err != nil { + return sequences.OnChainOutput{}, err + } + + deps, err := opTxDepsForChain(chains, input.Source.Selector) + if err != nil { + return sequences.OnChainOutput{}, err + } + + destRouter, err := evmAddressHexFromBytes(input.Dest.Router) + if err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("decode dest router for chain %d: %w", input.Dest.Selector, err) + } + + var out sequences.OnChainOutput + + tokenFeeReport, err := cldf_ops.ExecuteOperation(b, ccip_ops.FeeQuoterApplyTokenTransferFeeConfigUpdatesOp, deps, ccip_ops.FeeQuoterApplyTokenTransferFeeConfigUpdatesInput{ + CCIPPackageId: state.CCIPAddress, + StateObjectId: state.CCIPObjectRef, + OwnerCapObjectId: state.CCIPOwnerCapObjectId, + DestChainSelector: input.Dest.Selector, + AddTokens: []string{state.LinkTokenCoinMetadataId}, + AddMinFeeUsdCents: []uint32{DefaultLinkTokenTransferMinFeeUsdCents}, + AddMaxFeeUsdCents: []uint32{DefaultLinkTokenTransferMaxFeeUsdCents}, + AddDeciBps: []uint16{DefaultLinkTokenTransferDeciBps}, + AddDestGasOverhead: []uint32{DefaultLinkTokenTransferDestGasOverhead}, + AddDestBytesOverhead: []uint32{DefaultLinkTokenTransferDestBytesOverhead}, + AddIsEnabled: []bool{true}, + }) + if err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("apply token transfer fee config on Sui for dest %d: %w", input.Dest.Selector, err) + } + if err := appendMCMSBatchOpFromCall(&out, input.Source.Selector, tokenFeeReport.Output.Call, deps); err != nil { + return sequences.OnChainOutput{}, err + } + + destCfgInput := TranslateDestChainConfig(input.Dest.FeeQuoterDestChainConfig, input.Dest.Selector) + destCfgInput.CCIPPackageId = state.CCIPAddress + destCfgInput.StateObjectId = state.CCIPObjectRef + destCfgInput.OwnerCapObjectId = state.CCIPOwnerCapObjectId + destCfgReport, err := cldf_ops.ExecuteOperation(b, ccip_ops.FeeQuoterApplyDestChainConfigUpdatesOp, deps, destCfgInput) + if err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("apply dest chain config on Sui FeeQuoter for dest %d: %w", input.Dest.Selector, err) + } + if err := appendMCMSBatchOpFromCall(&out, input.Source.Selector, destCfgReport.Output.Call, deps); err != nil { + return sequences.OnChainOutput{}, err + } + + premiumReport, err := cldf_ops.ExecuteOperation(b, ccip_ops.FeeQuoterApplyPremiumMultiplierWeiPerEthUpdatesOp, deps, ccip_ops.FeeQuoterApplyPremiumMultiplierWeiPerEthUpdatesInput{ + CCIPPackageId: state.CCIPAddress, + StateObjectId: state.CCIPObjectRef, + OwnerCapObjectId: state.CCIPOwnerCapObjectId, + Tokens: []string{state.LinkTokenCoinMetadataId}, + PremiumMultiplierWeiPerEth: []uint64{DefaultLinkPremiumMultiplierWeiPerEth}, + }) + if err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("apply premium multiplier on Sui FeeQuoter for dest %d: %w", input.Dest.Selector, err) + } + if err := appendMCMSBatchOpFromCall(&out, input.Source.Selector, premiumReport.Output.Call, deps); err != nil { + return sequences.OnChainOutput{}, err + } + + onRampReport, err := cldf_ops.ExecuteOperation(b, ccip_onramp_ops.ApplyDestChainConfigUpdateOp, deps, ccip_onramp_ops.ApplyDestChainConfigureOnRampInput{ + OnRampPackageId: state.OnRampAddress, + CCIPObjectRefId: state.CCIPObjectRef, + OwnerCapObjectId: state.OnRampOwnerCapObjectId, + StateObjectId: state.OnRampStateObjectId, + DestChainSelector: []uint64{input.Dest.Selector}, + DestChainAllowListEnabled: []bool{input.Source.AllowListEnabled}, + DestChainRouters: []string{destRouter}, + }) + if err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("apply dest chain config on Sui OnRamp for dest %d: %w", input.Dest.Selector, err) + } + if err := appendMCMSBatchOpFromCall(&out, input.Source.Selector, onRampReport.Output.Call, deps); err != nil { + return sequences.OnChainOutput{}, err + } + + routerReport, err := cldf_ops.ExecuteOperation(b, ccip_router_ops.SetOnRampsOp, deps, ccip_router_ops.SetOnRampsInput{ + RouterPackageId: state.CCIPRouterAddress, + RouterStateObjectId: state.CCIPRouterStateObjectID, + OwnerCapObjectId: state.CCIPRouterOwnerCapObjectId, + DestChainSelectors: []uint64{input.Dest.Selector}, + OnRampAddresses: []string{state.OnRampAddress}, + }) + if err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("set on-ramps on Sui Router for dest %d: %w", input.Dest.Selector, err) + } + if err := appendMCMSBatchOpFromCall(&out, input.Source.Selector, routerReport.Output.Call, deps); err != nil { + return sequences.OnChainOutput{}, err + } + + return out, nil + }, +) + +func (a *SuiAdapter) ConfigureLaneLegAsSource() *cldf_ops.Sequence[laneapi.UpdateLanesInput, sequences.OnChainOutput, cldf_chain.BlockChains] { + return ConfigureLaneLegAsSource +} + +func evmAddressHexFromBytes(b []byte) (string, error) { + if len(b) == 0 { + return "", fmt.Errorf("empty address bytes") + } + switch len(b) { + case 20: + return common.BytesToAddress(b).Hex(), nil + case 32: + return common.BytesToAddress(b[12:]).Hex(), nil + default: + return "", fmt.Errorf("unexpected address length %d", len(b)) + } +} diff --git a/deployment/lanes/connect_chains_test.go b/deployment/lanes/connect_chains_test.go new file mode 100644 index 00000000..d8e761ee --- /dev/null +++ b/deployment/lanes/connect_chains_test.go @@ -0,0 +1,92 @@ +package lanes_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" + "github.com/smartcontractkit/chainlink-deployments-framework/chain" + "github.com/smartcontractkit/chainlink-deployments-framework/chain/sui" + cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + + "github.com/smartcontractkit/chainlink-sui/deployment/lanes" + "github.com/smartcontractkit/chainlink-sui/deployment/ops/mcmstest" + "github.com/smartcontractkit/chainlink-sui/deployment/utils" +) + +const evmSepoliaSelector = uint64(16015286601757825753) + +func TestConfigureLaneLegAsDest_MCMSBatchOp(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := chain.NewBlockChains(map[uint64]chain.BlockChain{ + suiTestnetSelector: sui.Chain{}, + }) + input := laneapi.UpdateLanesInput{ + Source: &laneapi.ChainDefinition{ + Selector: evmSepoliaSelector, + OnRamp: make([]byte, 32), + RMNVerificationEnabled: true, + }, + Dest: &laneapi.ChainDefinition{ + Selector: suiTestnetSelector, + }, + } + + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] + err := lanes.WithConnectChainsEnvironment(env, func() error { + var execErr error + report, execErr = cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + chains, + input, + ) + return execErr + }) + require.NoError(t, err) + require.Len(t, report.Output.BatchOps, 1) + require.Equal(t, suiTestnetSelector, uint64(report.Output.BatchOps[0].ChainSelector)) + require.Len(t, report.Output.BatchOps[0].Transactions, 1) + require.NotEmpty(t, report.Output.BatchOps[0].Transactions[0].Data) +} + +func TestConfigureLaneLegAsDest_MCMSBatchOp_WithLatestPackageID(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := chain.NewBlockChains(map[uint64]chain.BlockChain{ + suiTestnetSelector: sui.Chain{}, + }) + const latestOffRampPackageID = "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + input := laneapi.UpdateLanesInput{ + Source: &laneapi.ChainDefinition{ + Selector: evmSepoliaSelector, + OnRamp: make([]byte, 32), + RMNVerificationEnabled: true, + }, + Dest: &laneapi.ChainDefinition{ + Selector: suiTestnetSelector, + }, + } + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] + err := lanes.WithConnectChainsEnvironment(env, func() error { + return lanes.WithSuiLatestPackageIDs(map[uint64]lanes.LatestPackageIDsConfig{ + suiTestnetSelector: {OffRamp: latestOffRampPackageID}, + }, func() error { + var execErr error + report, execErr = cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + chains, + input, + ) + return execErr + }) + }) + require.NoError(t, err) + require.Len(t, report.Output.BatchOps, 1) + tx := report.Output.BatchOps[0].Transactions[0] + latestPackageIDFromTx, err := utils.TransactionLatestPackageID(tx) + require.NoError(t, err) + require.Equal(t, latestOffRampPackageID, latestPackageIDFromTx) +} diff --git a/deployment/lanes/disable_remote_chain.go b/deployment/lanes/disable_remote_chain.go new file mode 100644 index 00000000..9ce43211 --- /dev/null +++ b/deployment/lanes/disable_remote_chain.go @@ -0,0 +1,24 @@ +package lanes + +import ( + "github.com/Masterminds/semver/v3" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" + cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain" + cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" +) + +// DisableRemoteChain is a no-op on Sui until remote-chain disable is implemented on-chain. +var DisableRemoteChain = cldf_ops.NewSequence( + "DisableRemoteChain", + semver.MustParse("1.6.0"), + "No-op disable remote chain sequence for Sui", + func(_ cldf_ops.Bundle, _ cldf_chain.BlockChains, _ laneapi.DisableRemoteChainInput) (sequences.OnChainOutput, error) { + return sequences.OnChainOutput{}, nil + }, +) + +func (a *SuiAdapter) DisableRemoteChain() *cldf_ops.Sequence[laneapi.DisableRemoteChainInput, sequences.OnChainOutput, cldf_chain.BlockChains] { + return DisableRemoteChain +} diff --git a/deployment/lanes/env.go b/deployment/lanes/env.go index 1242c570..2c2846b6 100644 --- a/deployment/lanes/env.go +++ b/deployment/lanes/env.go @@ -37,6 +37,61 @@ func WithConnectChainsEnvironment(e cldf.Environment, fn func() error) error { return fn() } +var latestPackageIDsEnv struct { + mu sync.RWMutex + bySelector map[uint64]LatestPackageIDsConfig + active bool +} + +// WithSuiLatestPackageIDs runs fn while lane sequences read optional upgraded package IDs +// keyed by Sui chain selector. CLD resolver populates this map from durable pipeline YAML. +func WithSuiLatestPackageIDs(bySelector map[uint64]LatestPackageIDsConfig, fn func() error) error { + latestPackageIDsEnv.mu.Lock() + latestPackageIDsEnv.bySelector = copyLatestPackageIDsBySelector(bySelector) + latestPackageIDsEnv.active = true + latestPackageIDsEnv.mu.Unlock() + + defer func() { + latestPackageIDsEnv.mu.Lock() + latestPackageIDsEnv.active = false + latestPackageIDsEnv.bySelector = nil + latestPackageIDsEnv.mu.Unlock() + }() + + return fn() +} + +// RunConnectChainsWithSuiScopes wraps ConnectChains with address-book and resolver latest-package-ID scopes. +func RunConnectChainsWithSuiScopes( + e cldf.Environment, + latestBySelector map[uint64]LatestPackageIDsConfig, + fn func() error, +) error { + return WithConnectChainsEnvironment(e, func() error { + return WithSuiLatestPackageIDs(latestBySelector, fn) + }) +} + +func currentLatestPackageIDs(chainSelector uint64) LatestPackageIDsConfig { + latestPackageIDsEnv.mu.RLock() + defer latestPackageIDsEnv.mu.RUnlock() + if !latestPackageIDsEnv.active || latestPackageIDsEnv.bySelector == nil { + return LatestPackageIDsConfig{} + } + return latestPackageIDsEnv.bySelector[chainSelector] +} + +func copyLatestPackageIDsBySelector(in map[uint64]LatestPackageIDsConfig) map[uint64]LatestPackageIDsConfig { + if len(in) == 0 { + return nil + } + out := make(map[uint64]LatestPackageIDsConfig, len(in)) + for selector, ids := range in { + out[selector] = ids + } + return out +} + func connectChainsEnvironment() (cldf.Environment, error) { connectChainsEnv.mu.RLock() defer connectChainsEnv.mu.RUnlock() diff --git a/deployment/lanes/latest_package_ids.go b/deployment/lanes/latest_package_ids.go new file mode 100644 index 00000000..32b634d3 --- /dev/null +++ b/deployment/lanes/latest_package_ids.go @@ -0,0 +1,19 @@ +package lanes + +// LatestPackageIDsConfig holds optional upgraded Sui package IDs for MCMS PTB routing. +// Package IDs in the address book remain the MCMS registry identity; latest IDs route +// execution to upgraded bytecode. +// +// Populated by CLD via WithSuiLatestPackageIDs from durable pipeline YAML resolver output. +type LatestPackageIDsConfig struct { + OffRamp string + CCIP string + OnRamp string + Router string +} + +// resolveLatestPackageIDs returns upgraded package IDs for MCMS execution on chainSelector. +// Empty when no resolver scope is active or the selector has no overrides (pre-upgrade is OK). +func resolveLatestPackageIDs(chainSelector uint64) LatestPackageIDsConfig { + return currentLatestPackageIDs(chainSelector) +} diff --git a/deployment/lanes/register.go b/deployment/lanes/register.go new file mode 100644 index 00000000..edf0eb59 --- /dev/null +++ b/deployment/lanes/register.go @@ -0,0 +1,13 @@ +package lanes + +import ( + "github.com/Masterminds/semver/v3" + + chainsel "github.com/smartcontractkit/chain-selectors" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" +) + +func init() { + laneapi.GetLaneAdapterRegistry().RegisterLaneAdapter(chainsel.FamilySui, semver.MustParse("1.6.0"), &SuiAdapter{}) +} diff --git a/deployment/lanes/state.go b/deployment/lanes/state.go new file mode 100644 index 00000000..bb000b3a --- /dev/null +++ b/deployment/lanes/state.go @@ -0,0 +1,87 @@ +package lanes + +import ( + "fmt" + + cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + + "github.com/smartcontractkit/chainlink-sui/bindings/bind" + suideploy "github.com/smartcontractkit/chainlink-sui/deployment" + sui_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops" +) + +func opTxDepsForChain(chains cldf_chain.BlockChains, chainSelector uint64) (sui_ops.OpTxDeps, error) { + chain, ok := chains.SuiChains()[chainSelector] + if !ok { + return sui_ops.OpTxDeps{}, fmt.Errorf("sui chain with selector %d not found in environment", chainSelector) + } + + return sui_ops.OpTxDeps{ + Client: chain.Client, + Signer: chain.Signer, + GetCallOpts: func() *bind.CallOpts { + gasBudget := uint64(400_000_000) + return &bind.CallOpts{WaitForExecution: true, GasBudget: &gasBudget} + }, + SuiRPC: chain.URL, + }, nil +} + +func loadOffRampState(env cldf.Environment, chainSelector uint64) (suideploy.CCIPChainState, error) { + state, err := loadChainState(env, chainSelector) + if err != nil { + return suideploy.CCIPChainState{}, err + } + if state.CCIPObjectRef == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing CCIPObjectRef for sui chain %d in address book", chainSelector) + } + if state.OffRampAddress == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiOffRamp package for sui chain %d in address book", chainSelector) + } + if state.OffRampStateObjectId == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiOffRampStateObjectID for sui chain %d in address book", chainSelector) + } + if state.OffRampOwnerCapId == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiOffRampOwnerCapObjectID for sui chain %d in address book", chainSelector) + } + return state, nil +} + +func loadSourceChainState(env cldf.Environment, chainSelector uint64) (suideploy.CCIPChainState, error) { + state, err := loadChainState(env, chainSelector) + if err != nil { + return suideploy.CCIPChainState{}, err + } + if state.CCIPObjectRef == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing CCIPObjectRef for sui chain %d in address book", chainSelector) + } + if state.CCIPAddress == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiCCIP package for sui chain %d in address book", chainSelector) + } + if state.CCIPOwnerCapObjectId == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiCCIPOwnerCapObjectID for sui chain %d in address book", chainSelector) + } + if state.LinkTokenCoinMetadataId == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiLinkTokenObjectMetadataID for sui chain %d in address book", chainSelector) + } + if state.OnRampAddress == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiOnRamp package for sui chain %d in address book", chainSelector) + } + if state.OnRampStateObjectId == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiOnRampStateObjectID for sui chain %d in address book", chainSelector) + } + if state.OnRampOwnerCapObjectId == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiOnRampOwnerCapObjectID for sui chain %d in address book", chainSelector) + } + if state.CCIPRouterAddress == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiRouter package for sui chain %d in address book", chainSelector) + } + if state.CCIPRouterStateObjectID == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiRouterStateObjectID for sui chain %d in address book", chainSelector) + } + if state.CCIPRouterOwnerCapObjectId == "" { + return suideploy.CCIPChainState{}, fmt.Errorf("missing SuiRouterOwnerCapObjectID for sui chain %d in address book", chainSelector) + } + return state, nil +} diff --git a/deployment/ops/ccip_offramp/op_deploy.go b/deployment/ops/ccip_offramp/op_deploy.go index 2f9678e2..c2b33479 100644 --- a/deployment/ops/ccip_offramp/op_deploy.go +++ b/deployment/ops/ccip_offramp/op_deploy.go @@ -181,6 +181,7 @@ var setOCR3ConfigHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input S type ApplySourceChainConfigUpdateInput struct { CCIPObjectRef string OffRampPackageId string + LatestPackageId string // optional: upgraded package ID for PTB execution when OffRampPackageId is the MCMS registry identity OffRampStateId string OwnerCapObjectId string SourceChainsSelectors []uint64 @@ -190,7 +191,11 @@ type ApplySourceChainConfigUpdateInput struct { } var applySourceChainConfigUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input ApplySourceChainConfigUpdateInput) (output sui_ops.OpTxResult[DeployCCIPOffRampObjects], err error) { - offRampPackage, err := module_offramp.NewOfframp(input.OffRampPackageId, deps.Client) + binaryPkgId := input.OffRampPackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + offRampPackage, err := module_offramp.NewOfframp(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, err } @@ -211,6 +216,10 @@ var applySourceChainConfigUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.Op if err != nil { return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID + call.PackageID = input.OffRampPackageId + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of ApplySourceChainConfigUpdates on OffRamp as per no Signer provided") return sui_ops.OpTxResult[DeployCCIPOffRampObjects]{ diff --git a/deployment/ops/ccip_offramp/op_offramp_mcms_test.go b/deployment/ops/ccip_offramp/op_offramp_mcms_test.go index a5e29b80..5ce41bd3 100644 --- a/deployment/ops/ccip_offramp/op_offramp_mcms_test.go +++ b/deployment/ops/ccip_offramp/op_offramp_mcms_test.go @@ -62,6 +62,32 @@ func TestOffRampDualModeOps_ProposalDataMatchesBindingEncoder(t *testing.T) { mcmstest.AssertProposalDataMatches(t, report.Output.Call.Data, encoded, input.OffRampStateId, nil) }) + t.Run("apply_source_chain_config_updates_with_latest_package_id", func(t *testing.T) { + t.Parallel() + const latestPackageID = "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + input := ApplySourceChainConfigUpdateInput{ + CCIPObjectRef: mcmstest.StateObjectID, + OffRampPackageId: mcmstest.PackageID, + LatestPackageId: latestPackageID, + OffRampStateId: mcmstest.CoinMetadata, + OwnerCapObjectId: mcmstest.OwnerCapID, + SourceChainsSelectors: []uint64{mcmstest.DestChainSel}, + SourceChainsIsEnabled: []bool{true}, + SourceChainsIsRMNVerificationDisabled: []bool{false}, + SourceChainsOnRamp: [][]byte{{0x01}}, + } + report, err := cld_ops.ExecuteOperation(mcmstest.Bundle(t), ApplySourceChainConfigUpdatesOp, sui_ops.OpTxDeps{}, input) + require.NoError(t, err) + require.Equal(t, mcmstest.PackageID, report.Output.Call.PackageID) + require.Equal(t, latestPackageID, report.Output.Call.LatestPackageID) + + latestOfframp, err := module_offramp.NewOfframp(latestPackageID, nil) + require.NoError(t, err) + encoded, err := latestOfframp.Encoder().ApplySourceChainConfigUpdates(bind.Object{Id: input.CCIPObjectRef}, bind.Object{Id: input.OffRampStateId}, bind.Object{Id: input.OwnerCapObjectId}, input.SourceChainsSelectors, input.SourceChainsIsEnabled, input.SourceChainsIsRMNVerificationDisabled, input.SourceChainsOnRamp) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches(t, report.Output.Call.Data, encoded, input.OffRampStateId, nil) + }) + t.Run("add_package_id", func(t *testing.T) { t.Parallel() input := AddPackageIdOffRampInput{ diff --git a/deployment/utils/mcms.go b/deployment/utils/mcms.go index 8aeccf27..593d04c3 100644 --- a/deployment/utils/mcms.go +++ b/deployment/utils/mcms.go @@ -86,6 +86,39 @@ func GenerateProposal(ctx context.Context, input GenerateProposalInput) (*mcms.T return builder.Build() } +// TransactionCallToMCMSTransaction converts an encoded Sui op call into an MCMS transaction. +// When call.LatestPackageID is set, it is propagated for upgraded-package PTB routing. +func TransactionCallToMCMSTransaction(call sui_ops.TransactionCall) (types.Transaction, error) { + tx, err := suisdk.NewTransactionWithStateObj( + call.Module, + call.Function, + call.PackageID, + call.Data, + call.Module, + []string{}, + call.StateObjID, + call.TypeArgs, + ) + if err != nil { + return types.Transaction{}, fmt.Errorf("create MCMS transaction: %w", err) + } + if call.LatestPackageID != "" { + if setErr := suisdk.SetLatestPackageID(&tx, call.LatestPackageID); setErr != nil { + return types.Transaction{}, fmt.Errorf("set latest package ID: %w", setErr) + } + } + return tx, nil +} + +// TransactionLatestPackageID returns the optional upgraded package ID stored on an MCMS transaction. +func TransactionLatestPackageID(tx types.Transaction) (string, error) { + var fields suisdk.AdditionalFields + if err := json.Unmarshal(tx.AdditionalFields, &fields); err != nil { + return "", fmt.Errorf("unmarshal transaction additional fields: %w", err) + } + return fields.LatestPackageID, nil +} + func ExtractTransactionCall(output interface{}, operationID string) (sui_ops.TransactionCall, error) { jsonBytes, err := json.Marshal(output) if err != nil { diff --git a/deployment/utils/mcms_tx_test.go b/deployment/utils/mcms_tx_test.go new file mode 100644 index 00000000..88b03d32 --- /dev/null +++ b/deployment/utils/mcms_tx_test.go @@ -0,0 +1,35 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sui_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops" + "github.com/smartcontractkit/chainlink-sui/deployment/utils" +) + +func TestTransactionCallToMCMSTransaction_LatestPackageID(t *testing.T) { + t.Parallel() + + const ( + originalPackageID = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + latestPackageID = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + ) + + call := sui_ops.TransactionCall{ + PackageID: originalPackageID, + LatestPackageID: latestPackageID, + Module: "offramp", + Function: "apply_source_chain_config_updates", + Data: []byte{0x01}, + StateObjID: "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + } + + tx, err := utils.TransactionCallToMCMSTransaction(call) + require.NoError(t, err) + require.Equal(t, originalPackageID, tx.To) + latestPackageIDFromTx, err := utils.TransactionLatestPackageID(tx) + require.NoError(t, err) + require.Equal(t, latestPackageID, latestPackageIDFromTx) +} From 844eea91bb150aee180d99c0f23b32eeb2faa647 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Wed, 24 Jun 2026 18:31:55 -0400 Subject: [PATCH 03/13] add tests --- deployment/lanes/connect_chains_test.go | 218 +++++++++++++++++++++--- deployment/testdata/addresses.json | 4 + 2 files changed, 201 insertions(+), 21 deletions(-) diff --git a/deployment/lanes/connect_chains_test.go b/deployment/lanes/connect_chains_test.go index d8e761ee..e1e3eab6 100644 --- a/deployment/lanes/connect_chains_test.go +++ b/deployment/lanes/connect_chains_test.go @@ -3,27 +3,42 @@ package lanes_test import ( "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" "github.com/smartcontractkit/chainlink-deployments-framework/chain" "github.com/smartcontractkit/chainlink-deployments-framework/chain/sui" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + "github.com/smartcontractkit/chainlink-sui/bindings/bind" + module_fee_quoter "github.com/smartcontractkit/chainlink-sui/bindings/generated/ccip/ccip/fee_quoter" "github.com/smartcontractkit/chainlink-sui/deployment/lanes" "github.com/smartcontractkit/chainlink-sui/deployment/ops/mcmstest" "github.com/smartcontractkit/chainlink-sui/deployment/utils" ) -const evmSepoliaSelector = uint64(16015286601757825753) +const ( + evmSepoliaSelector = uint64(16015286601757825753) + evmRouterAddress = "0xd3E190f381f06DC0d289590fd452C42Fa2DAC586" -func TestConfigureLaneLegAsDest_MCMSBatchOp(t *testing.T) { - env := testEnvWithAddressBook(t) - chains := chain.NewBlockChains(map[uint64]chain.BlockChain{ + testCCIPPackageID = "0xece742a763bddf1e36629fa06b605497e413241afd14f05e558e80eef4f64e95" + testOffRampPackageID = "0x9438693fb18f5660aff9277240a2282be44dc01cdd7eed4e1d8de0591ad52c03" + testCCIPObjectRef = "0xbeace36c3c1e1f37c5806c4954140d15cbc7b0002ef7ccb490de26e82f5ec4ca" + testCCIPOwnerCapID = "0x874447a3ac6ae37bf545c6d71b0fa4a3d0af56a3f339faf4a4bab16ca4956ce7" + testLinkCoinMetadata = "0x8afb916ec72b91d28f519539659ebb1200b1824ff1f8d4c8f433acbb03017f2f" +) + +func testSuiChains() chain.BlockChains { + return chain.NewBlockChains(map[uint64]chain.BlockChain{ suiTestnetSelector: sui.Chain{}, }) - input := laneapi.UpdateLanesInput{ +} + +func destLegInput() laneapi.UpdateLanesInput { + return laneapi.UpdateLanesInput{ Source: &laneapi.ChainDefinition{ Selector: evmSepoliaSelector, OnRamp: make([]byte, 32), @@ -33,6 +48,31 @@ func TestConfigureLaneLegAsDest_MCMSBatchOp(t *testing.T) { Selector: suiTestnetSelector, }, } +} + +func sourceLegInput(destRouter []byte) laneapi.UpdateLanesInput { + return laneapi.UpdateLanesInput{ + Source: &laneapi.ChainDefinition{ + Selector: suiTestnetSelector, + }, + Dest: &laneapi.ChainDefinition{ + Selector: evmSepoliaSelector, + Router: destRouter, + FeeQuoterDestChainConfig: laneapi.FeeQuoterDestChainConfig{ + IsEnabled: true, + MaxDataBytes: 30_000, + MaxPerMsgGasLimit: 3_000_000, + ChainFamilySelector: 0x2812d52c, + DefaultTokenFeeUSDCents: 25, + NetworkFeeUSDCents: 10, + }, + }, + } +} + +func TestConfigureLaneLegAsDest_MCMSBatchOp(t *testing.T) { + env := testEnvWithAddressBook(t) + input := destLegInput() var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] err := lanes.WithConnectChainsEnvironment(env, func() error { @@ -40,7 +80,7 @@ func TestConfigureLaneLegAsDest_MCMSBatchOp(t *testing.T) { report, execErr = cldf_ops.ExecuteSequence( mcmstest.Bundle(t), lanes.ConfigureLaneLegAsDest, - chains, + testSuiChains(), input, ) return execErr @@ -49,25 +89,20 @@ func TestConfigureLaneLegAsDest_MCMSBatchOp(t *testing.T) { require.Len(t, report.Output.BatchOps, 1) require.Equal(t, suiTestnetSelector, uint64(report.Output.BatchOps[0].ChainSelector)) require.Len(t, report.Output.BatchOps[0].Transactions, 1) - require.NotEmpty(t, report.Output.BatchOps[0].Transactions[0].Data) + + tx := report.Output.BatchOps[0].Transactions[0] + require.NotEmpty(t, tx.Data) + require.Equal(t, testOffRampPackageID, tx.To) + latestPackageID, err := utils.TransactionLatestPackageID(tx) + require.NoError(t, err) + require.Empty(t, latestPackageID) } func TestConfigureLaneLegAsDest_MCMSBatchOp_WithLatestPackageID(t *testing.T) { env := testEnvWithAddressBook(t) - chains := chain.NewBlockChains(map[uint64]chain.BlockChain{ - suiTestnetSelector: sui.Chain{}, - }) const latestOffRampPackageID = "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" - input := laneapi.UpdateLanesInput{ - Source: &laneapi.ChainDefinition{ - Selector: evmSepoliaSelector, - OnRamp: make([]byte, 32), - RMNVerificationEnabled: true, - }, - Dest: &laneapi.ChainDefinition{ - Selector: suiTestnetSelector, - }, - } + input := destLegInput() + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] err := lanes.WithConnectChainsEnvironment(env, func() error { return lanes.WithSuiLatestPackageIDs(map[uint64]lanes.LatestPackageIDsConfig{ @@ -77,7 +112,7 @@ func TestConfigureLaneLegAsDest_MCMSBatchOp_WithLatestPackageID(t *testing.T) { report, execErr = cldf_ops.ExecuteSequence( mcmstest.Bundle(t), lanes.ConfigureLaneLegAsDest, - chains, + testSuiChains(), input, ) return execErr @@ -86,7 +121,148 @@ func TestConfigureLaneLegAsDest_MCMSBatchOp_WithLatestPackageID(t *testing.T) { require.NoError(t, err) require.Len(t, report.Output.BatchOps, 1) tx := report.Output.BatchOps[0].Transactions[0] + require.Equal(t, testOffRampPackageID, tx.To) latestPackageIDFromTx, err := utils.TransactionLatestPackageID(tx) require.NoError(t, err) require.Equal(t, latestOffRampPackageID, latestPackageIDFromTx) } + +func TestConfigureLaneLegAsDest_ValidationErrors(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := testSuiChains() + + t.Run("requires ConnectChains scope", func(t *testing.T) { + _, err := cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + chains, + destLegInput(), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "WithConnectChainsEnvironment") + }) + + t.Run("requires source OnRamp", func(t *testing.T) { + input := destLegInput() + input.Source.OnRamp = nil + err := lanes.WithConnectChainsEnvironment(env, func() error { + _, execErr := cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + chains, + input, + ) + return execErr + }) + require.Error(t, err) + require.Contains(t, err.Error(), "source OnRamp address required") + }) +} + +func TestConfigureLaneLegAsSource_ValidationErrors(t *testing.T) { + chains := testSuiChains() + router := common.HexToAddress(evmRouterAddress).Bytes() + + t.Run("requires ConnectChains scope", func(t *testing.T) { + _, err := cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsSource, + chains, + sourceLegInput(router), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "WithConnectChainsEnvironment") + }) + + t.Run("requires dest Router", func(t *testing.T) { + _, err := cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsSource, + chains, + sourceLegInput(nil), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "dest Router address required") + }) +} + +func TestConfigureLaneLegAsSource_RouterAddressBytes(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := testSuiChains() + evmAddr := common.HexToAddress(evmRouterAddress) + + t.Run("20 byte EVM address", func(t *testing.T) { + _, err := runSourceLeg(t, env, chains, sourceLegInput(evmAddr.Bytes())) + require.NoError(t, err) + }) + + t.Run("32 byte left padded EVM address", func(t *testing.T) { + padded := make([]byte, 32) + copy(padded[12:], evmAddr.Bytes()) + _, err := runSourceLeg(t, env, chains, sourceLegInput(padded)) + require.NoError(t, err) + }) + + t.Run("invalid router length", func(t *testing.T) { + _, err := runSourceLeg(t, env, chains, sourceLegInput([]byte{0x01, 0x02, 0x03})) + require.Error(t, err) + require.Contains(t, err.Error(), "unexpected address length") + }) +} + +func TestConfigureLaneLegAsSource_MCMSBatchOp_FirstOpEncoderParity(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := testSuiChains() + input := sourceLegInput(common.HexToAddress(evmRouterAddress).Bytes()) + + report, err := runSourceLeg(t, env, chains, input) + require.NoError(t, err) + require.Len(t, report.Output.BatchOps, 5) + require.Equal(t, suiTestnetSelector, uint64(report.Output.BatchOps[0].ChainSelector)) + + feeQuoter, err := module_fee_quoter.NewFeeQuoter(testCCIPPackageID, nil) + require.NoError(t, err) + encodedCall, err := feeQuoter.Encoder().ApplyTokenTransferFeeConfigUpdates( + bind.Object{Id: testCCIPObjectRef}, + bind.Object{Id: testCCIPOwnerCapID}, + evmSepoliaSelector, + []string{testLinkCoinMetadata}, + []uint32{lanes.DefaultLinkTokenTransferMinFeeUsdCents}, + []uint32{lanes.DefaultLinkTokenTransferMaxFeeUsdCents}, + []uint16{lanes.DefaultLinkTokenTransferDeciBps}, + []uint32{lanes.DefaultLinkTokenTransferDestGasOverhead}, + []uint32{lanes.DefaultLinkTokenTransferDestBytesOverhead}, + []bool{true}, + []string{}, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches( + t, + report.Output.BatchOps[0].Transactions[0].Data, + encodedCall, + testCCIPObjectRef, + nil, + ) +} + +func runSourceLeg( + t *testing.T, + env cldf.Environment, + chains chain.BlockChains, + input laneapi.UpdateLanesInput, +) (cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput], error) { + t.Helper() + + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] + err := lanes.WithConnectChainsEnvironment(env, func() error { + var execErr error + report, execErr = cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsSource, + chains, + input, + ) + return execErr + }) + return report, err +} diff --git a/deployment/testdata/addresses.json b/deployment/testdata/addresses.json index 0acbc609..59d562f4 100644 --- a/deployment/testdata/addresses.json +++ b/deployment/testdata/addresses.json @@ -221,6 +221,10 @@ "Type": "SuiRouterStateObjectID", "Version": "1.0.0" }, + "0xb000000000000000000000000000000000000000000000000000000000000001": { + "Type": "SuiCCIPRouterOwnerCapObjectID", + "Version": "1.0.0" + }, "0xbeace36c3c1e1f37c5806c4954140d15cbc7b0002ef7ccb490de26e82f5ec4ca": { "Type": "SuiCCIPObjectRef", "Version": "1.0.0" From d0743325e1c325c72ae91c295c4ad5378ff2f8e8 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Thu, 25 Jun 2026 11:55:20 -0400 Subject: [PATCH 04/13] fixes --- deployment/adapters/mcmsreader.go | 2 +- deployment/lanes/connect_chains_source.go | 7 +++ deployment/lanes/connect_chains_test.go | 55 +++++++++++++++++++++++ deployment/lanes/state.go | 10 ++++- deployment/ops/ccip/op_fee_quoter.go | 33 ++++++++++++-- deployment/ops/ccip_onramp/op_deploy.go | 11 ++++- deployment/ops/ccip_router/op_deploy.go | 11 ++++- 7 files changed, 122 insertions(+), 7 deletions(-) diff --git a/deployment/adapters/mcmsreader.go b/deployment/adapters/mcmsreader.go index 0b84a56b..e812b9e7 100644 --- a/deployment/adapters/mcmsreader.go +++ b/deployment/adapters/mcmsreader.go @@ -19,7 +19,7 @@ type MCMSReader struct{} // mcmsFieldsFromInput loads the on-chain state and selects the correct // MCMSStateFields (normal or fastcurse) based on the Qualifier in the input. -// A Qualifier value of "fastcurse" selects the fastcurse MCMS instance. +// A Qualifier value of "RMNMCMS" (fastCurseQualifier) selects the fastcurse MCMS instance. func mcmsFieldsFromInput(e cldf.Environment, chainSelector uint64, input mcms_utils.Input) (suideploy.MCMSStateFields, error) { stateMap, err := suideploy.LoadOnchainStatesui(e) if err != nil { diff --git a/deployment/lanes/connect_chains_source.go b/deployment/lanes/connect_chains_source.go index b6d7580a..b1e0bb1f 100644 --- a/deployment/lanes/connect_chains_source.go +++ b/deployment/lanes/connect_chains_source.go @@ -50,10 +50,13 @@ var ConfigureLaneLegAsSource = cldf_ops.NewSequence( return sequences.OnChainOutput{}, fmt.Errorf("decode dest router for chain %d: %w", input.Dest.Selector, err) } + latestIDs := resolveLatestPackageIDs(input.Source.Selector) + var out sequences.OnChainOutput tokenFeeReport, err := cldf_ops.ExecuteOperation(b, ccip_ops.FeeQuoterApplyTokenTransferFeeConfigUpdatesOp, deps, ccip_ops.FeeQuoterApplyTokenTransferFeeConfigUpdatesInput{ CCIPPackageId: state.CCIPAddress, + LatestPackageId: latestIDs.CCIP, StateObjectId: state.CCIPObjectRef, OwnerCapObjectId: state.CCIPOwnerCapObjectId, DestChainSelector: input.Dest.Selector, @@ -74,6 +77,7 @@ var ConfigureLaneLegAsSource = cldf_ops.NewSequence( destCfgInput := TranslateDestChainConfig(input.Dest.FeeQuoterDestChainConfig, input.Dest.Selector) destCfgInput.CCIPPackageId = state.CCIPAddress + destCfgInput.LatestPackageId = latestIDs.CCIP destCfgInput.StateObjectId = state.CCIPObjectRef destCfgInput.OwnerCapObjectId = state.CCIPOwnerCapObjectId destCfgReport, err := cldf_ops.ExecuteOperation(b, ccip_ops.FeeQuoterApplyDestChainConfigUpdatesOp, deps, destCfgInput) @@ -86,6 +90,7 @@ var ConfigureLaneLegAsSource = cldf_ops.NewSequence( premiumReport, err := cldf_ops.ExecuteOperation(b, ccip_ops.FeeQuoterApplyPremiumMultiplierWeiPerEthUpdatesOp, deps, ccip_ops.FeeQuoterApplyPremiumMultiplierWeiPerEthUpdatesInput{ CCIPPackageId: state.CCIPAddress, + LatestPackageId: latestIDs.CCIP, StateObjectId: state.CCIPObjectRef, OwnerCapObjectId: state.CCIPOwnerCapObjectId, Tokens: []string{state.LinkTokenCoinMetadataId}, @@ -100,6 +105,7 @@ var ConfigureLaneLegAsSource = cldf_ops.NewSequence( onRampReport, err := cldf_ops.ExecuteOperation(b, ccip_onramp_ops.ApplyDestChainConfigUpdateOp, deps, ccip_onramp_ops.ApplyDestChainConfigureOnRampInput{ OnRampPackageId: state.OnRampAddress, + LatestPackageId: latestIDs.OnRamp, CCIPObjectRefId: state.CCIPObjectRef, OwnerCapObjectId: state.OnRampOwnerCapObjectId, StateObjectId: state.OnRampStateObjectId, @@ -116,6 +122,7 @@ var ConfigureLaneLegAsSource = cldf_ops.NewSequence( routerReport, err := cldf_ops.ExecuteOperation(b, ccip_router_ops.SetOnRampsOp, deps, ccip_router_ops.SetOnRampsInput{ RouterPackageId: state.CCIPRouterAddress, + LatestPackageId: latestIDs.Router, RouterStateObjectId: state.CCIPRouterStateObjectID, OwnerCapObjectId: state.CCIPRouterOwnerCapObjectId, DestChainSelectors: []uint64{input.Dest.Selector}, diff --git a/deployment/lanes/connect_chains_test.go b/deployment/lanes/connect_chains_test.go index e1e3eab6..72558d5f 100644 --- a/deployment/lanes/connect_chains_test.go +++ b/deployment/lanes/connect_chains_test.go @@ -245,6 +245,61 @@ func TestConfigureLaneLegAsSource_MCMSBatchOp_FirstOpEncoderParity(t *testing.T) ) } +func TestConfigureLaneLegAsSource_MCMSBatchOp_WithLatestPackageIDs(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := testSuiChains() + input := sourceLegInput(common.HexToAddress(evmRouterAddress).Bytes()) + + const ( + // Address-book (registry-identity) package IDs for the source Sui chain. + ccipPkg = "0xece742a763bddf1e36629fa06b605497e413241afd14f05e558e80eef4f64e95" + onRampPkg = "0xf87c6010be571a304f0d860857204bc66f037842156f0f6c9d80be265fd83752" + routerPkg = "0xed4613bd35004954c07150c3e9b10230f5e23e3058bc2ca0e3e676cb43eb4dc1" + + latestCCIP = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + latestOnRamp = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + latestRouter = "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ) + + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] + err := lanes.WithConnectChainsEnvironment(env, func() error { + return lanes.WithSuiLatestPackageIDs(map[uint64]lanes.LatestPackageIDsConfig{ + suiTestnetSelector: {CCIP: latestCCIP, OnRamp: latestOnRamp, Router: latestRouter}, + }, func() error { + var execErr error + report, execErr = cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsSource, + chains, + input, + ) + return execErr + }) + }) + require.NoError(t, err) + require.Len(t, report.Output.BatchOps, 5) + + // Each op keeps the registry-identity package ID as the MCMS target (tx.To) while + // routing PTB execution through the upgraded (latest) package ID. + expected := []struct { + registry string + latest string + }{ + {ccipPkg, latestCCIP}, // FeeQuoter apply_token_transfer_fee_config_updates + {ccipPkg, latestCCIP}, // FeeQuoter apply_dest_chain_config_updates + {ccipPkg, latestCCIP}, // FeeQuoter apply_premium_multiplier_wei_per_eth_updates + {onRampPkg, latestOnRamp}, // OnRamp apply_dest_chain_config_updates + {routerPkg, latestRouter}, // Router set_on_ramps + } + for i, exp := range expected { + tx := report.Output.BatchOps[i].Transactions[0] + require.Equal(t, exp.registry, tx.To, "batch op %d To should be registry package id", i) + latest, err := utils.TransactionLatestPackageID(tx) + require.NoError(t, err) + require.Equal(t, exp.latest, latest, "batch op %d should route through latest package id", i) + } +} + func runSourceLeg( t *testing.T, env cldf.Environment, diff --git a/deployment/lanes/state.go b/deployment/lanes/state.go index bb000b3a..872b313b 100644 --- a/deployment/lanes/state.go +++ b/deployment/lanes/state.go @@ -19,7 +19,15 @@ func opTxDepsForChain(chains cldf_chain.BlockChains, chainSelector uint64) (sui_ return sui_ops.OpTxDeps{ Client: chain.Client, - Signer: chain.Signer, + // Signer is intentionally nil. The cross-family lane connect flow always emits + // MCMS batch operations (CCIP contracts on Sui are timelock-owned) rather than + // executing transactions directly: a nil signer makes every op skip execution + // and return its encoded Call for proposal assembly (see appendMCMSBatchOpFromCall). + // Passing chain.Signer here would instead execute each update against the + // timelock-owned contracts (which fails) and produce no proposal. Direct, + // signer-based execution of these updates remains available via the dedicated + // ConnectSuiToEVM changeset. + Signer: nil, GetCallOpts: func() *bind.CallOpts { gasBudget := uint64(400_000_000) return &bind.CallOpts{WaitForExecution: true, GasBudget: &gasBudget} diff --git a/deployment/ops/ccip/op_fee_quoter.go b/deployment/ops/ccip/op_fee_quoter.go index 2207946f..f7bd8653 100644 --- a/deployment/ops/ccip/op_fee_quoter.go +++ b/deployment/ops/ccip/op_fee_quoter.go @@ -157,6 +157,7 @@ var FeeQuoterApplyFeeTokenUpdatesOp = cld_ops.NewOperation( // FEE QUOTER -- apply_token_transfer_fee_config_updates type FeeQuoterApplyTokenTransferFeeConfigUpdatesInput struct { CCIPPackageId string + LatestPackageId string // optional: upgraded package ID for PTB execution when CCIPPackageId is the MCMS registry identity StateObjectId string OwnerCapObjectId string DestChainSelector uint64 @@ -171,7 +172,11 @@ type FeeQuoterApplyTokenTransferFeeConfigUpdatesInput struct { } var applyTokenTransferFeeHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input FeeQuoterApplyTokenTransferFeeConfigUpdatesInput) (output sui_ops.OpTxResult[NoObjects], err error) { - contract, err := module_fee_quoter.NewFeeQuoter(input.CCIPPackageId, deps.Client) + binaryPkgId := input.CCIPPackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + contract, err := module_fee_quoter.NewFeeQuoter(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[NoObjects]{}, fmt.Errorf("failed to create fee quoter contract: %w", err) } @@ -196,6 +201,10 @@ var applyTokenTransferFeeHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, if err != nil { return sui_ops.OpTxResult[NoObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID + call.PackageID = input.CCIPPackageId + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of ApplyTokenTransferFeeConfigUpdates on FeeQuoter as per no Signer provided") return sui_ops.OpTxResult[NoObjects]{ @@ -247,6 +256,7 @@ var FeeQuoterApplyTokenTransferFeeConfigUpdatesOp = cld_ops.NewOperation( // FEE QUOTER -- apply_dest_chain_config_updates type FeeQuoterApplyDestChainConfigUpdatesInput struct { CCIPPackageId string + LatestPackageId string // optional: upgraded package ID for PTB execution when CCIPPackageId is the MCMS registry identity StateObjectId string OwnerCapObjectId string DestChainSelector uint64 @@ -272,7 +282,11 @@ type FeeQuoterApplyDestChainConfigUpdatesInput struct { } var applyDestChainConfigHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input FeeQuoterApplyDestChainConfigUpdatesInput) (output sui_ops.OpTxResult[NoObjects], err error) { - contract, err := module_fee_quoter.NewFeeQuoter(input.CCIPPackageId, deps.Client) + binaryPkgId := input.CCIPPackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + contract, err := module_fee_quoter.NewFeeQuoter(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[NoObjects]{}, fmt.Errorf("failed to create fee quoter contract: %w", err) } @@ -308,6 +322,10 @@ var applyDestChainConfigHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, if err != nil { return sui_ops.OpTxResult[NoObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID + call.PackageID = input.CCIPPackageId + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of ApplyDestChainConfigUpdates on FeeQuoter as per no Signer provided") return sui_ops.OpTxResult[NoObjects]{ @@ -370,6 +388,7 @@ var FeeQuoterApplyDestChainConfigUpdatesOp = cld_ops.NewOperation( // FEE QUOTER -- apply_premium_multiplier_wei_per_eth_updates type FeeQuoterApplyPremiumMultiplierWeiPerEthUpdatesInput struct { CCIPPackageId string + LatestPackageId string // optional: upgraded package ID for PTB execution when CCIPPackageId is the MCMS registry identity StateObjectId string OwnerCapObjectId string Tokens []string @@ -377,7 +396,11 @@ type FeeQuoterApplyPremiumMultiplierWeiPerEthUpdatesInput struct { } var applyPremiumMultiplierHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input FeeQuoterApplyPremiumMultiplierWeiPerEthUpdatesInput) (output sui_ops.OpTxResult[NoObjects], err error) { - contract, err := module_fee_quoter.NewFeeQuoter(input.CCIPPackageId, deps.Client) + binaryPkgId := input.CCIPPackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + contract, err := module_fee_quoter.NewFeeQuoter(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[NoObjects]{}, fmt.Errorf("failed to create fee quoter contract: %w", err) } @@ -395,6 +418,10 @@ var applyPremiumMultiplierHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps if err != nil { return sui_ops.OpTxResult[NoObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID + call.PackageID = input.CCIPPackageId + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of ApplyPremiumMultiplierWeiPerEthUpdates on FeeQuoter as per no Signer provided") return sui_ops.OpTxResult[NoObjects]{ diff --git a/deployment/ops/ccip_onramp/op_deploy.go b/deployment/ops/ccip_onramp/op_deploy.go index 1f3ecb25..b6527eda 100644 --- a/deployment/ops/ccip_onramp/op_deploy.go +++ b/deployment/ops/ccip_onramp/op_deploy.go @@ -115,6 +115,7 @@ var InitializeHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input OnRa type ApplyDestChainConfigureOnRampInput struct { OnRampPackageId string + LatestPackageId string // optional: upgraded package ID for PTB execution when OnRampPackageId is the MCMS registry identity CCIPObjectRefId string OwnerCapObjectId string StateObjectId string @@ -124,7 +125,11 @@ type ApplyDestChainConfigureOnRampInput struct { } var ApplyDestChainUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input ApplyDestChainConfigureOnRampInput) (output sui_ops.OpTxResult[DeployCCIPOnRampObjects], err error) { - onRampPackage, err := module_onramp.NewOnramp(input.OnRampPackageId, deps.Client) + binaryPkgId := input.OnRampPackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + onRampPackage, err := module_onramp.NewOnramp(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, err } @@ -144,6 +149,10 @@ var ApplyDestChainUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, if err != nil { return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID + call.PackageID = input.OnRampPackageId + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of ApplyDestChainConfigUpdates on OnRamp as per no Signer provided") return sui_ops.OpTxResult[DeployCCIPOnRampObjects]{ diff --git a/deployment/ops/ccip_router/op_deploy.go b/deployment/ops/ccip_router/op_deploy.go index 858b793e..cd667cdb 100644 --- a/deployment/ops/ccip_router/op_deploy.go +++ b/deployment/ops/ccip_router/op_deploy.go @@ -173,6 +173,7 @@ var deployHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input DeployCC type SetOnRampsInput struct { RouterPackageId string + LatestPackageId string // optional: upgraded package ID for PTB execution when RouterPackageId is the MCMS registry identity RouterStateObjectId string OwnerCapObjectId string DestChainSelectors []uint64 @@ -191,7 +192,11 @@ var SetOnRampsOp = cld_ops.NewOperation( ) var setOnRampsHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input SetOnRampsInput) (output sui_ops.OpTxResult[SetOnRampsObjects], err error) { - routerPackage, err := module_router.NewRouter(input.RouterPackageId, deps.Client) + binaryPkgId := input.RouterPackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + routerPackage, err := module_router.NewRouter(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[SetOnRampsObjects]{}, err } @@ -209,6 +214,10 @@ var setOnRampsHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input SetO if err != nil { return sui_ops.OpTxResult[SetOnRampsObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID + call.PackageID = input.RouterPackageId + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of SetOnRamps on Router as per no Signer provided", "destChainSelectors", input.DestChainSelectors, From 0e1a17187488fd9b303f53ae4d845b69c6675df5 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Thu, 25 Jun 2026 12:00:41 -0400 Subject: [PATCH 05/13] upgrade chainlink-ccip --- deployment/go.mod | 4 ++-- deployment/go.sum | 8 ++++---- go.mod | 3 +-- go.sum | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/deployment/go.mod b/deployment/go.mod index 1b0ce170..b9b67af8 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -14,7 +14,7 @@ require ( github.com/ethereum/go-ethereum v1.17.3 github.com/google/go-cmp v0.7.0 github.com/smartcontractkit/chain-selectors v1.0.102 - github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260311190822-5cbfc939dd16 + github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 github.com/smartcontractkit/chainlink-deployments-framework v0.109.1-0.20260604174622-e26b8cddfa0a github.com/smartcontractkit/chainlink-sui v0.0.0 @@ -131,7 +131,7 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc // indirect - github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 // indirect + github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index fcbacae2..afd66ea0 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -599,14 +599,14 @@ github.com/smartcontractkit/chain-selectors v1.0.102 h1:qYP4+72HfvogCHR5ymwRFee3 github.com/smartcontractkit/chain-selectors v1.0.102/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc h1:Um9FBcf0JNSFuGbxgccDG1vM3cNrMGy0SdJ7r6VbX0o= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 h1:p0nFrTYrOQzDhWYm6suaM5CoWiXV5NV7llHnp6/Kn/8= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1/go.mod h1:1XxxpkgCmG/z6y30yRuVrcxre6zixIVX3xzi706Db/8= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a h1:bGNA59hevTF2rt1tkT71tEUPCDlA7HWpzc4tdoY9mDI= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 h1:jkChf04hhdiMBApbb+lLDxHMY62Md6UeM7v++GSw3K8= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139/go.mod h1:wuhagkM/lU0GbV2YcrROOH0GlsfXJYwm6qmpa4CK70w= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 h1:tw3K4UkH5XfW5SoyYkvAlbzrccoGSLdz/XkxD6nyGC8= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139/go.mod h1:1WcontO9PeuKdUf5HXfs3nuICtzUvFNnyCmrHkTCF9Y= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260311190822-5cbfc939dd16 h1:kG7DnjoCDJUt2htCqVxTA4IvQyR+a6mOmqlG1v7KMRE= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260311190822-5cbfc939dd16/go.mod h1:kMRGxNzyB5O6sqQlJEgBG/g49mzRvlcqbqMrzlhL+JY= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a h1:PfJMTY1jBeSMZw5bd0kPaIEJel4JQnq2k3CLstkLTj8= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 h1:5z3LQ27MJmhiaeqp9S2TzbF5Wm4GGvUKAYOtE9AauR8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= diff --git a/go.mod b/go.mod index 7a154b86..03a31621 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc - github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 + github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 github.com/stretchr/testify v1.11.1 github.com/test-go/testify v1.1.4 @@ -119,7 +119,6 @@ require ( github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/libocr v0.0.0-20260304194147-a03701e2c02e // indirect - github.com/stretchr/objx v0.5.3 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect diff --git a/go.sum b/go.sum index 9b2e474c..b46c29e1 100644 --- a/go.sum +++ b/go.sum @@ -296,8 +296,8 @@ github.com/smartcontractkit/chain-selectors v1.0.102 h1:qYP4+72HfvogCHR5ymwRFee3 github.com/smartcontractkit/chain-selectors v1.0.102/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc h1:Um9FBcf0JNSFuGbxgccDG1vM3cNrMGy0SdJ7r6VbX0o= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 h1:p0nFrTYrOQzDhWYm6suaM5CoWiXV5NV7llHnp6/Kn/8= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1/go.mod h1:1XxxpkgCmG/z6y30yRuVrcxre6zixIVX3xzi706Db/8= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a h1:bGNA59hevTF2rt1tkT71tEUPCDlA7HWpzc4tdoY9mDI= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 h1:5z3LQ27MJmhiaeqp9S2TzbF5Wm4GGvUKAYOtE9AauR8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= From 8f8e30bbf9eb232d9b05cfec23d250825ee9fa26 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Thu, 25 Jun 2026 12:07:35 -0400 Subject: [PATCH 06/13] fix --- scripts/go.mod | 2 +- scripts/go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/go.mod b/scripts/go.mod index ea6d98ff..9d73f981 100644 --- a/scripts/go.mod +++ b/scripts/go.mod @@ -127,7 +127,7 @@ require ( github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 // indirect - github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260608180601-efa81bfdfda9 // indirect + github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a // indirect github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 // indirect diff --git a/scripts/go.sum b/scripts/go.sum index 76c378f2..b97e1551 100644 --- a/scripts/go.sum +++ b/scripts/go.sum @@ -601,14 +601,14 @@ github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc h github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig= github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320 h1:ix4tCtSTB7S2XGll+uqnhrqAQ+2iW/Zk/vnPjBMYRB0= github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320/go.mod h1:WKmNUX4oy8IvB66ukudrE99uaXjlZ7WghCDwHOTyB1c= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 h1:p0nFrTYrOQzDhWYm6suaM5CoWiXV5NV7llHnp6/Kn/8= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1/go.mod h1:1XxxpkgCmG/z6y30yRuVrcxre6zixIVX3xzi706Db/8= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a h1:bGNA59hevTF2rt1tkT71tEUPCDlA7HWpzc4tdoY9mDI= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 h1:jkChf04hhdiMBApbb+lLDxHMY62Md6UeM7v++GSw3K8= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139/go.mod h1:wuhagkM/lU0GbV2YcrROOH0GlsfXJYwm6qmpa4CK70w= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 h1:tw3K4UkH5XfW5SoyYkvAlbzrccoGSLdz/XkxD6nyGC8= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139/go.mod h1:1WcontO9PeuKdUf5HXfs3nuICtzUvFNnyCmrHkTCF9Y= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260608180601-efa81bfdfda9 h1:MH8jQt6Dpt8LoBwrEkDvT/aIrkbZ/7HGcHVzsa5ufho= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260608180601-efa81bfdfda9/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a h1:PfJMTY1jBeSMZw5bd0kPaIEJel4JQnq2k3CLstkLTj8= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 h1:5z3LQ27MJmhiaeqp9S2TzbF5Wm4GGvUKAYOtE9AauR8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= From 2dec49b3066163629bd412102d8e9ef951ff2e33 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Thu, 25 Jun 2026 12:08:02 -0400 Subject: [PATCH 07/13] fix --- integration-tests/go.mod | 4 ++-- integration-tests/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 18b0f9a0..ee5bc997 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -10,7 +10,7 @@ require ( github.com/block-vision/sui-go-sdk v1.2.1 github.com/ethereum/go-ethereum v1.17.3 github.com/smartcontractkit/chain-selectors v1.0.102 - github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260608180601-efa81bfdfda9 + github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 github.com/smartcontractkit/chainlink-sui v0.0.0 @@ -133,7 +133,7 @@ require ( github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc // indirect github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320 // indirect - github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 // indirect + github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 2cb7b35d..c6c33d51 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -601,14 +601,14 @@ github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc h github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig= github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320 h1:ix4tCtSTB7S2XGll+uqnhrqAQ+2iW/Zk/vnPjBMYRB0= github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320/go.mod h1:WKmNUX4oy8IvB66ukudrE99uaXjlZ7WghCDwHOTyB1c= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1 h1:p0nFrTYrOQzDhWYm6suaM5CoWiXV5NV7llHnp6/Kn/8= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260428205619-2db1389501a1/go.mod h1:1XxxpkgCmG/z6y30yRuVrcxre6zixIVX3xzi706Db/8= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a h1:bGNA59hevTF2rt1tkT71tEUPCDlA7HWpzc4tdoY9mDI= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 h1:jkChf04hhdiMBApbb+lLDxHMY62Md6UeM7v++GSw3K8= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139/go.mod h1:wuhagkM/lU0GbV2YcrROOH0GlsfXJYwm6qmpa4CK70w= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 h1:tw3K4UkH5XfW5SoyYkvAlbzrccoGSLdz/XkxD6nyGC8= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139/go.mod h1:1WcontO9PeuKdUf5HXfs3nuICtzUvFNnyCmrHkTCF9Y= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260608180601-efa81bfdfda9 h1:MH8jQt6Dpt8LoBwrEkDvT/aIrkbZ/7HGcHVzsa5ufho= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260608180601-efa81bfdfda9/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a h1:PfJMTY1jBeSMZw5bd0kPaIEJel4JQnq2k3CLstkLTj8= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 h1:5z3LQ27MJmhiaeqp9S2TzbF5Wm4GGvUKAYOtE9AauR8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= From a431d32521283e1729d12585b50ed767007aa069 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Thu, 25 Jun 2026 22:55:18 -0400 Subject: [PATCH 08/13] update --- go.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/go.md b/go.md index 18b2e3fb..4dfab678 100644 --- a/go.md +++ b/go.md @@ -87,6 +87,9 @@ flowchart LR chainlink-canton --> chainlink-ccv/build/devenv chainlink-canton --> chainlink-ccv/deployment chainlink-canton --> chainlink-common/keystore + chainlink-canton --> chainlink-evm + chainlink-canton --> chainlink-evm/gethwrappers + chainlink-canton --> chainlink-framework/chains chainlink-canton --> chainlink-framework/multinode chainlink-canton --> chainlink-protos/chainlink-ccv/committee-verifier chainlink-canton --> chainlink-protos/chainlink-ccv/heartbeat @@ -111,9 +114,7 @@ flowchart LR click chainlink-ccip/chains/solana href "https://github.com/smartcontractkit/chainlink-ccip" chainlink-ccip/chains/solana/gobindings click chainlink-ccip/chains/solana/gobindings href "https://github.com/smartcontractkit/chainlink-ccip" - chainlink-ccip/deployment --> chainlink-evm - chainlink-ccip/deployment --> chainlink-evm/gethwrappers - chainlink-ccip/deployment --> chainlink-framework/chains + chainlink-ccip/deployment click chainlink-ccip/deployment href "https://github.com/smartcontractkit/chainlink-ccip" chainlink-ccv click chainlink-ccv href "https://github.com/smartcontractkit/chainlink-ccv" From 8792c562fcfb009417c335e9b2c3d65f86dd2b31 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 26 Jun 2026 11:31:29 -0400 Subject: [PATCH 09/13] address feedback --- deployment/lanes/addresses.go | 16 ++++++ ...nnect_chains.go => connect_chains_dest.go} | 0 deployment/lanes/connect_chains_source.go | 19 +------ deployment/lanes/connect_chains_test.go | 13 ++++- deployment/lanes/remote_address_test.go | 51 +++++++++++++++++++ 5 files changed, 80 insertions(+), 19 deletions(-) rename deployment/lanes/{connect_chains.go => connect_chains_dest.go} (100%) create mode 100644 deployment/lanes/remote_address_test.go diff --git a/deployment/lanes/addresses.go b/deployment/lanes/addresses.go index a7da96c2..388c76fe 100644 --- a/deployment/lanes/addresses.go +++ b/deployment/lanes/addresses.go @@ -1,6 +1,7 @@ package lanes import ( + "encoding/hex" "fmt" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" @@ -18,6 +19,21 @@ func packageIDToBytes(address string) ([]byte, error) { return out, nil } +// remoteAddressBytesToHex encodes opaque remote address bytes from ConnectChains as a +// Sui address string. Bytes are left-padded to 32 bytes when shorter, matching EVM +// OffRamp source OnRamp encoding and deployment.StrTo32. +func remoteAddressBytesToHex(b []byte) (string, error) { + if len(b) == 0 { + return "", fmt.Errorf("empty address bytes") + } + if len(b) > 32 { + return "", fmt.Errorf("address longer than 32 bytes: %d", len(b)) + } + padded := make([]byte, 32) + copy(padded[32-len(b):], b) + return "0x" + hex.EncodeToString(padded), nil +} + func loadChainState(env cldf.Environment, chainSelector uint64) (suideploy.CCIPChainState, error) { stateMap, err := suideploy.LoadOnchainStatesui(env) if err != nil { diff --git a/deployment/lanes/connect_chains.go b/deployment/lanes/connect_chains_dest.go similarity index 100% rename from deployment/lanes/connect_chains.go rename to deployment/lanes/connect_chains_dest.go diff --git a/deployment/lanes/connect_chains_source.go b/deployment/lanes/connect_chains_source.go index b1e0bb1f..21e85ee1 100644 --- a/deployment/lanes/connect_chains_source.go +++ b/deployment/lanes/connect_chains_source.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/Masterminds/semver/v3" - "github.com/ethereum/go-ethereum/common" laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" @@ -45,9 +44,9 @@ var ConfigureLaneLegAsSource = cldf_ops.NewSequence( return sequences.OnChainOutput{}, err } - destRouter, err := evmAddressHexFromBytes(input.Dest.Router) + destRouter, err := remoteAddressBytesToHex(input.Dest.Router) if err != nil { - return sequences.OnChainOutput{}, fmt.Errorf("decode dest router for chain %d: %w", input.Dest.Selector, err) + return sequences.OnChainOutput{}, fmt.Errorf("encode dest router for chain %d: %w", input.Dest.Selector, err) } latestIDs := resolveLatestPackageIDs(input.Source.Selector) @@ -142,17 +141,3 @@ var ConfigureLaneLegAsSource = cldf_ops.NewSequence( func (a *SuiAdapter) ConfigureLaneLegAsSource() *cldf_ops.Sequence[laneapi.UpdateLanesInput, sequences.OnChainOutput, cldf_chain.BlockChains] { return ConfigureLaneLegAsSource } - -func evmAddressHexFromBytes(b []byte) (string, error) { - if len(b) == 0 { - return "", fmt.Errorf("empty address bytes") - } - switch len(b) { - case 20: - return common.BytesToAddress(b).Hex(), nil - case 32: - return common.BytesToAddress(b[12:]).Hex(), nil - default: - return "", fmt.Errorf("unexpected address length %d", len(b)) - } -} diff --git a/deployment/lanes/connect_chains_test.go b/deployment/lanes/connect_chains_test.go index 72558d5f..a5d081fd 100644 --- a/deployment/lanes/connect_chains_test.go +++ b/deployment/lanes/connect_chains_test.go @@ -203,10 +203,19 @@ func TestConfigureLaneLegAsSource_RouterAddressBytes(t *testing.T) { require.NoError(t, err) }) + t.Run("32 byte native address", func(t *testing.T) { + native := make([]byte, 32) + for i := range native { + native[i] = byte(i + 1) + } + _, err := runSourceLeg(t, env, chains, sourceLegInput(native)) + require.NoError(t, err) + }) + t.Run("invalid router length", func(t *testing.T) { - _, err := runSourceLeg(t, env, chains, sourceLegInput([]byte{0x01, 0x02, 0x03})) + _, err := runSourceLeg(t, env, chains, sourceLegInput(make([]byte, 33))) require.Error(t, err) - require.Contains(t, err.Error(), "unexpected address length") + require.Contains(t, err.Error(), "address longer than 32 bytes") }) } diff --git a/deployment/lanes/remote_address_test.go b/deployment/lanes/remote_address_test.go new file mode 100644 index 00000000..e5de9641 --- /dev/null +++ b/deployment/lanes/remote_address_test.go @@ -0,0 +1,51 @@ +package lanes + +import ( + "encoding/hex" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestRemoteAddressBytesToHex(t *testing.T) { + t.Parallel() + + evmAddr := common.HexToAddress("0xd3E190f381f06DC0d289590fd452C42Fa2DAC586") + + t.Run("20 byte EVM address is left padded", func(t *testing.T) { + got, err := remoteAddressBytesToHex(evmAddr.Bytes()) + require.NoError(t, err) + require.Equal(t, "0x000000000000000000000000d3e190f381f06dc0d289590fd452c42fadac586", got) + }) + + t.Run("32 byte address is preserved", func(t *testing.T) { + raw := make([]byte, 32) + for i := range raw { + raw[i] = byte(i + 1) + } + got, err := remoteAddressBytesToHex(raw) + require.NoError(t, err) + require.Equal(t, "0x"+hex.EncodeToString(raw), got) + }) + + t.Run("32 byte left padded EVM address is preserved", func(t *testing.T) { + padded := make([]byte, 32) + copy(padded[12:], evmAddr.Bytes()) + got, err := remoteAddressBytesToHex(padded) + require.NoError(t, err) + require.Equal(t, "0x"+hex.EncodeToString(padded), got) + }) + + t.Run("empty bytes rejected", func(t *testing.T) { + _, err := remoteAddressBytesToHex(nil) + require.Error(t, err) + require.Contains(t, err.Error(), "empty address bytes") + }) + + t.Run("longer than 32 bytes rejected", func(t *testing.T) { + _, err := remoteAddressBytesToHex(make([]byte, 33)) + require.Error(t, err) + require.Contains(t, err.Error(), "address longer than 32 bytes") + }) +} From 6d82ccae2228db8b4ef62376b2daf789c2850b39 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 26 Jun 2026 16:21:15 -0400 Subject: [PATCH 10/13] tests and fix --- deployment/go.mod | 8 +- deployment/go.sum | 16 +- deployment/lanes/adapter.go | 39 +- deployment/lanes/adapter_test.go | 49 +- deployment/lanes/addresses_test.go | 6 - deployment/lanes/batchop_test.go | 60 +++ .../lanes/connect_chains_layer2_test.go | 440 ++++++++++++++++++ deployment/lanes/connect_chains_test.go | 39 +- deployment/lanes/remote_address_test.go | 2 +- deployment/lanes/translate.go | 24 +- go.mod | 2 +- go.sum | 4 +- scripts/go.mod | 6 +- scripts/go.sum | 16 +- 14 files changed, 588 insertions(+), 123 deletions(-) create mode 100644 deployment/lanes/batchop_test.go create mode 100644 deployment/lanes/connect_chains_layer2_test.go diff --git a/deployment/go.mod b/deployment/go.mod index b9b67af8..a8c1840c 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -14,7 +14,7 @@ require ( github.com/ethereum/go-ethereum v1.17.3 github.com/google/go-cmp v0.7.0 github.com/smartcontractkit/chain-selectors v1.0.102 - github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a + github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260624154507-ea7ff77a0ddb github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 github.com/smartcontractkit/chainlink-deployments-framework v0.109.1-0.20260604174622-e26b8cddfa0a github.com/smartcontractkit/chainlink-sui v0.0.0 @@ -131,9 +131,9 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc // indirect - github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 // indirect + github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260624154507-ea7ff77a0ddb // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260505131349-78e491b80735 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index afd66ea0..fe3bae7b 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -599,14 +599,14 @@ github.com/smartcontractkit/chain-selectors v1.0.102 h1:qYP4+72HfvogCHR5ymwRFee3 github.com/smartcontractkit/chain-selectors v1.0.102/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc h1:Um9FBcf0JNSFuGbxgccDG1vM3cNrMGy0SdJ7r6VbX0o= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a h1:bGNA59hevTF2rt1tkT71tEUPCDlA7HWpzc4tdoY9mDI= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 h1:jkChf04hhdiMBApbb+lLDxHMY62Md6UeM7v++GSw3K8= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139/go.mod h1:wuhagkM/lU0GbV2YcrROOH0GlsfXJYwm6qmpa4CK70w= -github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 h1:tw3K4UkH5XfW5SoyYkvAlbzrccoGSLdz/XkxD6nyGC8= -github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139/go.mod h1:1WcontO9PeuKdUf5HXfs3nuICtzUvFNnyCmrHkTCF9Y= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a h1:PfJMTY1jBeSMZw5bd0kPaIEJel4JQnq2k3CLstkLTj8= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260624154507-ea7ff77a0ddb h1:uSXBvj/idCBg7hMLmek8YYNa0JKggqlTkHYdtIooeNM= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260624154507-ea7ff77a0ddb/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb h1:TZUVwF0ZexyJKqBjutCGjR/kG3LOa1krTQt/EXFgMYg= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb/go.mod h1:xu0Jum/nGRkjBwT/Vq7WCElWOTBBkFRwG0ZIaw9tF2I= +github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb h1:2p+8KYL0bhHblGcOJKRH84i9QduKGcY72NYcLJzNdNc= +github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260624154507-ea7ff77a0ddb h1:lG7cBn+mRgkdPpp1MSGK8pLq72g8LHa3aVPHZv/UReQ= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260624154507-ea7ff77a0ddb/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 h1:5z3LQ27MJmhiaeqp9S2TzbF5Wm4GGvUKAYOtE9AauR8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= diff --git a/deployment/lanes/adapter.go b/deployment/lanes/adapter.go index 50a31d99..18a641b2 100644 --- a/deployment/lanes/adapter.go +++ b/deployment/lanes/adapter.go @@ -54,25 +54,26 @@ func (a *SuiAdapter) GetRouterAddress(_ datastore.DataStore, chainSelector uint6 // and Sui execution limits (MaxDataBytes 16_000). func (a *SuiAdapter) GetFeeQuoterDestChainConfig() laneapi.FeeQuoterDestChainConfig { return laneapi.FeeQuoterDestChainConfig{ - IsEnabled: true, - MaxNumberOfTokensPerMsg: 10, - MaxDataBytes: 16_000, - MaxPerMsgGasLimit: 3_000_000, - DestGasOverhead: 300_000, - DestGasPerPayloadByteBase: 16, - DestGasPerPayloadByteHigh: 40, - DestGasPerPayloadByteThreshold: 3_000, - DestDataAvailabilityOverheadGas: 100, - DestGasPerDataAvailabilityByte: 16, - DestDataAvailabilityMultiplierBps: 1, - ChainFamilySelector: binary.BigEndian.Uint32(suiFamilySelector[:]), - EnforceOutOfOrder: true, - DefaultTokenFeeUSDCents: 25, - DefaultTokenDestGasOverhead: 90_000, - DefaultTxGasLimit: 200_000, - GasMultiplierWeiPerEth: 11e17, - GasPriceStalenessThreshold: 0, - NetworkFeeUSDCents: 10, + IsEnabled: true, + MaxDataBytes: 16_000, + MaxPerMsgGasLimit: 3_000_000, + DestGasOverhead: 300_000, + DestGasPerPayloadByteBase: 16, + ChainFamilySelector: binary.BigEndian.Uint32(suiFamilySelector[:]), + DefaultTokenFeeUSDCents: 25, + DefaultTokenDestGasOverhead: 90_000, + DefaultTxGasLimit: 200_000, + NetworkFeeUSDCents: 10, + V1Params: &laneapi.FeeQuoterV1Params{ + MaxNumberOfTokensPerMsg: 10, + DestGasPerPayloadByteHigh: 40, + DestGasPerPayloadByteThreshold: 3_000, + DestDataAvailabilityOverheadGas: 100, + DestGasPerDataAvailabilityByte: 16, + DestDataAvailabilityMultiplierBps: 1, + EnforceOutOfOrder: true, + GasMultiplierWeiPerEth: 11e17, + }, } } diff --git a/deployment/lanes/adapter_test.go b/deployment/lanes/adapter_test.go index 20c6a206..a65f717b 100644 --- a/deployment/lanes/adapter_test.go +++ b/deployment/lanes/adapter_test.go @@ -18,7 +18,6 @@ func TestSuiAdapter_GetFeeQuoterDestChainConfig(t *testing.T) { cfg := (&lanes.SuiAdapter{}).GetFeeQuoterDestChainConfig() require.True(t, cfg.IsEnabled) - require.Equal(t, uint16(10), cfg.MaxNumberOfTokensPerMsg) require.Equal(t, uint32(16_000), cfg.MaxDataBytes) require.Equal(t, uint32(3_000_000), cfg.MaxPerMsgGasLimit) require.Equal(t, uint32(300_000), cfg.DestGasOverhead) @@ -27,9 +26,11 @@ func TestSuiAdapter_GetFeeQuoterDestChainConfig(t *testing.T) { require.Equal(t, uint16(25), cfg.DefaultTokenFeeUSDCents) require.Equal(t, uint32(90_000), cfg.DefaultTokenDestGasOverhead) require.Equal(t, uint32(200_000), cfg.DefaultTxGasLimit) - require.Equal(t, uint32(10), cfg.NetworkFeeUSDCents) - require.True(t, cfg.EnforceOutOfOrder) - require.Equal(t, uint64(11e17), cfg.GasMultiplierWeiPerEth) + require.Equal(t, uint16(10), cfg.NetworkFeeUSDCents) + require.NotNil(t, cfg.V1Params) + require.Equal(t, uint16(10), cfg.V1Params.MaxNumberOfTokensPerMsg) + require.True(t, cfg.V1Params.EnforceOutOfOrder) + require.Equal(t, uint64(11e17), cfg.V1Params.GasMultiplierWeiPerEth) } func TestSuiAdapter_GetDefaultGasPrice(t *testing.T) { @@ -51,25 +52,27 @@ func TestTranslateDestChainConfig(t *testing.T) { const destSel = uint64(945045181441419236) cfg := laneapi.FeeQuoterDestChainConfig{ - IsEnabled: true, - MaxNumberOfTokensPerMsg: 1, - MaxDataBytes: 30_000, - MaxPerMsgGasLimit: 3_000_000, - DestGasOverhead: 300_000, - DestGasPerPayloadByteBase: 16, - DestGasPerPayloadByteHigh: 40, - DestGasPerPayloadByteThreshold: 3_000, - DestDataAvailabilityOverheadGas: 100, - DestGasPerDataAvailabilityByte: 16, - DestDataAvailabilityMultiplierBps: 1, - ChainFamilySelector: 0x2812d52c, - EnforceOutOfOrder: true, - DefaultTokenFeeUSDCents: 25, - DefaultTokenDestGasOverhead: 90_000, - DefaultTxGasLimit: 200_000, - GasMultiplierWeiPerEth: 1e18, - GasPriceStalenessThreshold: 1_000_000, - NetworkFeeUSDCents: 10, + IsEnabled: true, + MaxDataBytes: 30_000, + MaxPerMsgGasLimit: 3_000_000, + DestGasOverhead: 300_000, + DestGasPerPayloadByteBase: 16, + ChainFamilySelector: 0x2812d52c, + DefaultTokenFeeUSDCents: 25, + DefaultTokenDestGasOverhead: 90_000, + DefaultTxGasLimit: 200_000, + NetworkFeeUSDCents: 10, + V1Params: &laneapi.FeeQuoterV1Params{ + MaxNumberOfTokensPerMsg: 1, + DestGasPerPayloadByteHigh: 40, + DestGasPerPayloadByteThreshold: 3_000, + DestDataAvailabilityOverheadGas: 100, + DestGasPerDataAvailabilityByte: 16, + DestDataAvailabilityMultiplierBps: 1, + EnforceOutOfOrder: true, + GasMultiplierWeiPerEth: 1e18, + GasPriceStalenessThreshold: 1_000_000, + }, } got := lanes.TranslateDestChainConfig(cfg, destSel) diff --git a/deployment/lanes/addresses_test.go b/deployment/lanes/addresses_test.go index f81e28ea..51c7f874 100644 --- a/deployment/lanes/addresses_test.go +++ b/deployment/lanes/addresses_test.go @@ -42,8 +42,6 @@ func loadTestAddressBook(t *testing.T) cldf.AddressBook { } func TestSuiAdapter_AddressGettersFromAddressBook(t *testing.T) { - t.Parallel() - env := testEnvWithAddressBook(t) adapter := &lanes.SuiAdapter{} @@ -75,8 +73,6 @@ func TestSuiAdapter_AddressGettersFromAddressBook(t *testing.T) { } func TestSuiAdapter_AddressGettersFromAddressBook_MissingRef(t *testing.T) { - t.Parallel() - adapter := &lanes.SuiAdapter{} env := cldf.Environment{ ExistingAddresses: cldf.NewMemoryAddressBook(), @@ -95,8 +91,6 @@ func TestSuiAdapter_AddressGettersFromAddressBook_MissingRef(t *testing.T) { } func TestSuiAdapter_AddressGetters_RequireConnectChainsScope(t *testing.T) { - t.Parallel() - adapter := &lanes.SuiAdapter{} _, err := adapter.GetOnRampAddress(nil, suiTestnetSelector) require.Error(t, err) diff --git a/deployment/lanes/batchop_test.go b/deployment/lanes/batchop_test.go new file mode 100644 index 00000000..fdc25361 --- /dev/null +++ b/deployment/lanes/batchop_test.go @@ -0,0 +1,60 @@ +package lanes + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" + mcmstypes "github.com/smartcontractkit/mcms/types" + + sui_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops" + "github.com/smartcontractkit/chainlink-sui/relayer/signer" +) + +type stubSuiSigner struct{} + +func (stubSuiSigner) Sign(_ []byte) ([]string, error) { return nil, nil } + +func (stubSuiSigner) GetAddress() (string, error) { return "0x1", nil } + +var _ signer.SuiSigner = stubSuiSigner{} + +func TestAppendMCMSBatchOpFromCall(t *testing.T) { + t.Parallel() + + const chainSelector uint64 = 9762610643973837292 + + call := sui_ops.TransactionCall{ + PackageID: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + Module: "offramp", + Function: "apply_source_chain_config_updates", + Data: []byte{0x01}, + StateObjID: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + } + + t.Run("appends batch op when signer is nil", func(t *testing.T) { + var out sequences.OnChainOutput + err := appendMCMSBatchOpFromCall(&out, chainSelector, call, sui_ops.OpTxDeps{}) + require.NoError(t, err) + require.Len(t, out.BatchOps, 1) + require.Equal(t, mcmstypes.ChainSelector(chainSelector), out.BatchOps[0].ChainSelector) + require.Len(t, out.BatchOps[0].Transactions, 1) + }) + + t.Run("skips batch op when signer is set", func(t *testing.T) { + var out sequences.OnChainOutput + err := appendMCMSBatchOpFromCall(&out, chainSelector, call, sui_ops.OpTxDeps{ + Signer: stubSuiSigner{}, + }) + require.NoError(t, err) + require.Empty(t, out.BatchOps) + }) + + t.Run("skips empty package id", func(t *testing.T) { + var out sequences.OnChainOutput + err := appendMCMSBatchOpFromCall(&out, chainSelector, sui_ops.TransactionCall{}, sui_ops.OpTxDeps{}) + require.NoError(t, err) + require.Empty(t, out.BatchOps) + }) +} diff --git a/deployment/lanes/connect_chains_layer2_test.go b/deployment/lanes/connect_chains_layer2_test.go new file mode 100644 index 00000000..3b66caa0 --- /dev/null +++ b/deployment/lanes/connect_chains_layer2_test.go @@ -0,0 +1,440 @@ +package lanes_test + +import ( + "encoding/hex" + "encoding/json" + "os" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/ethereum/go-ethereum/common" + chain_selectors "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/require" + + laneapi "github.com/smartcontractkit/chainlink-ccip/deployment/lanes" + "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" + "github.com/smartcontractkit/chainlink-deployments-framework/chain" + "github.com/smartcontractkit/chainlink-deployments-framework/chain/sui" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + + "github.com/smartcontractkit/chainlink-sui/bindings/bind" + module_fee_quoter "github.com/smartcontractkit/chainlink-sui/bindings/generated/ccip/ccip/fee_quoter" + module_offramp "github.com/smartcontractkit/chainlink-sui/bindings/generated/ccip/ccip_offramp/offramp" + module_onramp "github.com/smartcontractkit/chainlink-sui/bindings/generated/ccip/ccip_onramp/onramp" + module_router "github.com/smartcontractkit/chainlink-sui/bindings/generated/ccip/ccip_router" + suideploy "github.com/smartcontractkit/chainlink-sui/deployment" + "github.com/smartcontractkit/chainlink-sui/deployment/lanes" + "github.com/smartcontractkit/chainlink-sui/deployment/ops/mcmstest" + "github.com/smartcontractkit/chainlink-sui/deployment/utils" +) + +const ( + testOnRampPackageID = "0xf87c6010be571a304f0d860857204bc66f037842156f0f6c9d80be265fd83752" + testOnRampStateObjectID = "0x75ec1e10b4302f7c69476eb196c88a0aa43a4d509bbbe5cc1feb213e4b6dd58b" + testOnRampOwnerCapID = "0x8101795ff02d4935a05fb519e1b21b83855a970639cf0c28fa7a51f7d2e689ae" + testRouterPackageID = "0xed4613bd35004954c07150c3e9b10230f5e23e3058bc2ca0e3e676cb43eb4dc1" + testRouterStateObjectID = "0xbb2486d233b0d358f82fb8c4c5c75881e65069ea8ebe5ab692a636c9e0eff7cd" + testRouterOwnerCapID = "0xb000000000000000000000000000000000000000000000000000000000000001" + testOffRampStateObjectID = "0x4fdacea0d627df26a6f34ac62952ed8d3c32ea70aacb90a7f2134b39e36a79cd" + testOffRampOwnerCapID = "0x8f7eb5b7879449519b39db447b94c87b6bb88f0a1deff40b3e0ffef3d1058f69" +) + +func TestConfigureLaneLegAsSource_MCMSBatchOp_EncoderParity(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := testSuiChains() + evmRouter := common.HexToAddress(evmRouterAddress).Bytes() + input := sourceLegInput(evmRouter) + destRouterHex := leftPaddedAddressHex(evmRouter) + + report, err := runSourceLeg(t, env, chains, input) + require.NoError(t, err) + require.Len(t, report.Output.BatchOps, 5) + + feeQuoter, err := module_fee_quoter.NewFeeQuoter(testCCIPPackageID, nil) + require.NoError(t, err) + onRamp, err := module_onramp.NewOnramp(testOnRampPackageID, nil) + require.NoError(t, err) + router, err := module_router.NewRouter(testRouterPackageID, nil) + require.NoError(t, err) + + t.Run("apply_token_transfer_fee_config_updates", func(t *testing.T) { + encoded, err := feeQuoter.Encoder().ApplyTokenTransferFeeConfigUpdates( + bind.Object{Id: testCCIPObjectRef}, + bind.Object{Id: testCCIPOwnerCapID}, + evmSepoliaSelector, + []string{testLinkCoinMetadata}, + []uint32{lanes.DefaultLinkTokenTransferMinFeeUsdCents}, + []uint32{lanes.DefaultLinkTokenTransferMaxFeeUsdCents}, + []uint16{lanes.DefaultLinkTokenTransferDeciBps}, + []uint32{lanes.DefaultLinkTokenTransferDestGasOverhead}, + []uint32{lanes.DefaultLinkTokenTransferDestBytesOverhead}, + []bool{true}, + []string{}, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches(t, report.Output.BatchOps[0].Transactions[0].Data, encoded, testCCIPObjectRef, nil) + }) + + t.Run("apply_dest_chain_config_updates", func(t *testing.T) { + cfg := input.Dest.FeeQuoterDestChainConfig + translated := lanes.TranslateDestChainConfig(cfg, input.Dest.Selector) + encoded, err := feeQuoter.Encoder().ApplyDestChainConfigUpdates( + bind.Object{Id: testCCIPObjectRef}, + bind.Object{Id: testCCIPOwnerCapID}, + translated.DestChainSelector, + translated.IsEnabled, + translated.MaxNumberOfTokensPerMsg, + translated.MaxDataBytes, + translated.MaxPerMsgGasLimit, + translated.DestGasOverhead, + translated.DestGasPerPayloadByteBase, + translated.DestGasPerPayloadByteHigh, + translated.DestGasPerPayloadByteThreshold, + translated.DestDataAvailabilityOverheadGas, + translated.DestGasPerDataAvailabilityByte, + translated.DestDataAvailabilityMultiplierBps, + translated.ChainFamilySelector, + translated.EnforceOutOfOrder, + translated.DefaultTokenFeeUsdCents, + translated.DefaultTokenDestGasOverhead, + translated.DefaultTxGasLimit, + translated.GasMultiplierWeiPerEth, + translated.GasPriceStalenessThreshold, + translated.NetworkFeeUsdCents, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches(t, report.Output.BatchOps[1].Transactions[0].Data, encoded, testCCIPObjectRef, nil) + }) + + t.Run("apply_premium_multiplier_wei_per_eth_updates", func(t *testing.T) { + encoded, err := feeQuoter.Encoder().ApplyPremiumMultiplierWeiPerEthUpdates( + bind.Object{Id: testCCIPObjectRef}, + bind.Object{Id: testCCIPOwnerCapID}, + []string{testLinkCoinMetadata}, + []uint64{lanes.DefaultLinkPremiumMultiplierWeiPerEth}, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches(t, report.Output.BatchOps[2].Transactions[0].Data, encoded, testCCIPObjectRef, nil) + }) + + t.Run("onramp_apply_dest_chain_config_updates", func(t *testing.T) { + encoded, err := onRamp.Encoder().ApplyDestChainConfigUpdates( + bind.Object{Id: testCCIPObjectRef}, + bind.Object{Id: testOnRampStateObjectID}, + bind.Object{Id: testOnRampOwnerCapID}, + []uint64{evmSepoliaSelector}, + []bool{input.Source.AllowListEnabled}, + []string{destRouterHex}, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches(t, report.Output.BatchOps[3].Transactions[0].Data, encoded, testOnRampStateObjectID, nil) + }) + + t.Run("router_set_on_ramps", func(t *testing.T) { + encoded, err := router.Encoder().SetOnRamps( + bind.Object{Id: testRouterOwnerCapID}, + bind.Object{Id: testRouterStateObjectID}, + []uint64{evmSepoliaSelector}, + []string{testOnRampPackageID}, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches(t, report.Output.BatchOps[4].Transactions[0].Data, encoded, testRouterStateObjectID, nil) + }) +} + +func TestConfigureLaneLegAsSource_OnRampEncoderParity_20And32ByteRouterEquivalent(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := testSuiChains() + evmRouter := common.HexToAddress(evmRouterAddress).Bytes() + + report20, err := runSourceLeg(t, env, chains, sourceLegInput(evmRouter)) + require.NoError(t, err) + + padded := make([]byte, 32) + copy(padded[12:], evmRouter) + report32, err := runSourceLeg(t, env, chains, sourceLegInput(padded)) + require.NoError(t, err) + + require.Equal( + t, + report20.Output.BatchOps[3].Transactions[0].Data, + report32.Output.BatchOps[3].Transactions[0].Data, + "OnRamp dest router encoding should match for 20-byte and left-padded 32-byte EVM routers", + ) +} + +func TestConfigureLaneLegAsDest_MCMSBatchOp_EncoderParity(t *testing.T) { + env := testEnvWithAddressBook(t) + input := destLegInput() + + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] + err := lanes.WithConnectChainsEnvironment(env, func() error { + var execErr error + report, execErr = cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + testSuiChains(), + input, + ) + return execErr + }) + require.NoError(t, err) + require.Len(t, report.Output.BatchOps, 1) + + offRamp, err := module_offramp.NewOfframp(testOffRampPackageID, nil) + require.NoError(t, err) + encoded, err := offRamp.Encoder().ApplySourceChainConfigUpdates( + bind.Object{Id: testCCIPObjectRef}, + bind.Object{Id: testOffRampStateObjectID}, + bind.Object{Id: testOffRampOwnerCapID}, + []uint64{evmSepoliaSelector}, + []bool{true}, + []bool{false}, + [][]byte{input.Source.OnRamp}, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches( + t, + report.Output.BatchOps[0].Transactions[0].Data, + encoded, + testOffRampStateObjectID, + nil, + ) +} + +func TestConfigureLaneLegAsDest_Flags(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := testSuiChains() + + tests := []struct { + name string + isDisabled bool + rmnEnabled bool + wantEnabled bool + wantRMNDisabled bool + }{ + {name: "enabled with RMN verification", isDisabled: false, rmnEnabled: true, wantEnabled: true, wantRMNDisabled: false}, + {name: "enabled without RMN verification", isDisabled: false, rmnEnabled: false, wantEnabled: true, wantRMNDisabled: true}, + {name: "disabled lane", isDisabled: true, rmnEnabled: true, wantEnabled: false, wantRMNDisabled: false}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + input := destLegInput() + input.IsDisabled = tc.isDisabled + input.Source.RMNVerificationEnabled = tc.rmnEnabled + + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] + err := lanes.WithConnectChainsEnvironment(env, func() error { + var execErr error + report, execErr = cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + chains, + input, + ) + return execErr + }) + require.NoError(t, err) + + offRamp, err := module_offramp.NewOfframp(testOffRampPackageID, nil) + require.NoError(t, err) + encoded, err := offRamp.Encoder().ApplySourceChainConfigUpdates( + bind.Object{Id: testCCIPObjectRef}, + bind.Object{Id: testOffRampStateObjectID}, + bind.Object{Id: testOffRampOwnerCapID}, + []uint64{evmSepoliaSelector}, + []bool{tc.wantEnabled}, + []bool{tc.wantRMNDisabled}, + [][]byte{input.Source.OnRamp}, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches( + t, + report.Output.BatchOps[0].Transactions[0].Data, + encoded, + testOffRampStateObjectID, + nil, + ) + }) + } +} + +func TestConfigureLaneLegAsDest_PartialLatestPackageIDs(t *testing.T) { + env := testEnvWithAddressBook(t) + const latestOffRampPackageID = "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] + err := lanes.RunConnectChainsWithSuiScopes(env, map[uint64]lanes.LatestPackageIDsConfig{ + suiTestnetSelector: {OffRamp: latestOffRampPackageID}, + }, func() error { + var execErr error + report, execErr = cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + testSuiChains(), + destLegInput(), + ) + return execErr + }) + require.NoError(t, err) + tx := report.Output.BatchOps[0].Transactions[0] + require.Equal(t, testOffRampPackageID, tx.To) + latest, err := utils.TransactionLatestPackageID(tx) + require.NoError(t, err) + require.Equal(t, latestOffRampPackageID, latest) +} + +func TestRunConnectChainsWithSuiScopes(t *testing.T) { + env := testEnvWithAddressBook(t) + adapter := &lanes.SuiAdapter{} + const latestOffRamp = "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + + err := lanes.RunConnectChainsWithSuiScopes(env, map[uint64]lanes.LatestPackageIDsConfig{ + suiTestnetSelector: {OffRamp: latestOffRamp}, + }, func() error { + onRamp, err := adapter.GetOnRampAddress(nil, suiTestnetSelector) + require.NoError(t, err) + require.Equal(t, testOnRampPackageID, "0x"+hex.EncodeToString(onRamp)) + + var report cldf_ops.SequenceReport[laneapi.UpdateLanesInput, sequences.OnChainOutput] + report, err = cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + testSuiChains(), + destLegInput(), + ) + require.NoError(t, err) + latest, err := utils.TransactionLatestPackageID(report.Output.BatchOps[0].Transactions[0]) + require.NoError(t, err) + require.Equal(t, latestOffRamp, latest) + return nil + }) + require.NoError(t, err) +} + +func TestConfigureLaneLegAsSource_MissingAddressBookFields(t *testing.T) { + chains := testSuiChains() + router := common.HexToAddress(evmRouterAddress).Bytes() + + t.Run("missing link token metadata", func(t *testing.T) { + env := testEnvOmittingAddressTypes(t, string(suideploy.SuiLinkTokenObjectMetadataID)) + err := lanes.WithConnectChainsEnvironment(env, func() error { + _, execErr := cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsSource, + chains, + sourceLegInput(router), + ) + return execErr + }) + require.Error(t, err) + require.Contains(t, err.Error(), "SuiLinkTokenObjectMetadataID") + }) +} + +func TestConfigureLaneLegAsDest_MissingAddressBookFields(t *testing.T) { + chains := testSuiChains() + + t.Run("missing offramp owner cap", func(t *testing.T) { + env := testEnvOmittingAddressTypes(t, string(suideploy.SuiOffRampOwnerCapObjectIDType)) + err := lanes.WithConnectChainsEnvironment(env, func() error { + _, execErr := cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.ConfigureLaneLegAsDest, + chains, + destLegInput(), + ) + return execErr + }) + require.Error(t, err) + require.Contains(t, err.Error(), "SuiOffRampOwnerCapObjectID") + }) +} + +func TestDisableRemoteChain_NoOp(t *testing.T) { + report, err := cldf_ops.ExecuteSequence( + mcmstest.Bundle(t), + lanes.DisableRemoteChain, + testSuiChains(), + laneapi.DisableRemoteChainInput{ + LocalChainSelector: suiTestnetSelector, + RemoteChainSelector: evmSepoliaSelector, + }, + ) + require.NoError(t, err) + require.Empty(t, report.Output.BatchOps) +} + +func TestSuiLaneAdapter_RegisteredInRegistry(t *testing.T) { + version := semver.MustParse("1.6.0") + adapter, ok := laneapi.GetLaneAdapterRegistry().GetLaneAdapter(chain_selectors.FamilySui, version) + require.True(t, ok) + require.IsType(t, &lanes.SuiAdapter{}, adapter) + + selector := adapter.(laneapi.ChainMetadataProvider).GetChainFamilySelector() + require.Equal(t, [4]byte{0xc4, 0xe0, 0x59, 0x53}, selector) +} + +func TestConfigureLaneLegAsSource_AllowListEnabled(t *testing.T) { + env := testEnvWithAddressBook(t) + chains := testSuiChains() + input := sourceLegInput(common.HexToAddress(evmRouterAddress).Bytes()) + input.Source.AllowListEnabled = true + + report, err := runSourceLeg(t, env, chains, input) + require.NoError(t, err) + + onRamp, err := module_onramp.NewOnramp(testOnRampPackageID, nil) + require.NoError(t, err) + encoded, err := onRamp.Encoder().ApplyDestChainConfigUpdates( + bind.Object{Id: testCCIPObjectRef}, + bind.Object{Id: testOnRampStateObjectID}, + bind.Object{Id: testOnRampOwnerCapID}, + []uint64{evmSepoliaSelector}, + []bool{true}, + []string{leftPaddedAddressHex(common.HexToAddress(evmRouterAddress).Bytes())}, + ) + require.NoError(t, err) + mcmstest.AssertProposalDataMatches(t, report.Output.BatchOps[3].Transactions[0].Data, encoded, testOnRampStateObjectID, nil) +} + +func leftPaddedAddressHex(b []byte) string { + padded := make([]byte, 32) + copy(padded[32-len(b):], b) + return "0x" + hex.EncodeToString(padded) +} + +func testEnvOmittingAddressTypes(t *testing.T, omitTypes ...string) cldf.Environment { + t.Helper() + + b, err := os.ReadFile("../testdata/addresses.json") + require.NoError(t, err) + + addrsByChain := make(map[uint64]map[string]cldf.TypeAndVersion) + require.NoError(t, json.Unmarshal(b, &addrsByChain)) + + omit := make(map[string]struct{}, len(omitTypes)) + for _, typ := range omitTypes { + omit[typ] = struct{}{} + } + + chainAddrs := addrsByChain[suiTestnetSelector] + filtered := make(map[string]cldf.TypeAndVersion, len(chainAddrs)) + for addr, tv := range chainAddrs { + if _, skip := omit[string(tv.Type)]; skip { + continue + } + filtered[addr] = tv + } + addrsByChain[suiTestnetSelector] = filtered + + return cldf.Environment{ + Name: "test", + ExistingAddresses: cldf.NewMemoryAddressBookFromMap(addrsByChain), + BlockChains: chain.NewBlockChains(map[uint64]chain.BlockChain{ + suiTestnetSelector: sui.Chain{}, + }), + } +} diff --git a/deployment/lanes/connect_chains_test.go b/deployment/lanes/connect_chains_test.go index a5d081fd..530d6e8f 100644 --- a/deployment/lanes/connect_chains_test.go +++ b/deployment/lanes/connect_chains_test.go @@ -11,10 +11,8 @@ import ( "github.com/smartcontractkit/chainlink-deployments-framework/chain" "github.com/smartcontractkit/chainlink-deployments-framework/chain/sui" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" - cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" - "github.com/smartcontractkit/chainlink-sui/bindings/bind" - module_fee_quoter "github.com/smartcontractkit/chainlink-sui/bindings/generated/ccip/ccip/fee_quoter" "github.com/smartcontractkit/chainlink-sui/deployment/lanes" "github.com/smartcontractkit/chainlink-sui/deployment/ops/mcmstest" "github.com/smartcontractkit/chainlink-sui/deployment/utils" @@ -219,41 +217,6 @@ func TestConfigureLaneLegAsSource_RouterAddressBytes(t *testing.T) { }) } -func TestConfigureLaneLegAsSource_MCMSBatchOp_FirstOpEncoderParity(t *testing.T) { - env := testEnvWithAddressBook(t) - chains := testSuiChains() - input := sourceLegInput(common.HexToAddress(evmRouterAddress).Bytes()) - - report, err := runSourceLeg(t, env, chains, input) - require.NoError(t, err) - require.Len(t, report.Output.BatchOps, 5) - require.Equal(t, suiTestnetSelector, uint64(report.Output.BatchOps[0].ChainSelector)) - - feeQuoter, err := module_fee_quoter.NewFeeQuoter(testCCIPPackageID, nil) - require.NoError(t, err) - encodedCall, err := feeQuoter.Encoder().ApplyTokenTransferFeeConfigUpdates( - bind.Object{Id: testCCIPObjectRef}, - bind.Object{Id: testCCIPOwnerCapID}, - evmSepoliaSelector, - []string{testLinkCoinMetadata}, - []uint32{lanes.DefaultLinkTokenTransferMinFeeUsdCents}, - []uint32{lanes.DefaultLinkTokenTransferMaxFeeUsdCents}, - []uint16{lanes.DefaultLinkTokenTransferDeciBps}, - []uint32{lanes.DefaultLinkTokenTransferDestGasOverhead}, - []uint32{lanes.DefaultLinkTokenTransferDestBytesOverhead}, - []bool{true}, - []string{}, - ) - require.NoError(t, err) - mcmstest.AssertProposalDataMatches( - t, - report.Output.BatchOps[0].Transactions[0].Data, - encodedCall, - testCCIPObjectRef, - nil, - ) -} - func TestConfigureLaneLegAsSource_MCMSBatchOp_WithLatestPackageIDs(t *testing.T) { env := testEnvWithAddressBook(t) chains := testSuiChains() diff --git a/deployment/lanes/remote_address_test.go b/deployment/lanes/remote_address_test.go index e5de9641..612d999b 100644 --- a/deployment/lanes/remote_address_test.go +++ b/deployment/lanes/remote_address_test.go @@ -16,7 +16,7 @@ func TestRemoteAddressBytesToHex(t *testing.T) { t.Run("20 byte EVM address is left padded", func(t *testing.T) { got, err := remoteAddressBytesToHex(evmAddr.Bytes()) require.NoError(t, err) - require.Equal(t, "0x000000000000000000000000d3e190f381f06dc0d289590fd452c42fadac586", got) + require.Equal(t, "0x000000000000000000000000d3e190f381f06dc0d289590fd452c42fa2dac586", got) }) t.Run("32 byte address is preserved", func(t *testing.T) { diff --git a/deployment/lanes/translate.go b/deployment/lanes/translate.go index 5e051b10..a32c851c 100644 --- a/deployment/lanes/translate.go +++ b/deployment/lanes/translate.go @@ -15,26 +15,30 @@ func TranslateDestChainConfig( cfg laneapi.FeeQuoterDestChainConfig, destChainSelector uint64, ) ccip_ops.FeeQuoterApplyDestChainConfigUpdatesInput { + var v1 laneapi.FeeQuoterV1Params + if cfg.V1Params != nil { + v1 = *cfg.V1Params + } return ccip_ops.FeeQuoterApplyDestChainConfigUpdatesInput{ DestChainSelector: destChainSelector, IsEnabled: cfg.IsEnabled, - MaxNumberOfTokensPerMsg: cfg.MaxNumberOfTokensPerMsg, + MaxNumberOfTokensPerMsg: v1.MaxNumberOfTokensPerMsg, MaxDataBytes: cfg.MaxDataBytes, MaxPerMsgGasLimit: cfg.MaxPerMsgGasLimit, DestGasOverhead: cfg.DestGasOverhead, DestGasPerPayloadByteBase: cfg.DestGasPerPayloadByteBase, - DestGasPerPayloadByteHigh: cfg.DestGasPerPayloadByteHigh, - DestGasPerPayloadByteThreshold: cfg.DestGasPerPayloadByteThreshold, - DestDataAvailabilityOverheadGas: cfg.DestDataAvailabilityOverheadGas, - DestGasPerDataAvailabilityByte: cfg.DestGasPerDataAvailabilityByte, - DestDataAvailabilityMultiplierBps: cfg.DestDataAvailabilityMultiplierBps, + DestGasPerPayloadByteHigh: v1.DestGasPerPayloadByteHigh, + DestGasPerPayloadByteThreshold: v1.DestGasPerPayloadByteThreshold, + DestDataAvailabilityOverheadGas: v1.DestDataAvailabilityOverheadGas, + DestGasPerDataAvailabilityByte: v1.DestGasPerDataAvailabilityByte, + DestDataAvailabilityMultiplierBps: v1.DestDataAvailabilityMultiplierBps, ChainFamilySelector: binary.BigEndian.AppendUint32(nil, cfg.ChainFamilySelector), - EnforceOutOfOrder: cfg.EnforceOutOfOrder, + EnforceOutOfOrder: v1.EnforceOutOfOrder, DefaultTokenFeeUsdCents: cfg.DefaultTokenFeeUSDCents, DefaultTokenDestGasOverhead: cfg.DefaultTokenDestGasOverhead, DefaultTxGasLimit: cfg.DefaultTxGasLimit, - GasMultiplierWeiPerEth: cfg.GasMultiplierWeiPerEth, - GasPriceStalenessThreshold: cfg.GasPriceStalenessThreshold, - NetworkFeeUsdCents: cfg.NetworkFeeUSDCents, + GasMultiplierWeiPerEth: v1.GasMultiplierWeiPerEth, + GasPriceStalenessThreshold: v1.GasPriceStalenessThreshold, + NetworkFeeUsdCents: uint32(cfg.NetworkFeeUSDCents), } } diff --git a/go.mod b/go.mod index 03a31621..3c38b9bf 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc - github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a + github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260624154507-ea7ff77a0ddb github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 github.com/stretchr/testify v1.11.1 github.com/test-go/testify v1.1.4 diff --git a/go.sum b/go.sum index b46c29e1..2225de6a 100644 --- a/go.sum +++ b/go.sum @@ -296,8 +296,8 @@ github.com/smartcontractkit/chain-selectors v1.0.102 h1:qYP4+72HfvogCHR5ymwRFee3 github.com/smartcontractkit/chain-selectors v1.0.102/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc h1:Um9FBcf0JNSFuGbxgccDG1vM3cNrMGy0SdJ7r6VbX0o= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a h1:bGNA59hevTF2rt1tkT71tEUPCDlA7HWpzc4tdoY9mDI= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260624154507-ea7ff77a0ddb h1:uSXBvj/idCBg7hMLmek8YYNa0JKggqlTkHYdtIooeNM= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260624154507-ea7ff77a0ddb/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 h1:5z3LQ27MJmhiaeqp9S2TzbF5Wm4GGvUKAYOtE9AauR8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= diff --git a/scripts/go.mod b/scripts/go.mod index 9d73f981..0101c1d6 100644 --- a/scripts/go.mod +++ b/scripts/go.mod @@ -125,9 +125,9 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.102 // indirect github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc // indirect github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320 // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 // indirect - github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb // indirect + github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260624154507-ea7ff77a0ddb // indirect github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 // indirect diff --git a/scripts/go.sum b/scripts/go.sum index b97e1551..a51b4bd7 100644 --- a/scripts/go.sum +++ b/scripts/go.sum @@ -601,14 +601,14 @@ github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc h github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig= github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320 h1:ix4tCtSTB7S2XGll+uqnhrqAQ+2iW/Zk/vnPjBMYRB0= github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320/go.mod h1:WKmNUX4oy8IvB66ukudrE99uaXjlZ7WghCDwHOTyB1c= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a h1:bGNA59hevTF2rt1tkT71tEUPCDlA7HWpzc4tdoY9mDI= -github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260612233420-cdac9c74970a/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 h1:jkChf04hhdiMBApbb+lLDxHMY62Md6UeM7v++GSw3K8= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139/go.mod h1:wuhagkM/lU0GbV2YcrROOH0GlsfXJYwm6qmpa4CK70w= -github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 h1:tw3K4UkH5XfW5SoyYkvAlbzrccoGSLdz/XkxD6nyGC8= -github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139/go.mod h1:1WcontO9PeuKdUf5HXfs3nuICtzUvFNnyCmrHkTCF9Y= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a h1:PfJMTY1jBeSMZw5bd0kPaIEJel4JQnq2k3CLstkLTj8= -github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260612233420-cdac9c74970a/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260624154507-ea7ff77a0ddb h1:uSXBvj/idCBg7hMLmek8YYNa0JKggqlTkHYdtIooeNM= +github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260624154507-ea7ff77a0ddb/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb h1:TZUVwF0ZexyJKqBjutCGjR/kG3LOa1krTQt/EXFgMYg= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb/go.mod h1:xu0Jum/nGRkjBwT/Vq7WCElWOTBBkFRwG0ZIaw9tF2I= +github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb h1:2p+8KYL0bhHblGcOJKRH84i9QduKGcY72NYcLJzNdNc= +github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260624154507-ea7ff77a0ddb h1:lG7cBn+mRgkdPpp1MSGK8pLq72g8LHa3aVPHZv/UReQ= +github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260624154507-ea7ff77a0ddb/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 h1:5z3LQ27MJmhiaeqp9S2TzbF5Wm4GGvUKAYOtE9AauR8= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= From 8b6de107db31e3d27e172f5012581aaf50b5cf5a Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 26 Jun 2026 16:39:27 -0400 Subject: [PATCH 11/13] lint --- integration-tests/go.mod | 6 +++--- integration-tests/go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 4aa81b86..27c59c67 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -13,7 +13,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260625091148-e5618f5682ee github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 - github.com/smartcontractkit/chainlink-sui v0.0.0-20260610194843-349ea43d69ce + github.com/smartcontractkit/chainlink-sui v0.0.0 github.com/smartcontractkit/chainlink-sui/deployment v0.0.0-00010101000000-000000000000 github.com/smartcontractkit/mcms v0.48.0 github.com/stretchr/testify v1.11.1 @@ -134,8 +134,8 @@ require ( github.com/smartcontractkit/chainlink-aptos v0.0.0-20260428085939-5c70de12dbfc // indirect github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320 // indirect github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260625091148-e5618f5682ee // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260505131349-78e491b80735 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.19.0 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 9321bb7e..a74375a0 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -603,10 +603,10 @@ github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320 github.com/smartcontractkit/chainlink-canton v0.0.0-20260609155219-dcbe77d4a320/go.mod h1:WKmNUX4oy8IvB66ukudrE99uaXjlZ7WghCDwHOTyB1c= github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260625091148-e5618f5682ee h1:YAE9gMuCsjp3toJXBQge7pvSZhsFCv9GakTEjjoiE50= github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260625091148-e5618f5682ee/go.mod h1:2xVZJ3o9udYFeJhwyHXAMlNhptJ99uoiGjzfOicYNX8= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139 h1:jkChf04hhdiMBApbb+lLDxHMY62Md6UeM7v++GSw3K8= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260129103204-4c8453dd8139/go.mod h1:wuhagkM/lU0GbV2YcrROOH0GlsfXJYwm6qmpa4CK70w= -github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139 h1:tw3K4UkH5XfW5SoyYkvAlbzrccoGSLdz/XkxD6nyGC8= -github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260129103204-4c8453dd8139/go.mod h1:1WcontO9PeuKdUf5HXfs3nuICtzUvFNnyCmrHkTCF9Y= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb h1:TZUVwF0ZexyJKqBjutCGjR/kG3LOa1krTQt/EXFgMYg= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260624154507-ea7ff77a0ddb/go.mod h1:xu0Jum/nGRkjBwT/Vq7WCElWOTBBkFRwG0ZIaw9tF2I= +github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb h1:2p+8KYL0bhHblGcOJKRH84i9QduKGcY72NYcLJzNdNc= +github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260624154507-ea7ff77a0ddb/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260625091148-e5618f5682ee h1:ScVlPpWyoswUlu3izqOH7N0oW5UCrp7lPifkOZ2Mgew= github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260625091148-e5618f5682ee/go.mod h1:xDXlDsou69NYOolOAj+KITRn9luER6Bg52NXelrLl+A= github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 h1:5z3LQ27MJmhiaeqp9S2TzbF5Wm4GGvUKAYOtE9AauR8= From 575d2f4ace5c5744d670a7c912c56696322ffbd7 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 26 Jun 2026 16:59:41 -0400 Subject: [PATCH 12/13] fix --- .github/workflows/sui-ccip-test.yml | 13 +++++++++++++ bindings/bind/compile.go | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sui-ccip-test.yml b/.github/workflows/sui-ccip-test.yml index 15f1414b..24b0abf1 100644 --- a/.github/workflows/sui-ccip-test.yml +++ b/.github/workflows/sui-ccip-test.yml @@ -184,6 +184,19 @@ jobs: go get github.com/smartcontractkit/chainlink-sui@$REF go get github.com/smartcontractkit/chainlink-sui/deployment@$REF + # chainlink-sui/deployment pulls a newer chainlink-ccip/deployment than chainlink + # develop pins for chains/evm; keep both sibling modules on the same commit. + CCIP_DEP=$(grep -m1 'github.com/smartcontractkit/chainlink-ccip/deployment v' \ + ../../chainlink-sui/deployment/go.mod | awk '{print $2}') + CCIP_REF="${CCIP_DEP##*-}" + echo "Aligning chainlink-ccip modules to commit ${CCIP_REF} (from ${CCIP_DEP})" + go get "github.com/smartcontractkit/chainlink-ccip/deployment@${CCIP_REF}" + go get "github.com/smartcontractkit/chainlink-ccip/chains/evm@${CCIP_REF}" + + cd ../deployment + go get "github.com/smartcontractkit/chainlink-ccip/deployment@${CCIP_REF}" + go get "github.com/smartcontractkit/chainlink-ccip/chains/evm@${CCIP_REF}" + cd .. make gomodtidy diff --git a/bindings/bind/compile.go b/bindings/bind/compile.go index 4baf1c06..12fed1e5 100644 --- a/bindings/bind/compile.go +++ b/bindings/bind/compile.go @@ -1088,7 +1088,7 @@ func getDynamicSuiRPC() (string, error) { return envRPC, nil } - cmd := exec.Command("docker", "ps", "--filter", "ancestor=mysten/sui-tools:mainnet-v1.72.5", "--format", "{{.Ports}}") + cmd := exec.Command("docker", "ps", "--filter", "ancestor=mysten/sui-tools:mainnet-v1.73.2", "--format", "{{.Ports}}") out, err := cmd.Output() if err != nil { return "", fmt.Errorf("docker ps failed: %w", err) From 3e1d5d82dd80fecf09c35c75068d4ec55ecec636 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 26 Jun 2026 17:52:01 -0400 Subject: [PATCH 13/13] fix lint --- bindings/bind/compile.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/bind/compile.go b/bindings/bind/compile.go index 12fed1e5..d2cea82a 100644 --- a/bindings/bind/compile.go +++ b/bindings/bind/compile.go @@ -1,6 +1,7 @@ package bind import ( + "context" "embed" "encoding/base64" "encoding/hex" @@ -1088,7 +1089,7 @@ func getDynamicSuiRPC() (string, error) { return envRPC, nil } - cmd := exec.Command("docker", "ps", "--filter", "ancestor=mysten/sui-tools:mainnet-v1.73.2", "--format", "{{.Ports}}") + cmd := exec.CommandContext(context.Background(), "docker", "ps", "--filter", "ancestor=mysten/sui-tools:mainnet-v1.73.2", "--format", "{{.Ports}}") out, err := cmd.Output() if err != nil { return "", fmt.Errorf("docker ps failed: %w", err)