Skip to content

Consolidate CS to enable lane between an existing EVM chain and solana #17607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 8, 2025
443 changes: 443 additions & 0 deletions deployment/ccip/changeset/crossfamily/v1_6/cs_add_evm_solana_lane.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package v1_6_test

import (
"math/big"
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/require"

solOffRamp "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp"
solRouter "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router"
solFeeQuoter "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/fee_quoter"
solState "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/state"
"github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext"

ccipchangeset "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
crossfamily "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/crossfamily/v1_6"
ccipChangesetSolana "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/solana"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/v1_6"
commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
)

func TestAddEVMSolanaLaneBidirectional(t *testing.T) {
for _, tc := range []struct {
name string
mcmsEnabled bool
}{
{
name: "MCMS disabled",
mcmsEnabled: false,
},
{
name: "MCMS enabled",
mcmsEnabled: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ctx := testcontext.Get(t)
tenv, _ := testhelpers.NewMemoryEnvironment(t, testhelpers.WithSolChains(1))
e := tenv.Env
solChains := tenv.Env.AllChainSelectorsSolana()
require.NotEmpty(t, solChains)
evmChains := tenv.Env.AllChainSelectors()
require.NotEmpty(t, evmChains)
solChain := solChains[0]
evmChain := evmChains[0]
evmState, err := ccipchangeset.LoadOnchainState(e)
require.NoError(t, err)
var mcmsConfig *ccipChangesetSolana.MCMSConfigSolana
if tc.mcmsEnabled {
_, _ = testhelpers.TransferOwnershipSolana(t, &e, solChain, true,
ccipChangesetSolana.CCIPContractsToTransfer{
Router: true,
FeeQuoter: true,
OffRamp: true,
})
mcmsConfig = &ccipChangesetSolana.MCMSConfigSolana{
MCMS: &proposalutils.TimelockConfig{
MinDelay: 1 * time.Second,
},
RouterOwnedByTimelock: true,
FeeQuoterOwnedByTimelock: true,
OffRampOwnedByTimelock: true,
}
testhelpers.TransferToTimelock(t, tenv, evmState, []uint64{evmChain})
}

// Add EVM and Solana lane
evmChainState := evmState.Chains[evmChain]
feeQCfgSolana := solFeeQuoter.DestChainConfig{
IsEnabled: true,
DefaultTxGasLimit: 200000,
MaxPerMsgGasLimit: 3000000,
MaxDataBytes: 30000,
MaxNumberOfTokensPerMsg: 5,
DefaultTokenDestGasOverhead: 90000,
DestGasOverhead: 90000,
// bytes4(keccak256("CCIP ChainFamilySelector EVM"))
ChainFamilySelector: [4]uint8{40, 18, 213, 44},
}
feeQCfgEVM := v1_6.DefaultFeeQuoterDestChainConfig(true, solChain)
evmSolanaLaneCSInput := crossfamily.AddRemoteChainE2EConfig{
SolanaChainSelector: solChain,
EVMChainSelector: evmChain,
IsTestRouter: true,
EVMOnRampAllowListEnabled: false,
EVMFeeQuoterDestChainInput: feeQCfgEVM,
InitialSolanaGasPriceForEVMFeeQuoter: testhelpers.DefaultGasPrice,
InitialEVMTokenPricesForEVMFeeQuoter: map[common.Address]*big.Int{
evmChainState.LinkToken.Address(): testhelpers.DefaultLinkPrice,
evmChainState.Weth9.Address(): testhelpers.DefaultWethPrice,
},
IsRMNVerificationEnabledOnEVMOffRamp: true,
SolanaRouterConfig: ccipChangesetSolana.RouterConfig{
RouterDestinationConfig: solRouter.DestChainConfig{
AllowListEnabled: true,
AllowedSenders: []solana.PublicKey{e.SolChains[solChain].DeployerKey.PublicKey()},
},
},
SolanaOffRampConfig: ccipChangesetSolana.OffRampConfig{
EnabledAsSource: true,
},
SolanaFeeQuoterConfig: ccipChangesetSolana.FeeQuoterConfig{
FeeQuoterDestinationConfig: feeQCfgSolana,
},
MCMSConfig: mcmsConfig,
}

// run the changeset
e, _, err = commonchangeset.ApplyChangesetsV2(t, e, []commonchangeset.ConfiguredChangeSet{
commonchangeset.Configure(crossfamily.AddEVMAndSolanaLaneChangeset, evmSolanaLaneCSInput),
})
require.NoError(t, err)

// Check that the changeset was applied
evmState, err = ccipchangeset.LoadOnchainState(e)
require.NoError(t, err)

solanaState, err := ccipchangeset.LoadOnchainStateSolana(e)
require.NoError(t, err)

// evm changes
evmChainState = evmState.Chains[evmChain]

destCfg, err := evmChainState.OnRamp.GetDestChainConfig(&bind.CallOpts{Context: ctx}, solChain)
require.NoError(t, err)
require.Equal(t, evmChainState.TestRouter.Address(), destCfg.Router)
require.False(t, destCfg.AllowlistEnabled)

srcCfg, err := evmChainState.OffRamp.GetSourceChainConfig(&bind.CallOpts{Context: ctx}, solChain)
require.NoError(t, err)
require.Equal(t, evmChainState.TestRouter.Address(), destCfg.Router)
require.True(t, srcCfg.IsRMNVerificationDisabled)
require.True(t, srcCfg.IsEnabled)
expOnRamp, err := evmState.GetOnRampAddressBytes(solChain)
require.NoError(t, err)
require.Equal(t, expOnRamp, srcCfg.OnRamp)

fqDestCfg, err := evmChainState.FeeQuoter.GetDestChainConfig(&bind.CallOpts{Context: ctx}, solChain)
require.NoError(t, err)
testhelpers.AssertEqualFeeConfig(t, feeQCfgEVM, fqDestCfg)

// solana changes
var offRampSourceChain solOffRamp.SourceChain
var destChainStateAccount solRouter.DestChain
var destChainFqAccount solFeeQuoter.DestChain
var offRampEvmSourceChainPDA solana.PublicKey
var evmDestChainStatePDA solana.PublicKey
var fqEvmDestChainPDA solana.PublicKey
offRampEvmSourceChainPDA, _, _ = solState.FindOfframpSourceChainPDA(evmChain, solanaState.SolChains[solChain].OffRamp)
err = e.SolChains[solChain].GetAccountDataBorshInto(e.GetContext(), offRampEvmSourceChainPDA, &offRampSourceChain)
require.NoError(t, err)
require.True(t, offRampSourceChain.Config.IsEnabled)

fqEvmDestChainPDA, _, _ = solState.FindFqDestChainPDA(evmChain, solanaState.SolChains[solChain].FeeQuoter)
err = e.SolChains[solChain].GetAccountDataBorshInto(e.GetContext(), fqEvmDestChainPDA, &destChainFqAccount)
require.NoError(t, err, "failed to get account info")
require.Equal(t, solFeeQuoter.TimestampedPackedU224{}, destChainFqAccount.State.UsdPerUnitGas)
require.True(t, destChainFqAccount.Config.IsEnabled)
require.Equal(t, feeQCfgSolana, destChainFqAccount.Config)

evmDestChainStatePDA, _ = solState.FindDestChainStatePDA(evmChain, solanaState.SolChains[solChain].Router)
err = e.SolChains[solChain].GetAccountDataBorshInto(e.GetContext(), evmDestChainStatePDA, &destChainStateAccount)
require.NoError(t, err)
require.NotEmpty(t, destChainStateAccount.Config.AllowedSenders)
require.True(t, destChainStateAccount.Config.AllowListEnabled)
})
}
}
2 changes: 0 additions & 2 deletions deployment/ccip/changeset/cs_prerequisites.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"golang.org/x/sync/errgroup"

