Skip to content

Commit f2c9ee9

Browse files
authored
Aptos msghasher.go tests (#18699)
* Aptos msghasher.go tests * Add tests to CI
1 parent ebbb205 commit f2c9ee9

7 files changed

Lines changed: 294 additions & 7 deletions

File tree

.github/e2e-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1251,7 +1251,7 @@ runner-test-matrix:
12511251
runs_on: ubuntu24.04-16cores-64GB
12521252
runs_on_self_hosted: runs-on/cpu=32/ram=128/family=m6i/spot=false/image=ubuntu24-full-x64/extras=s3-cache+tmpfs
12531253
triggers:
1254-
- PR E2E CCIP Tests
1254+
- PR E2E CCIP Tests
12551255
- Nightly E2E Tests
12561256
- Push E2E CCIP Tests
12571257
- Workflow Dispatch E2E CCIP Tests

.github/integration-in-memory-tests.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,18 @@ runner-test-matrix:
484484
aptos_cli_version: 7.2.0
485485
install_plugins_public: true
486486

487+
- id: smoke/ccip/ccip_aptos_msghasher_test.go:*
488+
path: integration-tests/smoke/ccip/ccip_aptos_msghasher_test.go
489+
test_env_type: in-memory
490+
runs_on: ubuntu-latest
491+
triggers:
492+
- PR Integration CCIP Tests
493+
- Nightly Integration CCIP Tests
494+
test_cmd: cd smoke/ccip && go test ccip_aptos_msghasher_test.go -run "Test_CCIP_AptosMessageHasher_OnChainVerification" -timeout 20m -test.parallel=1 -count=1 -json
495+
test_go_project_path: integration-tests
496+
aptos_cli_version: 7.2.0
497+
install_plugins_public: true
498+
487499
# TON tests
488500
- id: smoke/ccip/ccip_ton_messaging_test.go:*
489501
path: integration-tests/smoke/ccip/ccip_ton_messaging_test.go

integration-tests/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ require (
4545
github.com/shopspring/decimal v1.4.0
4646
github.com/slack-go/slack v0.15.0
4747
github.com/smartcontractkit/chain-selectors v1.0.62
48-
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250626122206-319db248496a
48+
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250718185907-0123e1ba03af
4949
github.com/smartcontractkit/chainlink-automation v0.8.1
5050
github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20250716191209-5639135b7716
5151
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250609091505-5c8cd74b92ed

integration-tests/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,8 +1515,8 @@ github.com/smartcontractkit/ccip-owner-contracts v0.1.0 h1:GiBDtlx7539o7AKlDV+9L
15151515
github.com/smartcontractkit/ccip-owner-contracts v0.1.0/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY=
15161516
github.com/smartcontractkit/chain-selectors v1.0.62 h1:KWLEyKQXHxGGHIlUfLrzjYldlB8hBjRZi9GP49WtgYs=
15171517
github.com/smartcontractkit/chain-selectors v1.0.62/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8=
1518-
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250626122206-319db248496a h1:45A1juQPvLV4xDt35wPiNpdzQwg2uGqbLQtHXLhZjWw=
1519-
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250626122206-319db248496a/go.mod h1:+LMKso9gI5B78xdk3uP69/WrsJTcalbKFO63amJexsE=
1518+
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250718185907-0123e1ba03af h1:cbUeLLst0gVfNgtNWD7EiEy5NBuD/NmhUVTRXbtEICc=
1519+
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250718185907-0123e1ba03af/go.mod h1:+LMKso9gI5B78xdk3uP69/WrsJTcalbKFO63amJexsE=
15201520
github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU=
15211521
github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08=
15221522
github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20250716191209-5639135b7716 h1:fT6iLsksC2bGjqlfOl2BYQ2/fHPxF8WeIEHEn5mfWQQ=

integration-tests/load/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ require (
459459
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect
460460
github.com/sirupsen/logrus v1.9.3 // indirect
461461
github.com/smartcontractkit/ccip-owner-contracts v0.1.0 // indirect
462-
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250626122206-319db248496a // indirect
462+
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250718185907-0123e1ba03af // indirect
463463
github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect
464464
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250728091106-f398970aa4bc // indirect
465465
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.1 // indirect

integration-tests/load/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,8 +1491,8 @@ github.com/smartcontractkit/ccip-owner-contracts v0.1.0 h1:GiBDtlx7539o7AKlDV+9L
14911491
github.com/smartcontractkit/ccip-owner-contracts v0.1.0/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY=
14921492
github.com/smartcontractkit/chain-selectors v1.0.62 h1:KWLEyKQXHxGGHIlUfLrzjYldlB8hBjRZi9GP49WtgYs=
14931493
github.com/smartcontractkit/chain-selectors v1.0.62/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8=
1494-
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250626122206-319db248496a h1:45A1juQPvLV4xDt35wPiNpdzQwg2uGqbLQtHXLhZjWw=
1495-
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250626122206-319db248496a/go.mod h1:+LMKso9gI5B78xdk3uP69/WrsJTcalbKFO63amJexsE=
1494+
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250718185907-0123e1ba03af h1:cbUeLLst0gVfNgtNWD7EiEy5NBuD/NmhUVTRXbtEICc=
1495+
github.com/smartcontractkit/chainlink-aptos v0.0.0-20250718185907-0123e1ba03af/go.mod h1:+LMKso9gI5B78xdk3uP69/WrsJTcalbKFO63amJexsE=
14961496
github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU=
14971497
github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08=
14981498
github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20250716191209-5639135b7716 h1:fT6iLsksC2bGjqlfOl2BYQ2/fHPxF8WeIEHEn5mfWQQ=
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
package ccip
2+
3+
import (
4+
"context"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/aptos-labs/aptos-go-sdk"
9+
chain_selectors "github.com/smartcontractkit/chain-selectors"
10+
11+
"github.com/ethereum/go-ethereum/common/hexutil"
12+
"github.com/stretchr/testify/require"
13+
14+
ccipocr3common "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"
15+
"github.com/smartcontractkit/chainlink-deployments-framework/chain"
16+
"github.com/smartcontractkit/chainlink/v2/core/logger"
17+
18+
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipaptos"
19+
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm"
20+
ccipcommon "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common"
21+
22+
"github.com/smartcontractkit/chainlink-evm/pkg/utils"
23+
24+
aptos_call_opts "github.com/smartcontractkit/chainlink-aptos/bindings/bind"
25+
aptos_ccip_offramp "github.com/smartcontractkit/chainlink-aptos/bindings/ccip_offramp/offramp"
26+
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
27+
"github.com/smartcontractkit/chainlink/deployment/ccip/shared/stateview"
28+
aptosstate "github.com/smartcontractkit/chainlink/deployment/ccip/shared/stateview/aptos"
29+
testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
30+
)
31+
32+
// Test_CCIP_AptosMessageHasher_OnChainVerification compares off-chain aptos msghasher.go implementation
33+
// with on-chain Aptos Move offramp::calculate_message_hash()
34+
func Test_CCIP_AptosMessageHasher_OnChainVerification(t *testing.T) {
35+
lggr := logger.TestLogger(t)
36+
ctx := testhelpers.Context(t)
37+
38+
e, _, _ := testsetups.NewIntegrationEnvironment(
39+
t,
40+
testhelpers.WithNumOfChains(2),
41+
testhelpers.WithAptosChains(1),
42+
)
43+
44+
// Deploy CCIP contracts and load state
45+
state, err := stateview.LoadOnchainState(e.Env)
46+
require.NoError(t, err)
47+
48+
// Get chain selectors
49+
evmChainSelectors := e.Env.BlockChains.ListChainSelectors(chain.WithFamily(chain_selectors.FamilyEVM))
50+
aptosChainSelectors := e.Env.BlockChains.ListChainSelectors(chain.WithFamily(chain_selectors.FamilyAptos))
51+
52+
sourceChain := evmChainSelectors[0] // EVM source
53+
destChain := aptosChainSelectors[0] // Aptos destination
54+
55+
// Setup off-chain message hasher
56+
extraDataCodec := ccipcommon.ExtraDataCodec(map[string]ccipcommon.SourceChainExtraDataCodec{
57+
chain_selectors.FamilyAptos: ccipaptos.ExtraDataDecoder{},
58+
chain_selectors.FamilyEVM: ccipevm.ExtraDataDecoder{},
59+
})
60+
msgHasher := ccipaptos.NewMessageHasherV1(lggr, extraDataCodec)
61+
62+
// Get deployed contract addresses
63+
ccipChainState := state.AptosChains[destChain]
64+
65+
t.Run("EVM_to_Aptos_BasicMessage", func(t *testing.T) {
66+
msg := createBasicEVMToAptosMessage(t, sourceChain, destChain)
67+
verifyHashMatches(ctx, t, msgHasher, ccipChainState, msg, e)
68+
})
69+
70+
t.Run("EVM_to_Aptos_WithTokens", func(t *testing.T) {
71+
msg := createEVMToAptosMessageWithTokens(t, sourceChain, destChain)
72+
verifyHashMatches(ctx, t, msgHasher, ccipChainState, msg, e)
73+
})
74+
75+
t.Run("EVM_to_Aptos_EmptyData", func(t *testing.T) {
76+
msg := createEVMToAptosMessageWithEmptyData(t, sourceChain, destChain)
77+
verifyHashMatches(ctx, t, msgHasher, ccipChainState, msg, e)
78+
})
79+
80+
t.Run("EVM_to_Aptos_LargeData_3KB", func(t *testing.T) {
81+
msg := createEVMToAptosMessageWithLargeData(t, sourceChain, destChain, 3000)
82+
verifyHashMatches(ctx, t, msgHasher, ccipChainState, msg, e)
83+
})
84+
}
85+
86+
func verifyHashMatches(
87+
ctx context.Context,
88+
t *testing.T,
89+
msgHasher ccipocr3common.MessageHasher,
90+
ccipChainState aptosstate.CCIPChainState,
91+
msg ccipocr3common.Message,
92+
e testhelpers.DeployedEnv,
93+
) {
94+
// Compute off-chain hash using Go implementation
95+
offChainHash, err := msgHasher.Hash(ctx, msg)
96+
require.NoError(t, err, "Off-chain hash computation failed")
97+
98+
// Compute on-chain hash using Aptos Move contract
99+
onChainHash := computeOnChainHash(t, ccipChainState, msg, e)
100+
101+
require.Equal(t, onChainHash[:], offChainHash[:],
102+
"On-chain and off-chain hash mismatch! \n"+
103+
"On-chain: %s\n"+
104+
"Off-chain: %s\n"+
105+
"Message: %+v",
106+
hexutil.Encode(onChainHash[:]),
107+
hexutil.Encode(offChainHash[:]),
108+
msg)
109+
110+
t.Logf("✓ Hash verification passed")
111+
t.Logf(" Onchain Hash: %s", hexutil.Encode(onChainHash[:]))
112+
t.Logf(" Offchain Hash: %s", hexutil.Encode(offChainHash[:]))
113+
}
114+
115+
func computeOnChainHash(
116+
t *testing.T,
117+
ccipChainState aptosstate.CCIPChainState,
118+
msg ccipocr3common.Message,
119+
e testhelpers.DeployedEnv,
120+
) [32]byte {
121+
destChain := uint64(msg.Header.DestChainSelector)
122+
123+
aptosChain, exists := e.Env.BlockChains.AptosChains()[destChain]
124+
require.True(t, exists, "Aptos chain not found in dest (%d)", destChain)
125+
126+
aptosClient := aptosChain.Client
127+
ccipAddr := ccipChainState.CCIPAddress
128+
offramp := aptos_ccip_offramp.NewOfframp(ccipAddr, aptosClient)
129+
gasLimit := parseGasLimitFromExtraArgs(msg.ExtraArgs)
130+
131+
sourcePoolAddresses := make([][]byte, len(msg.TokenAmounts))
132+
destTokenAddresses := make([]aptos.AccountAddress, len(msg.TokenAmounts))
133+
destGasAmounts := make([]uint32, len(msg.TokenAmounts))
134+
extraDatas := make([][]byte, len(msg.TokenAmounts))
135+
amounts := make([]*big.Int, len(msg.TokenAmounts))
136+
137+
for i, token := range msg.TokenAmounts {
138+
sourcePoolAddresses[i] = token.SourcePoolAddress
139+
var addr aptos.AccountAddress
140+
copy(addr[:], token.DestTokenAddress)
141+
destTokenAddresses[i] = addr
142+
destGasAmounts[i] = parseDestGasAmount(token.DestExecData)
143+
extraDatas[i] = token.ExtraData
144+
amounts[i] = token.Amount.Int
145+
}
146+
147+
var receiver aptos.AccountAddress
148+
copy(receiver[:], msg.Receiver)
149+
150+
result, err := offramp.CalculateMessageHash(
151+
&aptos_call_opts.CallOpts{},
152+
msg.Header.MessageID[:],
153+
uint64(msg.Header.SourceChainSelector),
154+
uint64(msg.Header.DestChainSelector),
155+
uint64(msg.Header.SequenceNumber),
156+
msg.Header.Nonce,
157+
msg.Sender,
158+
receiver,
159+
msg.Header.OnRamp,
160+
msg.Data,
161+
gasLimit,
162+
sourcePoolAddresses,
163+
destTokenAddresses,
164+
destGasAmounts,
165+
extraDatas,
166+
amounts,
167+
)
168+
require.NoError(t, err, "On chain offramp::calculate_message_hash() failed")
169+
170+
var hash [32]byte
171+
copy(hash[:], result)
172+
return hash
173+
}
174+
175+
func createBasicEVMToAptosMessage(t *testing.T, sourceChain, destChain uint64) ccipocr3common.Message {
176+
messageIDBytes := hexutil.MustDecode("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
177+
var messageID ccipocr3common.Bytes32
178+
copy(messageID[:], messageIDBytes)
179+
180+
onRampBytes := hexutil.MustDecode("0x47a1f0a819457f01153f35c6b6b0d42e2e16e91e")
181+
senderBytes := hexutil.MustDecode("0xd87929a32cf0cbdc9e2d07ffc7c33344079de727")
182+
receiverBytes := hexutil.MustDecode("0xbd8a1fb0af25dc8700d2d302cfbae718c3b2c3c61cfe47f58a45b1126c006490")
183+
184+
extraArgs := testhelpers.MakeEVMExtraArgsV2(500000, true)
185+
186+
return ccipocr3common.Message{
187+
Header: ccipocr3common.RampMessageHeader{
188+
MessageID: messageID,
189+
SourceChainSelector: ccipocr3common.ChainSelector(sourceChain),
190+
DestChainSelector: ccipocr3common.ChainSelector(destChain),
191+
SequenceNumber: ccipocr3common.SeqNum(42),
192+
Nonce: 123,
193+
OnRamp: onRampBytes,
194+
},
195+
Sender: senderBytes,
196+
Receiver: receiverBytes,
197+
Data: []byte("hello CCIPReceiver"),
198+
ExtraArgs: extraArgs,
199+
TokenAmounts: []ccipocr3common.RampTokenAmount{},
200+
}
201+
}
202+
203+
func createEVMToAptosMessageWithTokens(t *testing.T, sourceChain, destChain uint64) ccipocr3common.Message {
204+
msg := createBasicEVMToAptosMessage(t, sourceChain, destChain)
205+
206+
srcPool1 := hexutil.MustDecode("0xabcdef1234567890abcdef1234567890abcdef12")
207+
destToken1Bytes := hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000005678")
208+
extraData1 := hexutil.MustDecode("0x00112233")
209+
210+
srcPool2 := hexutil.MustDecode("0x123456789abcdef123456789abcdef123456789a")
211+
destToken2Bytes := hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000009abc")
212+
extraData2 := hexutil.MustDecode("0xffeeddcc")
213+
214+
destExecData1, err := utils.ABIEncode(`[{"type":"uint32"}]`, uint32(10000))
215+
require.NoError(t, err)
216+
destExecData2, err := utils.ABIEncode(`[{"type":"uint32"}]`, uint32(20000))
217+
require.NoError(t, err)
218+
219+
msg.TokenAmounts = []ccipocr3common.RampTokenAmount{
220+
{
221+
SourcePoolAddress: srcPool1,
222+
DestTokenAddress: destToken1Bytes,
223+
ExtraData: extraData1,
224+
Amount: ccipocr3common.NewBigInt(big.NewInt(1000000)),
225+
DestExecData: destExecData1,
226+
},
227+
{
228+
SourcePoolAddress: srcPool2,
229+
DestTokenAddress: destToken2Bytes,
230+
ExtraData: extraData2,
231+
Amount: ccipocr3common.NewBigInt(big.NewInt(5000000)),
232+
DestExecData: destExecData2,
233+
},
234+
}
235+
return msg
236+
}
237+
238+
func createEVMToAptosMessageWithEmptyData(t *testing.T, sourceChain, destChain uint64) ccipocr3common.Message {
239+
msg := createBasicEVMToAptosMessage(t, sourceChain, destChain)
240+
msg.Data = []byte{}
241+
return msg
242+
}
243+
244+
func createEVMToAptosMessageWithLargeData(t *testing.T, sourceChain, destChain uint64, size int) ccipocr3common.Message {
245+
msg := createBasicEVMToAptosMessage(t, sourceChain, destChain)
246+
msg.Data = make([]byte, size)
247+
for i := range msg.Data {
248+
msg.Data[i] = byte(i % 256)
249+
}
250+
return msg
251+
}
252+
253+
func parseGasLimitFromExtraArgs(extraArgs []byte) *big.Int {
254+
evmDecoder := ccipevm.ExtraDataDecoder{}
255+
if decodedMap, err := evmDecoder.DecodeExtraArgsToMap(extraArgs); err == nil {
256+
if gasLimit, exists := decodedMap["gasLimit"]; exists {
257+
if gl, ok := gasLimit.(*big.Int); ok {
258+
return gl
259+
}
260+
}
261+
}
262+
return big.NewInt(200000)
263+
}
264+
265+
func parseDestGasAmount(destExecData []byte) uint32 {
266+
evmDecoder := ccipevm.ExtraDataDecoder{}
267+
if decodedMap, err := evmDecoder.DecodeDestExecDataToMap(destExecData); err == nil {
268+
if destGasAmount, exists := decodedMap["destGasAmount"]; exists {
269+
if gasAmount, ok := destGasAmount.(uint32); ok {
270+
return gasAmount
271+
}
272+
}
273+
}
274+
return 50000
275+
}

0 commit comments

Comments
 (0)