Skip to content

Commit

Permalink
CCIP-3591 USDC transfer with multi source setup (#15305)
Browse files Browse the repository at this point in the history
* Multisource transfer

* Added status checks

* Use the same setup instead of separate ones (aka deploy once)

* Different naming and topology

* Different naming and topology
  • Loading branch information
mateusz-sekara authored Nov 22, 2024
1 parent 7b3ef94 commit 6afc0d2
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .github/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ runner-test-matrix:
test_cmd: cd integration-tests/smoke/ccip && go test ccip_usdc_test.go -timeout 18m -test.parallel=1 -count=1 -json
pyroscope_env: ci-smoke-ccipv1_6-evm-simulated
test_env_vars:
E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2
E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3
E2E_JD_VERSION: 0.6.0

- id: smoke/ccip/fee_boosting_test.go:*
Expand Down
9 changes: 9 additions & 0 deletions deployment/ccip/changeset/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,15 @@ func attachTokenToTheRegistry(
token common.Address,
tokenPool common.Address,
) error {
pool, err := state.TokenAdminRegistry.GetPool(nil, token)
if err != nil {
return err
}
// Pool is already registered, don't reattach it, because it would cause revert
if pool != (common.Address{}) {
return nil
}

tx, err := state.RegistryModule.RegisterAdminViaOwner(owner, token)
if err != nil {
return err
Expand Down
165 changes: 117 additions & 48 deletions integration-tests/smoke/ccip/ccip_usdc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,49 @@ import (
"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/integration-tests/testsetups"

"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677"
"github.com/smartcontractkit/chainlink/v2/core/logger"
)

/*
* Chain topology for this test
* chainA (USDC, MY_TOKEN)
* |
* | ------- chainC (USDC, MY_TOKEN)
* |
* chainB (USDC)
*/
func TestUSDCTokenTransfer(t *testing.T) {
lggr := logger.TestLogger(t)
config := &changeset.TestConfigs{
IsUSDC: true,
}
tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, config)
//tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 2, 4, config)
//tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 3, 4, config)

e := tenv.Env
state, err := changeset.LoadOnchainState(e)
require.NoError(t, err)

allChainSelectors := maps.Keys(e.Chains)
sourceChain := allChainSelectors[0]
destChain := allChainSelectors[1]
chainA := allChainSelectors[0]
chainC := allChainSelectors[1]
chainB := allChainSelectors[2]

aChainUSDC, cChainUSDC, err := changeset.ConfigureUSDCTokenPools(lggr, e.Chains, chainA, chainC, state)
require.NoError(t, err)

srcUSDC, dstUSDC, err := changeset.ConfigureUSDCTokenPools(lggr, e.Chains, sourceChain, destChain, state)
bChainUSDC, _, err := changeset.ConfigureUSDCTokenPools(lggr, e.Chains, chainB, chainC, state)
require.NoError(t, err)

srcToken, _, dstToken, _, err := changeset.DeployTransferableToken(
aChainToken, _, cChainToken, _, err := changeset.DeployTransferableToken(
lggr,
tenv.Env.Chains,
sourceChain,
destChain,
chainA,
chainC,
state,
e.ExistingAddresses,
"MY_TOKEN",
Expand All @@ -58,97 +70,106 @@ func TestUSDCTokenTransfer(t *testing.T) {
require.NoError(t, changeset.AddLanesForAll(e, state))

mintAndAllow(t, e, state, map[uint64][]*burn_mint_erc677.BurnMintERC677{
sourceChain: {srcUSDC, srcToken},
destChain: {dstUSDC, dstToken},
chainA: {aChainUSDC, aChainToken},
chainB: {bChainUSDC},
chainC: {cChainUSDC, cChainToken},
})

err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[sourceChain], state.Chains[sourceChain], destChain, srcUSDC)
err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainA], state.Chains[chainA], chainC, aChainUSDC)
require.NoError(t, err)

err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[destChain], state.Chains[destChain], sourceChain, dstUSDC)
err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainB], state.Chains[chainB], chainC, bChainUSDC)
require.NoError(t, err)

err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainC], state.Chains[chainC], chainA, cChainUSDC)
require.NoError(t, err)