"github.com/smartcontractkit/chainlink-ccip/pkg/reader"
Expand Down Expand Up @@ -57,7 +56,6 @@ func DeployPrerequisitesChangeset(env deployment.Environment, cfg DeployPrerequi
}, fmt.Errorf("failed to deploy prerequisite contracts: %w", err)
}
return deployment.ChangesetOutput{
Proposals: []timelock.MCMSWithTimelockProposal{},
AddressBook: ab,
}, nil
}
Expand Down
3 changes: 1 addition & 2 deletions deployment/ccip/changeset/solana/cs_add_remote_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ package solana

import (
"context"
"math"

"fmt"
"math"
"strconv"

"github.com/gagliardetto/solana-go"
Expand Down
33 changes: 32 additions & 1 deletion deployment/ccip/changeset/testhelpers/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/v1_6"
commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/common/changeset/state"
commonstate "github.com/smartcontractkit/chainlink/deployment/common/changeset/state"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
commontypes "github.com/smartcontractkit/chainlink/deployment/common/types"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm"
Expand Down Expand Up @@ -2303,7 +2304,7 @@ func DeployLinkTokenTest(t *testing.T, solChains int) {
require.NoError(t, err)
addrs, err := e.ExistingAddresses.AddressesForChain(chain1)
require.NoError(t, err)
state, err := commoncs.MaybeLoadLinkTokenChainState(e.Chains[chain1], addrs)
state, err := commonstate.MaybeLoadLinkTokenChainState(e.Chains[chain1], addrs)
require.NoError(t, err)
// View itself already unit tested
_, err = state.GenerateLinkView()
Expand All @@ -2316,3 +2317,33 @@ func DeployLinkTokenTest(t *testing.T, solChains int) {
require.NotEmpty(t, addrs)
}
}

func TransferToTimelock(
t *testing.T,
tenv DeployedEnv,
state changeset.CCIPOnChainState,
chains []uint64,
) {
timelockContracts := make(map[uint64]*proposalutils.TimelockExecutionContracts, len(chains)+1)
for _, chain := range chains {
timelockContracts[chain] = &proposalutils.TimelockExecutionContracts{
Timelock: state.Chains[chain].Timelock,
CallProxy: state.Chains[chain].CallProxy,
}
}
// Add the home chain to the timelock contracts.
timelockContracts[tenv.HomeChainSel] = &proposalutils.TimelockExecutionContracts{
Timelock: state.Chains[tenv.HomeChainSel].Timelock,
CallProxy: state.Chains[tenv.HomeChainSel].CallProxy,
}
// Transfer ownership to timelock so that we can promote the zero digest later down the line.
_, err := commoncs.Apply(t, tenv.Env,
timelockContracts,
commoncs.Configure(
cldf.CreateLegacyChangeSet(commoncs.TransferToMCMSWithTimelockV2),
GenTestTransferOwnershipConfig(tenv, chains, state),
),
)
require.NoError(t, err)
AssertTimelockOwnership(t, tenv, chains, state)
}
2 changes: 1 addition & 1 deletion deployment/ccip/changeset/v1_5_1/cs_add_token_e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ func addTokenE2ELogic(env deployment.Environment, config AddTokensE2EConfig) (de
// if there are multiple proposals, aggregate them so that we don't have to propose them separately
if len(finalCSOut.MCMSTimelockProposals) > 1 {
aggregatedProposals, err := proposalutils.AggregateProposals(
e, state.EVMMCMSStateByChain(), finalCSOut.MCMSTimelockProposals, nil,
e, state.EVMMCMSStateByChain(), nil, finalCSOut.MCMSTimelockProposals, nil,
"Add Tokens E2E", config.MCMS)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to aggregate proposals: %w", err)
Expand Down
4 changes: 4 additions & 0 deletions deployment/ccip/changeset/v1_6/cs_add_new_chain_e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
mcmslib "github.com/smartcontractkit/mcms"

cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/globals"
Expand Down Expand Up @@ -404,6 +405,7 @@ func addCandidatesForNewChainLogic(e deployment.Environment, c AddCandidatesForN
proposal, err := proposalutils.AggregateProposals(
e,
state.EVMMCMSStateByChain(),
nil,
allProposals,
nil,
fmt.Sprintf("Deploy and set candidates for chain with selector %d", c.NewChain.Selector),
Expand Down Expand Up @@ -583,6 +585,7 @@ func promoteNewChainForConfigLogic(e deployment.Environment, c PromoteNewChainFo
proposal, err := proposalutils.AggregateProposals(
e,
state.EVMMCMSStateByChain(),
nil,
allProposals,
nil,
fmt.Sprintf("Promote chain with selector %d for testing", c.NewChain.Selector),
Expand Down Expand Up @@ -801,6 +804,7 @@ func connectNewChainLogic(env deployment.Environment, c ConnectNewChainConfig) (
proposal, err := proposalutils.AggregateProposals(
env,
state.EVMMCMSStateByChain(),
nil,
allEnablementProposals,
ownershipTransferProposals,
fmt.Sprintf("Connect chain with selector %d to other chains", c.NewChainSelector),
Expand Down
39 changes: 5 additions & 34 deletions deployment/ccip/changeset/v1_6/cs_ccip_home_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext"

cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/globals"
Expand Down Expand Up @@ -123,7 +124,7 @@ func Test_PromoteCandidate(t *testing.T) {

if tc.mcmsEnabled {
// Transfer ownership to timelock so that we can promote the zero digest later down the line.
transferToTimelock(t, tenv, state, []uint64{source, dest})
testhelpers.TransferToTimelock(t, tenv, state, []uint64{source, dest})
}

var (
Expand Down Expand Up @@ -221,7 +222,7 @@ func Test_SetCandidate(t *testing.T) {

if tc.mcmsEnabled {
// Transfer ownership to timelock so that we can promote the zero digest later down the line.
transferToTimelock(t, tenv, state, []uint64{source, dest})
testhelpers.TransferToTimelock(t, tenv, state, []uint64{source, dest})
}

var (
Expand Down Expand Up @@ -361,7 +362,7 @@ func Test_RevokeCandidate(t *testing.T) {

if tc.mcmsEnabled {
// Transfer ownership to timelock so that we can promote the zero digest later down the line.
transferToTimelock(t, tenv, state, []uint64{source, dest})
testhelpers.TransferToTimelock(t, tenv, state, []uint64{source, dest})
}

var (
Expand Down Expand Up @@ -486,36 +487,6 @@ func Test_RevokeCandidate(t *testing.T) {
}
}

func transferToTimelock(
t *testing.T,
tenv testhelpers.DeployedEnv,
state changeset.CCIPOnChainState,
chains []uint64,
) {
timelockContracts := make(map[uint64]*proposalutils.TimelockExecutionContracts, len(chains)+1)
for _, chain := range chains {
timelockContracts[chain] = &proposalutils.TimelockExecutionContracts{
Timelock: state.Chains[chain].Timelock,
CallProxy: state.Chains[chain].CallProxy,
}
}
// Add the home chain to the timelock contracts.
timelockContracts[tenv.HomeChainSel] = &proposalutils.TimelockExecutionContracts{
Timelock: state.Chains[tenv.HomeChainSel].Timelock,
CallProxy: state.Chains[tenv.HomeChainSel].CallProxy,
}
// Transfer ownership to timelock so that we can promote the zero digest later down the line.
_, err := commonchangeset.Apply(t, tenv.Env,
timelockContracts,
commonchangeset.Configure(
cldf.CreateLegacyChangeSet(commonchangeset.TransferToMCMSWithTimelock),
testhelpers.GenTestTransferOwnershipConfig(tenv, chains, state),
),
)
require.NoError(t, err)
testhelpers.AssertTimelockOwnership(t, tenv, chains, state)
}

func Test_UpdateChainConfigs(t *testing.T) {
for _, tc := range []struct {
name string
Expand All @@ -542,7 +513,7 @@ func Test_UpdateChainConfigs(t *testing.T) {

if tc.mcmsEnabled {
// Transfer ownership to timelock so that we can promote the zero digest later down the line.
transferToTimelock(t, tenv, state, []uint64{source, dest})
testhelpers.TransferToTimelock(t, tenv, state, []uint64{source, dest})
}

ccipHome := state.Chains[tenv.HomeChainSel].CCIPHome
Expand Down
Loading
Loading