Skip to content

Commit 6afc0d2

Browse files
CCIP-3591 USDC transfer with multi source setup (smartcontractkit#15305)
* Multisource transfer * Added status checks * Use the same setup instead of separate ones (aka deploy once) * Different naming and topology * Different naming and topology
1 parent 7b3ef94 commit 6afc0d2

File tree

3 files changed

+127
-49
lines changed

3 files changed

+127
-49
lines changed

.github/e2e-tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ runner-test-matrix:
984984
test_cmd: cd integration-tests/smoke/ccip && go test ccip_usdc_test.go -timeout 18m -test.parallel=1 -count=1 -json
985985
pyroscope_env: ci-smoke-ccipv1_6-evm-simulated
986986
test_env_vars:
987-
E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2
987+
E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3
988988
E2E_JD_VERSION: 0.6.0
989989

990990
- id: smoke/ccip/fee_boosting_test.go:*

deployment/ccip/changeset/test_helpers.go

+9
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,15 @@ func attachTokenToTheRegistry(
857857
token common.Address,
858858
tokenPool common.Address,
859859
) error {
860+
pool, err := state.TokenAdminRegistry.GetPool(nil, token)
861+
if err != nil {
862+
return err
863+
}
864+
// Pool is already registered, don't reattach it, because it would cause revert
865+
if pool != (common.Address{}) {
866+
return nil
867+
}
868+
860869
tx, err := state.RegistryModule.RegisterAdminViaOwner(owner, token)
861870
if err != nil {
862871
return err

integration-tests/smoke/ccip/ccip_usdc_test.go

+117-48
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,49 @@ import (
1717
"github.com/smartcontractkit/chainlink/deployment"
1818
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
1919
"github.com/smartcontractkit/chainlink/integration-tests/testsetups"
20-
2120
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils"
21+
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp"
2222
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
2323
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677"
2424
"github.com/smartcontractkit/chainlink/v2/core/logger"
2525
)
2626

27+
/*
28+
* Chain topology for this test
29+
* chainA (USDC, MY_TOKEN)
30+
* |
31+
* | ------- chainC (USDC, MY_TOKEN)
32+
* |
33+
* chainB (USDC)
34+
*/
2735
func TestUSDCTokenTransfer(t *testing.T) {
2836
lggr := logger.TestLogger(t)
2937
config := &changeset.TestConfigs{
3038
IsUSDC: true,
3139
}
3240
tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, config)
33-
//tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 2, 4, config)
41+
//tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 3, 4, config)
3442

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

3947
allChainSelectors := maps.Keys(e.Chains)
40-
sourceChain := allChainSelectors[0]
41-
destChain := allChainSelectors[1]
48+
chainA := allChainSelectors[0]
49+
chainC := allChainSelectors[1]
50+
chainB := allChainSelectors[2]
51+
52+
aChainUSDC, cChainUSDC, err := changeset.ConfigureUSDCTokenPools(lggr, e.Chains, chainA, chainC, state)
53+
require.NoError(t, err)
4254

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

46-
srcToken, _, dstToken, _, err := changeset.DeployTransferableToken(
58+
aChainToken, _, cChainToken, _, err := changeset.DeployTransferableToken(
4759
lggr,
4860
tenv.Env.Chains,
49-
sourceChain,
50-
destChain,
61+
chainA,
62+
chainC,
5163
state,
5264
e.ExistingAddresses,
5365
"MY_TOKEN",
@@ -58,97 +70,106 @@ func TestUSDCTokenTransfer(t *testing.T) {
5870
require.NoError(t, changeset.AddLanesForAll(e, state))
5971

6072
mintAndAllow(t, e, state, map[uint64][]*burn_mint_erc677.BurnMintERC677{
61-
sourceChain: {srcUSDC, srcToken},
62-
destChain: {dstUSDC, dstToken},
73+
chainA: {aChainUSDC, aChainToken},
74+
chainB: {bChainUSDC},
75+
chainC: {cChainUSDC, cChainToken},
6376
})
6477

65-
err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[sourceChain], state.Chains[sourceChain], destChain, srcUSDC)
78+
err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainA], state.Chains[chainA], chainC, aChainUSDC)
6679
require.NoError(t, err)
6780

68-
err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[destChain], state.Chains[destChain], sourceChain, dstUSDC)
81+
err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainB], state.Chains[chainB], chainC, bChainUSDC)
82+
require.NoError(t, err)
83+
84+
err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainC], state.Chains[chainC], chainA, cChainUSDC)
6985
require.NoError(t, err)
7086

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