// MockE2EUSDCTransmitter always mint 1, see MockE2EUSDCTransmitter.sol for more details
tinyOneCoin := new(big.Int).SetUint64(1)

tcs := []struct {
name string
receiver common.Address
sourceChain uint64
destChain uint64
tokens []router.ClientEVMTokenAmount
data []byte
expectedTokenBalances map[common.Address]*big.Int
name string
receiver common.Address
sourceChain uint64
destChain uint64
tokens []router.ClientEVMTokenAmount
data []byte
expectedTokenBalances map[common.Address]*big.Int
expectedExecutionState int
}{
{
name: "single USDC token transfer to EOA",
receiver: utils.RandomAddress(),
sourceChain: destChain,
destChain: sourceChain,
sourceChain: chainC,
destChain: chainA,
tokens: []router.ClientEVMTokenAmount{
{
Token: dstUSDC.Address(),
Token: cChainUSDC.Address(),
Amount: tinyOneCoin,
}},
expectedTokenBalances: map[common.Address]*big.Int{
srcUSDC.Address(): tinyOneCoin,
aChainUSDC.Address(): tinyOneCoin,
},
expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS,
},
{
name: "multiple USDC tokens within the same message",
receiver: utils.RandomAddress(),
sourceChain: destChain,
destChain: sourceChain,
sourceChain: chainC,
destChain: chainA,
tokens: []router.ClientEVMTokenAmount{
{
Token: dstUSDC.Address(),
Token: cChainUSDC.Address(),
Amount: tinyOneCoin,
},
{
Token: dstUSDC.Address(),
Token: cChainUSDC.Address(),
Amount: tinyOneCoin,
},
},
expectedTokenBalances: map[common.Address]*big.Int{
// 2 coins because of the same receiver
srcUSDC.Address(): new(big.Int).Add(tinyOneCoin, tinyOneCoin),
aChainUSDC.Address(): new(big.Int).Add(tinyOneCoin, tinyOneCoin),
},
expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS,
},
{
name: "USDC token together with another token transferred to EOA",
receiver: utils.RandomAddress(),
sourceChain: sourceChain,
destChain: destChain,
sourceChain: chainA,
destChain: chainC,
tokens: []router.ClientEVMTokenAmount{
{
Token: srcUSDC.Address(),
Token: aChainUSDC.Address(),
Amount: tinyOneCoin,
},
{
Token: srcToken.Address(),
Token: aChainToken.Address(),
Amount: new(big.Int).Mul(tinyOneCoin, big.NewInt(10)),
},
},
expectedTokenBalances: map[common.Address]*big.Int{
dstUSDC.Address(): tinyOneCoin,
dstToken.Address(): new(big.Int).Mul(tinyOneCoin, big.NewInt(10)),
cChainUSDC.Address(): tinyOneCoin,
cChainToken.Address(): new(big.Int).Mul(tinyOneCoin, big.NewInt(10)),
},
expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS,
},
{
name: "programmable token transfer to valid contract receiver",
receiver: state.Chains[destChain].Receiver.Address(),
sourceChain: sourceChain,
destChain: destChain,
receiver: state.Chains[chainC].Receiver.Address(),
sourceChain: chainA,
destChain: chainC,
tokens: []router.ClientEVMTokenAmount{
{
Token: srcUSDC.Address(),
Token: aChainUSDC.Address(),
Amount: tinyOneCoin,
},
},
data: []byte("hello world"),
expectedTokenBalances: map[common.Address]*big.Int{
dstUSDC.Address(): tinyOneCoin,
cChainUSDC.Address(): tinyOneCoin,
},
expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS,
},
}

Expand All @@ -169,6 +190,7 @@ func TestUSDCTokenTransfer(t *testing.T) {
tt.tokens,
tt.receiver,
tt.data,
tt.expectedExecutionState,
)

for token, balance := range tt.expectedTokenBalances {
Expand All @@ -177,6 +199,52 @@ func TestUSDCTokenTransfer(t *testing.T) {
}
})
}

