-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
AddChain inbound CCIP integration test #14377
Changes from 13 commits
ab6b4e4
bdef10f
a5c48c3
902155c
dda8ca8
d75fbec
f79b3b0
41b314e
4ab3826
adbf236
186c7fc
554769f
92052a3
fe703f9
4be4c6e
300676c
0a63818
4ea8911
55fb99b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package ccipdeployment | ||
|
||
import ( | ||
"math/big" | ||
|
||
"github.com/smartcontractkit/ccip-owner-contracts/tools/proposal/mcms" | ||
"github.com/smartcontractkit/ccip-owner-contracts/tools/proposal/timelock" | ||
chainsel "github.com/smartcontractkit/chain-selectors" | ||
|
||
"github.com/smartcontractkit/chainlink-ccip/chainconfig" | ||
"github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" | ||
"github.com/smartcontractkit/chainlink/integration-tests/deployment" | ||
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_config" | ||
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" | ||
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" | ||
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" | ||
) | ||
|
||
// NewChainInboundProposal generates a proposal | ||
// to connect the new chain to the existing chains. | ||
func NewChainInboundProposal( | ||
e deployment.Environment, | ||
state CCIPOnChainState, | ||
homeChainSel uint64, | ||
newChainSel uint64, | ||
sources []uint64, | ||
) (*timelock.MCMSWithTimelockProposal, error) { | ||
// Generate proposal which enables new destination (from test router) on all source chains. | ||
var batches []timelock.BatchChainOperation | ||
metaDataPerChain := make(map[mcms.ChainIdentifier]timelock.MCMSWithTimelockChainMetadata) | ||
for _, source := range sources { | ||
chain, _ := chainsel.ChainBySelector(source) | ||
enableOnRampDest, err := state.Chains[source].OnRamp.ApplyDestChainConfigUpdates(SimTransactOpts(), []onramp.OnRampDestChainConfigArgs{ | ||
{ | ||
DestChainSelector: newChainSel, | ||
Router: state.Chains[source].TestRouter.Address(), | ||
}, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
enablePriceRegDest, err := state.Chains[source].FeeQuoter.ApplyDestChainConfigUpdates( | ||
connorwstein marked this conversation as resolved.
Show resolved
Hide resolved
|
||
SimTransactOpts(), | ||
[]fee_quoter.FeeQuoterDestChainConfigArgs{ | ||
{ | ||
DestChainSelector: newChainSel, | ||
DestChainConfig: defaultFeeQuoterDestChainConfig(), | ||
}, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
initialPrices, err := state.Chains[source].FeeQuoter.UpdatePrices( | ||
SimTransactOpts(), | ||
fee_quoter.InternalPriceUpdates{ | ||
TokenPriceUpdates: []fee_quoter.InternalTokenPriceUpdate{}, | ||
GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ | ||
{ | ||
DestChainSelector: newChainSel, | ||
// TODO: parameterize | ||
UsdPerUnitGas: big.NewInt(2e12), | ||
}, | ||
}}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
batches = append(batches, timelock.BatchChainOperation{ | ||
ChainIdentifier: mcms.ChainIdentifier(chain.Selector), | ||
Batch: []mcms.Operation{ | ||
{ | ||
// Enable the source in on ramp | ||
To: state.Chains[source].OnRamp.Address(), | ||
Data: enableOnRampDest.Data(), | ||
Value: big.NewInt(0), | ||
}, | ||
{ | ||
// Set initial dest prices to unblock testing. | ||
To: state.Chains[source].FeeQuoter.Address(), | ||
Data: initialPrices.Data(), | ||
Value: big.NewInt(0), | ||
}, | ||
{ | ||
To: state.Chains[source].FeeQuoter.Address(), | ||
Data: enablePriceRegDest.Data(), | ||
connorwstein marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Value: big.NewInt(0), | ||
}, | ||
}, | ||
}) | ||
metaDataPerChain[mcms.ChainIdentifier(chain.Selector)] = timelock.MCMSWithTimelockChainMetadata{ | ||
ChainMetadata: mcms.ChainMetadata{ | ||
NonceOffset: 0, | ||
MCMAddress: state.Chains[source].McmAddr, | ||
}, | ||
TimelockAddress: state.Chains[source].TimelockAddr, | ||
} | ||
} | ||
|
||
// Home chain new don. | ||
// - Add new DONs for destination to home chain | ||
nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) | ||
if err != nil { | ||
return nil, err | ||
} | ||
encodedExtraChainConfig, err := chainconfig.EncodeChainConfig(chainconfig.ChainConfig{ | ||
GasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(1000), | ||
DAGasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(0), | ||
OptimisticConfirmations: 1, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
chainConfig := SetupConfigInfo(newChainSel, nodes.PeerIDs(newChainSel), nodes.DefaultF(), encodedExtraChainConfig) | ||
addChain, err := state.Chains[homeChainSel].CCIPConfig.ApplyChainConfigUpdates(SimTransactOpts(), nil, []ccip_config.CCIPConfigTypesChainConfigInfo{ | ||
chainConfig, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
newDONArgs, err := BuildAddDONArgs(e.Logger, state.Chains[newChainSel].OffRamp, e.Chains[newChainSel], nodes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
addDON, err := state.Chains[homeChainSel].CapabilityRegistry.AddDON(SimTransactOpts(), | ||
nodes.PeerIDs(newChainSel), []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ | ||
{ | ||
CapabilityId: CCIPCapabilityId, | ||
Config: newDONArgs, | ||
}, | ||
}, false, false, nodes.DefaultF()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
homeChain, _ := chainsel.ChainBySelector(homeChainSel) | ||
metaDataPerChain[mcms.ChainIdentifier(homeChain.Selector)] = timelock.MCMSWithTimelockChainMetadata{ | ||
ChainMetadata: mcms.ChainMetadata{ | ||
NonceOffset: 0, | ||
MCMAddress: state.Chains[homeChainSel].McmAddr, | ||
}, | ||
TimelockAddress: state.Chains[homeChainSel].TimelockAddr, | ||
} | ||
batches = append(batches, timelock.BatchChainOperation{ | ||
ChainIdentifier: mcms.ChainIdentifier(homeChain.Selector), | ||
Batch: []mcms.Operation{ | ||
{ | ||
// Add the chain first, don needs it to be there. | ||
To: state.Chains[homeChainSel].CCIPConfig.Address(), | ||
Data: addChain.Data(), | ||
Value: big.NewInt(0), | ||
}, | ||
{ | ||
To: state.Chains[homeChainSel].CapabilityRegistry.Address(), | ||
Data: addDON.Data(), | ||
Value: big.NewInt(0), | ||
}, | ||
}, | ||
}) | ||
return timelock.NewMCMSWithTimelockProposal( | ||
"1", | ||
AnieeG marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comments on string/bool/int literal fields would be useful There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah lets punt to CCIP-3411 |
||
2004259681, | ||
[]mcms.Signature{}, | ||
false, | ||
metaDataPerChain, | ||
"blah", | ||
batches, | ||
timelock.Schedule, "0s") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package ccipdeployment | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/smartcontractkit/chainlink/integration-tests/deployment" | ||
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" | ||
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" | ||
"github.com/smartcontractkit/chainlink/v2/core/logger" | ||
) | ||
|
||
func TestAddChainInbound(t *testing.T) { | ||
// 4 chains where the 4th is added after initial deployment. | ||
e := NewEnvironmentWithCRAndJobs(t, logger.TestLogger(t), 4) | ||
require.Equal(t, len(e.Nodes), 5) | ||
state, err := LoadOnchainState(e.Env, e.Ab) | ||
require.NoError(t, err) | ||
// Take first non-home chain as the new chain. | ||
newChain := e.Env.AllChainSelectorsExcluding([]uint64{e.HomeChainSel})[0] | ||
// We deploy to the rest. | ||
initialDeploy := e.Env.AllChainSelectorsExcluding([]uint64{newChain}) | ||
|
||
ab, err := DeployCCIPContracts(e.Env, DeployCCIPContractConfig{ | ||
HomeChainSel: e.HomeChainSel, | ||
ChainsToDeploy: initialDeploy, | ||
CCIPOnChainState: state, | ||
}) | ||
require.NoError(t, err) | ||
require.NoError(t, e.Ab.Merge(ab)) | ||
state, err = LoadOnchainState(e.Env, e.Ab) | ||
require.NoError(t, err) | ||
|
||
// Connect all the existing lanes. | ||
for _, source := range initialDeploy { | ||
for _, dest := range initialDeploy { | ||
if source != dest { | ||
require.NoError(t, AddLane(e.Env, state, source, dest)) | ||
} | ||
} | ||
} | ||
|
||
// Deploy contracts to new chain | ||
newAddresses, err := DeployChainContracts(e.Env, e.Env.Chains[newChain], deployment.NewMemoryAddressBook()) | ||
require.NoError(t, err) | ||
require.NoError(t, e.Ab.Merge(newAddresses)) | ||
state, err = LoadOnchainState(e.Env, e.Ab) | ||
require.NoError(t, err) | ||
|
||
// Transfer onramp/fq ownership to timelock. | ||
// Enable the new dest on the test router. | ||
for _, source := range initialDeploy { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Semantically distinct code blocks below (already designated by comments) can probably be moved to functions to make this test read/understand better There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree - was thinking we wait for another proposal to see if there are nice re-usable utilities we can extract |
||
tx, err := state.Chains[source].OnRamp.TransferOwnership(e.Env.Chains[source].DeployerKey, state.Chains[source].TimelockAddr) | ||
require.NoError(t, err) | ||
_, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) | ||
require.NoError(t, err) | ||
tx, err = state.Chains[source].FeeQuoter.TransferOwnership(e.Env.Chains[source].DeployerKey, state.Chains[source].TimelockAddr) | ||
require.NoError(t, err) | ||
_, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) | ||
require.NoError(t, err) | ||
tx, err = state.Chains[source].TestRouter.ApplyRampUpdates(e.Env.Chains[source].DeployerKey, []router.RouterOnRamp{ | ||
{ | ||
DestChainSelector: newChain, | ||
OnRamp: state.Chains[source].OnRamp.Address(), | ||
}, | ||
}, nil, nil) | ||
_, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) | ||
require.NoError(t, err) | ||
} | ||
// Transfer CR contract ownership | ||
tx, err := state.Chains[e.HomeChainSel].CapabilityRegistry.TransferOwnership(e.Env.Chains[e.HomeChainSel].DeployerKey, state.Chains[e.HomeChainSel].TimelockAddr) | ||
require.NoError(t, err) | ||
_, err = deployment.ConfirmIfNoError(e.Env.Chains[e.HomeChainSel], tx, err) | ||
require.NoError(t, err) | ||
tx, err = state.Chains[e.HomeChainSel].CCIPConfig.TransferOwnership(e.Env.Chains[e.HomeChainSel].DeployerKey, state.Chains[e.HomeChainSel].TimelockAddr) | ||
require.NoError(t, err) | ||
_, err = deployment.ConfirmIfNoError(e.Env.Chains[e.HomeChainSel], tx, err) | ||
require.NoError(t, err) | ||
|
||
acceptOwnershipProposal, err := GenerateAcceptOwnershipProposal(state, e.HomeChainSel, initialDeploy) | ||
require.NoError(t, err) | ||
acceptOwnershipExec := SignProposal(t, e.Env, acceptOwnershipProposal) | ||
// Apply the accept ownership proposal to all the chains. | ||
for _, sel := range initialDeploy { | ||
ExecuteProposal(t, e.Env, acceptOwnershipExec, state, sel) | ||
} | ||
for _, chain := range initialDeploy { | ||
owner, err2 := state.Chains[chain].OnRamp.Owner(nil) | ||
require.NoError(t, err2) | ||
require.Equal(t, state.Chains[chain].TimelockAddr, owner) | ||
} | ||
cfgOwner, err := state.Chains[e.HomeChainSel].CCIPConfig.Owner(nil) | ||
require.NoError(t, err) | ||
crOwner, err := state.Chains[e.HomeChainSel].CapabilityRegistry.Owner(nil) | ||
require.NoError(t, err) | ||
require.Equal(t, state.Chains[e.HomeChainSel].TimelockAddr, cfgOwner) | ||
require.Equal(t, state.Chains[e.HomeChainSel].TimelockAddr, crOwner) | ||
|
||
// Generate and sign inbound proposal to new 4th chain. | ||
chainInboundProposal, err := NewChainInboundProposal(e.Env, state, e.HomeChainSel, newChain, initialDeploy) | ||
require.NoError(t, err) | ||
chainInboundExec := SignProposal(t, e.Env, chainInboundProposal) | ||
for _, sel := range initialDeploy { | ||
ExecuteProposal(t, e.Env, chainInboundExec, state, sel) | ||
} | ||
|
||
// Now configure the new chain using deployer key (not transferred to timelock yet). | ||
var offRampEnables []offramp.OffRampSourceChainConfigArgs | ||
for _, source := range initialDeploy { | ||
offRampEnables = append(offRampEnables, offramp.OffRampSourceChainConfigArgs{ | ||
Router: state.Chains[newChain].Router.Address(), | ||
SourceChainSelector: source, | ||
IsEnabled: true, | ||
OnRamp: common.LeftPadBytes(state.Chains[source].OnRamp.Address().Bytes(), 32), | ||
}) | ||
} | ||
tx, err = state.Chains[newChain].OffRamp.ApplySourceChainConfigUpdates(e.Env.Chains[newChain].DeployerKey, offRampEnables) | ||
require.NoError(t, err) | ||
_, err = deployment.ConfirmIfNoError(e.Env.Chains[newChain], tx, err) | ||
require.NoError(t, err) | ||
// Set the OCR3 config on new 4th chain to enable the plugin. | ||
latestDON, err := LatestCCIPDON(state.Chains[e.HomeChainSel].CapabilityRegistry) | ||
require.NoError(t, err) | ||
ocrConfigs, err := BuildSetOCR3ConfigArgs(latestDON.Id, state.Chains[e.HomeChainSel].CCIPConfig) | ||
require.NoError(t, err) | ||
tx, err = state.Chains[newChain].OffRamp.SetOCR3Configs(e.Env.Chains[newChain].DeployerKey, ocrConfigs) | ||
require.NoError(t, err) | ||
_, err = deployment.ConfirmIfNoError(e.Env.Chains[newChain], tx, err) | ||
require.NoError(t, err) | ||
|
||
// Assert the inbound lanes to the new chain are wired correctly. | ||
state, err = LoadOnchainState(e.Env, e.Ab) | ||
require.NoError(t, err) | ||
for _, chain := range initialDeploy { | ||
cfg, err2 := state.Chains[chain].OnRamp.GetDestChainConfig(nil, newChain) | ||
require.NoError(t, err2) | ||
assert.Equal(t, cfg.Router, state.Chains[chain].TestRouter.Address()) | ||
fqCfg, err2 := state.Chains[chain].FeeQuoter.GetDestChainConfig(nil, newChain) | ||
require.NoError(t, err2) | ||
assert.True(t, fqCfg.IsEnabled) | ||
s, err2 := state.Chains[newChain].OffRamp.GetSourceChainConfig(nil, chain) | ||
require.NoError(t, err2) | ||
assert.Equal(t, common.LeftPadBytes(state.Chains[chain].OnRamp.Address().Bytes(), 32), s.OnRamp) | ||
} | ||
// Ensure job related logs are up to date. | ||
time.Sleep(30 * time.Second) | ||
require.NoError(t, ReplayAllLogs(e.Nodes, e.Env.Chains)) | ||
|
||
// Now that the proposal has been executed we expect to be able to send traffic to this new 4th chain. | ||
seqNr := SendRequest(t, e.Env, state, initialDeploy[0], newChain, true) | ||
ConfirmExecution(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, seqNr) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this actually "send" the tx to the simchain? Proposals just need calldata right? I'm wondering what'll happen here for the non-sim chains which have "automine" or similar on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it doesn't - uses the
NoSend
flag in transact opts, just a scalable way to get the calldata for proposalsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that's clever, alright did not notice that