7490
tcs := []struct {
75-
name string
76-
receiver common.Address
77-
sourceChain uint64
78-
destChain uint64
79-
tokens []router.ClientEVMTokenAmount
80-
data []byte
81-
expectedTokenBalances map[common.Address]*big.Int
91+
name string
92+
receiver common.Address
93+
sourceChain uint64
94+
destChain uint64
95+
tokens []router.ClientEVMTokenAmount
96+
data []byte
97+
expectedTokenBalances map[common.Address]*big.Int
98+
expectedExecutionState int
8299
}{
83100
{
84101
name: "single USDC token transfer to EOA",
85102
receiver: utils.RandomAddress(),
86-
sourceChain: destChain,
87-
destChain: sourceChain,
103+
sourceChain: chainC,
104+
destChain: chainA,
88105
tokens: []router.ClientEVMTokenAmount{
89106
{
90-
Token: dstUSDC.Address(),
107+
Token: cChainUSDC.Address(),
91108
Amount: tinyOneCoin,
92109
}},
93110
expectedTokenBalances: map[common.Address]*big.Int{
94-
srcUSDC.Address(): tinyOneCoin,
111+
aChainUSDC.Address(): tinyOneCoin,
95112
},
113+
expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS,
96114
},
97115
{
98116
name: "multiple USDC tokens within the same message",
99117
receiver: utils.RandomAddress(),
100-
sourceChain: destChain,
101-
destChain: sourceChain,
118+
sourceChain: chainC,
119+
destChain: chainA,
102120
tokens: []router.ClientEVMTokenAmount{
103121
{
104-
Token: dstUSDC.Address(),
122+
Token: cChainUSDC.Address(),
105123
Amount: tinyOneCoin,
106124
},
107125
{
108-
Token: dstUSDC.Address(),
126+
Token: cChainUSDC.Address(),
109127
Amount: tinyOneCoin,
110128
},
111129
},
112130
expectedTokenBalances: map[common.Address]*big.Int{
113131
// 2 coins because of the same receiver
114-
srcUSDC.Address(): new(big.Int).Add(tinyOneCoin, tinyOneCoin),
132+
aChainUSDC.Address(): new(big.Int).Add(tinyOneCoin, tinyOneCoin),
115133
},
134+
expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS,
116135
},
117136
{
118137
name: "USDC token together with another token transferred to EOA",
119138
receiver: utils.RandomAddress(),
120-
sourceChain: sourceChain,
121-
destChain: destChain,
139+
sourceChain: chainA,
140+
destChain: chainC,
122141
tokens: []router.ClientEVMTokenAmount{
123142
{
124-
Token: srcUSDC.Address(),
143+
Token: aChainUSDC.Address(),
125144
Amount: tinyOneCoin,
126145
},
127146
{
128-
Token: srcToken.Address(),
147+
Token: aChainToken.Address(),
129148
Amount: new(big.Int).Mul(tinyOneCoin, big.NewInt(10)),
130149
},
131150
},
132151
expectedTokenBalances: map[common.Address]*big.Int{
133-
dstUSDC.Address(): tinyOneCoin,
134-
dstToken.Address(): new(big.Int).Mul(tinyOneCoin, big.NewInt(10)),
152+
cChainUSDC.Address(): tinyOneCoin,
153+
cChainToken.Address(): new(big.Int).Mul(tinyOneCoin, big.NewInt(10)),
135154
},
155+
expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS,
136156
},
137157
{
138158
name: "programmable token transfer to valid contract receiver",
139-
receiver: state.Chains[destChain].Receiver.Address(),
140-
sourceChain: sourceChain,
141-
destChain: destChain,
159+
receiver: state.Chains[chainC].Receiver.Address(),
160+
sourceChain: chainA,
161+
destChain: chainC,
142162
tokens: []router.ClientEVMTokenAmount{
143163
{
144-
Token: srcUSDC.Address(),
164+
Token: aChainUSDC.Address(),
145165
Amount: tinyOneCoin,
146166
},
147167
},
148168
data: []byte("hello world"),
149169
expectedTokenBalances: map[common.Address]*big.Int{
150-
dstUSDC.Address(): tinyOneCoin,
170+
cChainUSDC.Address(): tinyOneCoin,
151171
},
172+
expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS,
152173
},
153174
}
154175

@@ -169,6 +190,7 @@ func TestUSDCTokenTransfer(t *testing.T) {
169190
tt.tokens,
170191
tt.receiver,
171192
tt.data,
193+
tt.expectedExecutionState,
172194
)
173195