t.Run("multi-source USDC transfer targeting the same dest receiver", func(t *testing.T) {
sendSingleTokenTransfer := func(source, dest uint64, token common.Address, receiver common.Address) (*onramp.OnRampCCIPMessageSent, changeset.SourceDestPair) {
msg := changeset.TestSendRequest(t, e, state, source, dest, false, router.ClientEVM2AnyMessage{
Receiver: common.LeftPadBytes(receiver.Bytes(), 32),
Data: []byte{},
TokenAmounts: []router.ClientEVMTokenAmount{{Token: token, Amount: tinyOneCoin}},
FeeToken: common.HexToAddress("0x0"),
ExtraArgs: nil,
})
return msg, changeset.SourceDestPair{
SourceChainSelector: source,
DestChainSelector: dest,
}
}

receiver := utils.RandomAddress()

startBlocks := make(map[uint64]*uint64)
expectedSeqNum := make(map[changeset.SourceDestPair]uint64)
expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64)

latesthdr, err := e.Chains[chainC].Client.HeaderByNumber(testcontext.Get(t), nil)
require.NoError(t, err)
block := latesthdr.Number.Uint64()
startBlocks[chainC] = &block

message1, message1ID := sendSingleTokenTransfer(chainA, chainC, aChainUSDC.Address(), receiver)
expectedSeqNum[message1ID] = message1.SequenceNumber
expectedSeqNumExec[message1ID] = []uint64{message1.SequenceNumber}

message2, message2ID := sendSingleTokenTransfer(chainB, chainC, bChainUSDC.Address(), receiver)
expectedSeqNum[message2ID] = message2.SequenceNumber
expectedSeqNumExec[message2ID] = []uint64{message2.SequenceNumber}

changeset.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks)
states := changeset.ConfirmExecWithSeqNrsForAll(t, e, state, expectedSeqNumExec, startBlocks)

require.Equal(t, changeset.EXECUTION_STATE_SUCCESS, states[message1ID][message1.SequenceNumber])
require.Equal(t, changeset.EXECUTION_STATE_SUCCESS, states[message2ID][message2.SequenceNumber])

// We sent 1 coin from each source chain, so we should have 2 coins on the destination chain
// Receiver is randomly generated so we don't need to get the initial balance first
expectedBalance := new(big.Int).Add(tinyOneCoin, tinyOneCoin)
waitForTheTokenBalance(t, cChainUSDC.Address(), receiver, e.Chains[chainC], expectedBalance)
})
}

// mintAndAllow mints tokens for deployers and allow router to spend them
Expand Down Expand Up @@ -216,7 +284,13 @@ func transferAndWaitForSuccess(
tokens []router.ClientEVMTokenAmount,
receiver common.Address,
data []byte,
expectedStatus int,
) {
identifier := changeset.SourceDestPair{
SourceChainSelector: sourceChain,
DestChainSelector: destChain,
}

startBlocks := make(map[uint64]*uint64)
expectedSeqNum := make(map[changeset.SourceDestPair]uint64)
expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64)
Expand All @@ -233,20 +307,15 @@ func transferAndWaitForSuccess(
FeeToken: common.HexToAddress("0x0"),
ExtraArgs: nil,
})
expectedSeqNum[changeset.SourceDestPair{
SourceChainSelector: sourceChain,
DestChainSelector: destChain,
}] = msgSentEvent.SequenceNumber
expectedSeqNumExec[changeset.SourceDestPair{
SourceChainSelector: sourceChain,
DestChainSelector: destChain,
}] = []uint64{msgSentEvent.SequenceNumber}
expectedSeqNum[identifier] = msgSentEvent.SequenceNumber
expectedSeqNumExec[identifier] = []uint64{msgSentEvent.SequenceNumber}

// Wait for all commit reports to land.
changeset.ConfirmCommitForAllWithExpectedSeqNums(t, env, state, expectedSeqNum, startBlocks)

// Wait for all exec reports to land
changeset.ConfirmExecWithSeqNrsForAll(t, env, state, expectedSeqNumExec, startBlocks)
states := changeset.ConfirmExecWithSeqNrsForAll(t, env, state, expectedSeqNumExec, startBlocks)
require.Equal(t, expectedStatus, states[identifier][msgSentEvent.SequenceNumber])
}

func waitForTheTokenBalance(
Expand Down

0 comments on commit 6afc0d2

Please sign in to comment.