diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index 007a3c37e52..8b737d2035e 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -1,6 +1,7 @@ package smoke import ( + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" "math/big" "testing" @@ -112,54 +113,36 @@ func TestInitialDeployOnLocal(t *testing.T) { func TestTokenTransfer(t *testing.T) { t.Parallel() - lggr := logger.TestLogger(t) - ctx := ccdeploy.Context(t) - tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) + tenv, state := setupEnvironment(t) - e := tenv.Env - state, err := ccdeploy.LoadOnchainState(e) - require.NoError(t, err) - - output, err := changeset.DeployPrerequisites(e, changeset.DeployPrerequisiteConfig{ - ChainSelectors: e.AllChainSelectors(), + output, err := changeset.DeployPrerequisites(tenv.Env, changeset.DeployPrerequisiteConfig{ + ChainSelectors: tenv.Env.AllChainSelectors(), }) + require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) - // Apply migration - output, err = changeset.InitialDeploy(e, ccdeploy.DeployCCIPContractConfig{ + // Deploy CCIP contracts + output, err = changeset.InitialDeploy(tenv.Env, ccdeploy.DeployCCIPContractConfig{ HomeChainSel: tenv.HomeChainSel, FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: e.AllChainSelectors(), + ChainsToDeploy: tenv.Env.AllChainSelectors(), TokenConfig: ccdeploy.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), + MCMSConfig: ccdeploy.NewTestMCMSConfig(t, tenv.Env), OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) - // Get new state after migration and mock USDC token deployment. - state, err = ccdeploy.LoadOnchainState(e) - require.NoError(t, err) - - srcToken, _, dstToken, _, err := ccdeploy.DeployTransferableToken( - lggr, - tenv.Env.Chains, - tenv.HomeChainSel, - tenv.FeedChainSel, - state, - e.ExistingAddresses, - "MY_TOKEN", - ) + require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) + state, err = ccdeploy.LoadOnchainState(tenv.Env) require.NoError(t, err) - // Ensure capreg logs are up to date. - ccdeploy.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) + // Replay logs + ccdeploy.ReplayLogs(t, tenv.Env.Offchain, tenv.ReplayBlocks) - // Apply the jobs. + // Apply the jobs for nodeID, jobs := range output.JobSpecs { for _, job := range jobs { - // Note these auto-accept - _, err := e.Offchain.ProposeJob(ctx, + _, err := tenv.Env.Offchain.ProposeJob(ccdeploy.Context(t), &jobv1.ProposeJobRequest{ NodeId: nodeID, Spec: job, @@ -169,104 +152,234 @@ func TestTokenTransfer(t *testing.T) { } // Add all lanes - require.NoError(t, ccdeploy.AddLanesForAll(e, state)) - // Need to keep track of the block number for each chain so that event subscription can be done from that block. - startBlocks := make(map[uint64]*uint64) - // Send a message from each chain to every other chain. - expectedSeqNum := make(map[uint64]uint64) - - twoCoins := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2)) - tx, err := srcToken.Mint( - e.Chains[tenv.HomeChainSel].DeployerKey, - e.Chains[tenv.HomeChainSel].DeployerKey.From, - new(big.Int).Mul(twoCoins, big.NewInt(10)), - ) - require.NoError(t, err) - _, err = e.Chains[tenv.HomeChainSel].Confirm(tx) - require.NoError(t, err) - - tx, err = dstToken.Mint( - e.Chains[tenv.FeedChainSel].DeployerKey, - e.Chains[tenv.FeedChainSel].DeployerKey.From, - new(big.Int).Mul(twoCoins, big.NewInt(10)), - ) - require.NoError(t, err) - _, err = e.Chains[tenv.FeedChainSel].Confirm(tx) - require.NoError(t, err) + require.NoError(t, ccdeploy.AddLanesForAll(tenv.Env, state)) + + // Deploy and approve tokens + srcToken1, dstToken1 := deployAndApproveTokens(t, tenv, state, "Token1") + srcToken2, dstToken2 := deployAndApproveTokens(t, tenv, state, "Token2") + + // Define your scenarios + scenarios := []struct { + name string + srcChain uint64 + dstChain uint64 + tokenAmounts []router.ClientEVMTokenAmount + receiver common.Address + data []byte + }{ + { + name: "Send token to EOA", + srcChain: tenv.HomeChainSel, + dstChain: tenv.FeedChainSel, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken1.Address(), + Amount: big.NewInt(1e18), + }, + }, + receiver: tenv.Env.Chains[tenv.FeedChainSel].DeployerKey.From, + data: []byte(""), + }, + { + name: "Send token to contract", + srcChain: tenv.HomeChainSel, // Will be set in the test + dstChain: tenv.FeedChainSel, // Will be set in the test + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken1.Address(), // Will be set in the test + Amount: big.NewInt(1e18), + }, + }, + receiver: state.Chains[tenv.FeedChainSel].Receiver.Address(), // Will be set in the test + data: []byte(""), + }, + { + name: "Send 2 tokens to receiver", + srcChain: tenv.HomeChainSel, + dstChain: tenv.FeedChainSel, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken1.Address(), + Amount: big.NewInt(1e18), + }, + { + Token: srcToken2.Address(), + Amount: big.NewInt(2e18), + }, + }, + receiver: tenv.Env.Chains[tenv.FeedChainSel].DeployerKey.From, + data: []byte(""), + }, + { + name: "Send N tokens to contract", + srcChain: tenv.HomeChainSel, + dstChain: tenv.FeedChainSel, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken1.Address(), + Amount: big.NewInt(1e18), + }, + { + Token: srcToken2.Address(), + Amount: big.NewInt(2e18), + }, + { + Token: srcToken1.Address(), + Amount: big.NewInt(3e18), + }, + }, + receiver: state.Chains[tenv.FeedChainSel].Receiver.Address(), + data: []byte(""), + }, + } - tx, err = srcToken.Approve(e.Chains[tenv.HomeChainSel].DeployerKey, state.Chains[tenv.HomeChainSel].Router.Address(), twoCoins) - require.NoError(t, err) - _, err = e.Chains[tenv.HomeChainSel].Confirm(tx) - require.NoError(t, err) - tx, err = dstToken.Approve(e.Chains[tenv.FeedChainSel].DeployerKey, state.Chains[tenv.FeedChainSel].Router.Address(), twoCoins) - require.NoError(t, err) - _, err = e.Chains[tenv.FeedChainSel].Confirm(tx) - require.NoError(t, err) + for _, scenario := range scenarios { + scenario := scenario // Capture range variable + t.Run(scenario.name, func(t *testing.T) { - tokens := map[uint64][]router.ClientEVMTokenAmount{ - tenv.HomeChainSel: {{ - Token: srcToken.Address(), - Amount: twoCoins, - }}, - tenv.FeedChainSel: {{ - Token: dstToken.Address(), - Amount: twoCoins, - }}, - } + // Prepare for message sending + startBlocks := make(map[uint64]*uint64) + expectedSeqNum := make(map[uint64]uint64) - for src := range e.Chains { - for dest, destChain := range e.Chains { - if src == dest { - continue - } + destChain := tenv.Env.Chains[scenario.dstChain] latesthdr, err := destChain.Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) block := latesthdr.Number.Uint64() - startBlocks[dest] = &block + startBlocks[scenario.dstChain] = &block + + // Fetch initial balances and aggregate total amounts + initialBalances := make(map[common.Address]*big.Int) + totalAmountsTransferred := make(map[common.Address]*big.Int) + + for _, tokenAmount := range scenario.tokenAmounts { + dstToken := getDestinationToken(scenario, tenv, srcToken1, dstToken1, srcToken2, dstToken2, tokenAmount.Token) + require.NotNil(t, dstToken, "Destination token not found") + + // Fetch initial balance + if _, exists := initialBalances[dstToken.Address()]; !exists { + balance, err := dstToken.BalanceOf(nil, scenario.receiver) + require.NoError(t, err) + initialBalances[dstToken.Address()] = balance + } + + // Initialize and sum up total amounts + if _, exists := totalAmountsTransferred[dstToken.Address()]; !exists { + totalAmountsTransferred[dstToken.Address()] = big.NewInt(0) + } + totalAmountsTransferred[dstToken.Address()].Add(totalAmountsTransferred[dstToken.Address()], tokenAmount.Amount) + } - var ( - receiver = common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32) - data = []byte("hello world") - feeToken = common.HexToAddress("0x0") - ) - if src == tenv.HomeChainSel && dest == tenv.FeedChainSel { - msgSentEvent := ccdeploy.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ - Receiver: receiver, - Data: data, - TokenAmounts: tokens[src], - FeeToken: feeToken, - ExtraArgs: nil, - }) - expectedSeqNum[dest] = msgSentEvent.SequenceNumber - } else { - msgSentEvent := ccdeploy.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ - Receiver: receiver, - Data: data, - TokenAmounts: nil, - FeeToken: feeToken, - ExtraArgs: nil, - }) - expectedSeqNum[dest] = msgSentEvent.SequenceNumber + // Prepare message + msg := router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(scenario.receiver.Bytes(), 32), + Data: scenario.data, + TokenAmounts: scenario.tokenAmounts, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, } - } + + // Send the message + msgSentEvent := ccdeploy.TestSendRequest(t, tenv.Env, state, scenario.srcChain, scenario.dstChain, false, msg) + expectedSeqNum[scenario.dstChain] = msgSentEvent.SequenceNumber + + // Wait for commit and execution + ccdeploy.ConfirmCommitForAllWithExpectedSeqNums(t, tenv.Env, state, expectedSeqNum, startBlocks) + ccdeploy.ConfirmExecWithSeqNrForAll(t, tenv.Env, state, expectedSeqNum, startBlocks) + + // Fetch final balances and assert + for tokenAddress, totalAmount := range totalAmountsTransferred { + dstToken := getTokenByAddress(dstToken1, dstToken2, tokenAddress) + require.NotNil(t, dstToken, "Destination token not found for address %s", tokenAddress.Hex()) + + finalBalance, err := dstToken.BalanceOf(nil, scenario.receiver) + require.NoError(t, err) + + initialBalance := initialBalances[dstToken.Address()] + expectedBalance := new(big.Int).Add(initialBalance, totalAmount) + + require.Equal(t, expectedBalance, finalBalance, "Incorrect balance for token %s", dstToken.Address().Hex()) + } + }) } +} - // Wait for all commit reports to land. - ccdeploy.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks) +func getTokenByAddress(dstToken1, dstToken2 *burn_mint_erc677.BurnMintERC677, tokenAddress common.Address) *burn_mint_erc677.BurnMintERC677 { + if dstToken1.Address() == tokenAddress { + return dstToken1 + } else if dstToken2.Address() == tokenAddress { + return dstToken2 + } + return nil +} - // After commit is reported on all chains, token prices should be updated in FeeQuoter. - for dest := range e.Chains { - linkAddress := state.Chains[dest].LinkToken.Address() - feeQuoter := state.Chains[dest].FeeQuoter - timestampedPrice, err := feeQuoter.GetTokenPrice(nil, linkAddress) - require.NoError(t, err) - require.Equal(t, ccdeploy.MockLinkPrice, timestampedPrice.Value) +// Helper function to determine the destination token +func getDestinationToken(scenario struct { + name string + srcChain uint64 + dstChain uint64 + tokenAmounts []router.ClientEVMTokenAmount + receiver common.Address + data []byte +}, tenv ccdeploy.DeployedEnv, srcToken1, dstToken1, srcToken2, dstToken2 *burn_mint_erc677.BurnMintERC677, tokenAddress common.Address) *burn_mint_erc677.BurnMintERC677 { + if scenario.srcChain == tenv.HomeChainSel && scenario.dstChain == tenv.FeedChainSel { + if tokenAddress == srcToken1.Address() { + return dstToken1 + } else if tokenAddress == srcToken2.Address() { + return dstToken2 + } + } else if scenario.srcChain == tenv.FeedChainSel && scenario.dstChain == tenv.HomeChainSel { + if tokenAddress == dstToken1.Address() { + return srcToken1 + } else if tokenAddress == dstToken2.Address() { + return srcToken2 + } } + return nil +} - // Wait for all exec reports to land - ccdeploy.ConfirmExecWithSeqNrForAll(t, e, state, expectedSeqNum, startBlocks) +func setupEnvironment(t *testing.T) (ccdeploy.DeployedEnv, ccdeploy.CCIPOnChainState) { + lggr := logger.TestLogger(t) + tenv := ccdeploy.NewMemoryEnvironment(t, lggr, 2, 4, ccdeploy.MockLinkPrice, ccdeploy.MockWethPrice) + state, err := ccdeploy.LoadOnchainState(tenv.Env) + require.NoError(t, err) + return tenv, state +} + +func deployAndApproveTokens(t *testing.T, e ccdeploy.DeployedEnv, state ccdeploy.CCIPOnChainState, tokenName string) (*burn_mint_erc677.BurnMintERC677, *burn_mint_erc677.BurnMintERC677) { + srcToken, _, dstToken, _, err := ccdeploy.DeployTransferableToken( + logger.TestLogger(t), + e.Env.Chains, + e.HomeChainSel, + e.FeedChainSel, + state, + e.Env.ExistingAddresses, + tokenName, + ) + require.NoError(t, err) + + tenTokens := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(10)) + mintAndApprove(t, e, state, srcToken, e.HomeChainSel, tenTokens) + mintAndApprove(t, e, state, dstToken, e.FeedChainSel, tenTokens) + + return srcToken, dstToken +} - balance, err := dstToken.BalanceOf(nil, state.Chains[tenv.FeedChainSel].Receiver.Address()) +func mintAndApprove(t *testing.T, e ccdeploy.DeployedEnv, state ccdeploy.CCIPOnChainState, token *burn_mint_erc677.BurnMintERC677, chainSel uint64, amount *big.Int) { + tx, err := token.Mint( + e.Env.Chains[chainSel].DeployerKey, + e.Env.Chains[chainSel].DeployerKey.From, + amount, + ) + require.NoError(t, err) + _, err = e.Env.Chains[chainSel].Confirm(tx) + require.NoError(t, err) + + tx, err = token.Approve( + e.Env.Chains[chainSel].DeployerKey, + state.Chains[chainSel].Router.Address(), + amount, + ) + require.NoError(t, err) + _, err = e.Env.Chains[chainSel].Confirm(tx) require.NoError(t, err) - require.Equal(t, twoCoins, balance) }