174196
for token, balance := range tt.expectedTokenBalances {
@@ -177,6 +199,52 @@ func TestUSDCTokenTransfer(t *testing.T) {
177199
}
178200
})
179201
}
202+
203+
t.Run("multi-source USDC transfer targeting the same dest receiver", func(t *testing.T) {
204+
sendSingleTokenTransfer := func(source, dest uint64, token common.Address, receiver common.Address) (*onramp.OnRampCCIPMessageSent, changeset.SourceDestPair) {
205+
msg := changeset.TestSendRequest(t, e, state, source, dest, false, router.ClientEVM2AnyMessage{
206+
Receiver: common.LeftPadBytes(receiver.Bytes(), 32),
207+
Data: []byte{},
208+
TokenAmounts: []router.ClientEVMTokenAmount{{Token: token, Amount: tinyOneCoin}},
209+
FeeToken: common.HexToAddress("0x0"),
210+
ExtraArgs: nil,
211+
})
212+
return msg, changeset.SourceDestPair{
213+
SourceChainSelector: source,
214+
DestChainSelector: dest,
215+
}
216+
}
217+
218+
receiver := utils.RandomAddress()
219+
220+
startBlocks := make(map[uint64]*uint64)
221+
expectedSeqNum := make(map[changeset.SourceDestPair]uint64)
222+
expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64)
223+
224+
latesthdr, err := e.Chains[chainC].Client.HeaderByNumber(testcontext.Get(t), nil)
225+
require.NoError(t, err)
226+
block := latesthdr.Number.Uint64()
227+
startBlocks[chainC] = &block
228+
229+
message1, message1ID := sendSingleTokenTransfer(chainA, chainC, aChainUSDC.Address(), receiver)
230+
expectedSeqNum[message1ID] = message1.SequenceNumber
231+
expectedSeqNumExec[message1ID] = []uint64{message1.SequenceNumber}
232+
233+
message2, message2ID := sendSingleTokenTransfer(chainB, chainC, bChainUSDC.Address(), receiver)
234+
expectedSeqNum[message2ID] = message2.SequenceNumber
235+
expectedSeqNumExec[message2ID] = []uint64{message2.SequenceNumber}
236+
237+
changeset.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks)
238+
states := changeset.ConfirmExecWithSeqNrsForAll(t, e, state, expectedSeqNumExec, startBlocks)
239+
240+
require.Equal(t, changeset.EXECUTION_STATE_SUCCESS, states[message1ID][message1.SequenceNumber])
241+
require.Equal(t, changeset.EXECUTION_STATE_SUCCESS, states[message2ID][message2.SequenceNumber])
242+
243+
// We sent 1 coin from each source chain, so we should have 2 coins on the destination chain
244+
// Receiver is randomly generated so we don't need to get the initial balance first
245+
expectedBalance := new(big.Int).Add(tinyOneCoin, tinyOneCoin)
246+
waitForTheTokenBalance(t, cChainUSDC.Address(), receiver, e.Chains[chainC], expectedBalance)
247+
})
180248
}
181249

182250
// mintAndAllow mints tokens for deployers and allow router to spend them
@@ -216,7 +284,13 @@ func transferAndWaitForSuccess(
216284
tokens []router.ClientEVMTokenAmount,
217285
receiver common.Address,
218286
data []byte,
287+
expectedStatus int,
219288
) {
289+
identifier := changeset.SourceDestPair{
290+
SourceChainSelector: sourceChain,
291+
DestChainSelector: destChain,
292+
}
293+
220294
startBlocks := make(map[uint64]*uint64)
221295
expectedSeqNum := make(map[changeset.SourceDestPair]uint64)
222296
expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64)
@@ -233,20 +307,15 @@ func transferAndWaitForSuccess(
233307
FeeToken: common.HexToAddress("0x0"),
234308
ExtraArgs: nil,
235309
})
236-
expectedSeqNum[changeset.SourceDestPair{
237-
SourceChainSelector: sourceChain,
238-
DestChainSelector: destChain,
239-
}] = msgSentEvent.SequenceNumber
240-
expectedSeqNumExec[changeset.SourceDestPair{
241-
SourceChainSelector: sourceChain,
242-
DestChainSelector: destChain,
243-
}] = []uint64{msgSentEvent.SequenceNumber}
310+
expectedSeqNum[identifier] = msgSentEvent.SequenceNumber
311+
expectedSeqNumExec[identifier] = []uint64{msgSentEvent.SequenceNumber}
244312

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

248316
// Wait for all exec reports to land
249-
changeset.ConfirmExecWithSeqNrsForAll(t, env, state, expectedSeqNumExec, startBlocks)
317+
states := changeset.ConfirmExecWithSeqNrsForAll(t, env, state, expectedSeqNumExec, startBlocks)
318+
require.Equal(t, expectedStatus, states[identifier][msgSentEvent.SequenceNumber])
250319
}
251320

252321
func waitForTheTokenBalance(

0 commit comments

Comments
 (0)