Skip to content
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

CCIP-3591 USDC transfer with multi source setup #15305

Merged
merged 5 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading