From 28a062fc7f23e50a37e9299734b524569b096ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 17 Jan 2025 17:07:37 +0900 Subject: [PATCH 01/62] ccip_messaging_test: Make messagingTestCase receiver chain agnostic --- .../ccip/changeset/testhelpers/messagingtest/helpers.go | 2 +- integration-tests/smoke/ccip/ccip_messaging_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go index 2f2fc32ae3f..aacbd4f53b6 100644 --- a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go +++ b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go @@ -82,7 +82,7 @@ type TestCase struct { TestSetup Replayed bool Nonce uint64 - Receiver common.Address + Receiver []byte MsgData []byte ExtraArgs []byte ExpectedExecutionState int diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 33133bd958e..1c91f4554ef 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -93,7 +93,7 @@ func Test_CCIPMessaging(t *testing.T) { TestSetup: setup, Replayed: replayed, Nonce: nonce, - Receiver: common.HexToAddress("0xdead"), + Receiver: common.HexToAddress("0xdead").Bytes(), MsgData: []byte("hello eoa"), ExtraArgs: nil, // default extraArgs ExpectedExecutionState: testhelpers.EXECUTION_STATE_SUCCESS, // success because offRamp won't call an EOA @@ -112,7 +112,7 @@ func Test_CCIPMessaging(t *testing.T) { TestSetup: setup, Replayed: out.Replayed, Nonce: out.Nonce, - Receiver: state.Chains[destChain].FeeQuoter.Address(), + Receiver: state.Chains[destChain].FeeQuoter.Address().Bytes(), MsgData: []byte("hello FeeQuoter"), ExtraArgs: nil, // default extraArgs ExpectedExecutionState: testhelpers.EXECUTION_STATE_SUCCESS, // success because offRamp won't call a contract not implementing CCIPReceiver @@ -128,7 +128,7 @@ func Test_CCIPMessaging(t *testing.T) { TestSetup: setup, Replayed: out.Replayed, Nonce: out.Nonce, - Receiver: state.Chains[destChain].Receiver.Address(), + Receiver: state.Chains[destChain].Receiver.Address().Bytes(), MsgData: []byte("hello CCIPReceiver"), ExtraArgs: nil, // default extraArgs ExpectedExecutionState: testhelpers.EXECUTION_STATE_SUCCESS, @@ -153,7 +153,7 @@ func Test_CCIPMessaging(t *testing.T) { TestSetup: setup, Replayed: out.Replayed, Nonce: out.Nonce, - Receiver: state.Chains[destChain].Receiver.Address(), + Receiver: state.Chains[destChain].Receiver.Address().Bytes(), MsgData: []byte("hello CCIPReceiver with low exec gas"), ExtraArgs: testhelpers.MakeEVMExtraArgsV2(1, false), // 1 gas is too low. ExpectedExecutionState: testhelpers.EXECUTION_STATE_FAILURE, // state would be failed onchain due to low gas From 05e844f343e3fa7c6db50301e0c505b30dec16b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 17 Jan 2025 17:14:49 +0900 Subject: [PATCH 02/62] Test_CCIPMessaging_Solana skeleton --- .../smoke/ccip/ccip_messaging_test.go | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 1c91f4554ef..4c025cca674 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -185,6 +185,72 @@ func Test_CCIPMessaging(t *testing.T) { require.Equal(t, int32(0), ms.reExecutionsObserved.Load()) } +func Test_CCIPMessaging_Solana(t *testing.T) { + // Setup 2 chains (EVM and Solana) and a single lane. + ctx := testhelpers.Context(t) + e, _, _ := testsetups.NewIntegrationEnvironment(t, testhelpers.WithSolChains(1)) + + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + + allChainSelectors := maps.Keys(e.Env.Chains) + allSolChainSelectors := maps.Keys(e.Env.SolChains) + sourceChain := allChainSelectors[0] + destChain := allSolChainSelectors[1] + t.Log("All chain selectors:", allChainSelectors, + ", sol chain selectors:", allSolChainSelectors, + ", home chain selector:", e.HomeChainSel, + ", feed chain selector:", e.FeedChainSel, + ", source chain selector:", sourceChain, + ", dest chain selector:", destChain, + ) + // connect a single lane, source to dest + testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceChain, destChain, false) + + var ( + replayed bool + nonce uint64 + sender = common.LeftPadBytes(e.Env.Chains[sourceChain].DeployerKey.From.Bytes(), 32) + out messagingTestCaseOutput + setup = testCaseSetup{ + t: t, + sender: sender, + deployedEnv: e, + onchainState: state, + sourceChain: sourceChain, + destChain: destChain, + } + ) + + t.Run("message to contract implementing CCIPReceiver", func(t *testing.T) { + latestHead, err := e.Env.Chains[destChain].Client.HeaderByNumber(ctx, nil) + require.NoError(t, err) + receiver := state.SolChains[destChain].Receiver.Bytes() + out = runMessagingTestCase( + messagingTestCase{ + testCaseSetup: setup, + replayed: replayed, + nonce: nonce, + }, + receiver, + []byte("hello CCIPReceiver"), + nil, // default extraArgs + testhelpers.EXECUTION_STATE_SUCCESS, + func(t *testing.T) { + iter, err := state.Chains[destChain].Receiver.FilterMessageReceived(&bind.FilterOpts{ + Context: ctx, + Start: latestHead.Number.Uint64(), + }) + require.NoError(t, err) + require.True(t, iter.Next()) + // MessageReceived doesn't emit the data unfortunately, so can't check that. + }, + ) + }) + + fmt.Printf("out: %v\n", out) +} + type monitorState struct { reExecutionsObserved atomic.Int32 } From 78804c7c83bfa4ca4f94064d0f1ee3104bdca091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 22 Jan 2025 15:55:02 +0900 Subject: [PATCH 03/62] Draft Solana versions of commit and exec confirmers --- .../changeset/testhelpers/test_assertions.go | 281 +++++++++++++++--- 1 file changed, 245 insertions(+), 36 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index ff026962ce6..5cebde22c9c 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -11,10 +11,17 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/gagliardetto/solana-go" + solrpc "github.com/gagliardetto/solana-go/rpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" + solconfig "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" + solccip "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/ccip" + solcommon "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common" + + chainsel "github.com/smartcontractkit/chain-selectors" commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" @@ -186,18 +193,40 @@ func ConfirmCommitForAllWithExpectedSeqNums( startBlock = startBlocks[dstChain] } - return commonutils.JustError(ConfirmCommitWithExpectedSeqNumRange( - t, - srcChain, - e.Chains[dstChain], - state.Chains[dstChain].OffRamp, - startBlock, - ccipocr3.SeqNumRange{ - ccipocr3.SeqNum(expectedSeqNum), - ccipocr3.SeqNum(expectedSeqNum), - }, - true, - )) + family, err := chainsel.GetSelectorFamily(dstChain) + if err != nil { + return err + } + switch family { + case chainsel.FamilyEVM: + return commonutils.JustError(ConfirmCommitWithExpectedSeqNumRange( + t, + srcChain, + e.Chains[dstChain], + state.Chains[dstChain].OffRamp, + startBlock, + ccipocr3.SeqNumRange{ + ccipocr3.SeqNum(expectedSeqNum), + ccipocr3.SeqNum(expectedSeqNum), + }, + true, + )) + case chainsel.FamilySolana: + return commonutils.JustError(ConfirmCommitWithExpectedSeqNumRangeSol( + t, + srcChain, + e.SolChains[dstChain], + state.SolChains[dstChain].Router, + *startBlock, + ccipocr3.SeqNumRange{ + ccipocr3.SeqNum(expectedSeqNum), + ccipocr3.SeqNum(expectedSeqNum), + }, + true, + )) + default: + return fmt.Errorf("unsupported chain family; %v", family) + } }) } @@ -345,16 +374,8 @@ func ConfirmCommitWithExpectedSeqNumRange( } defer subscription.Unsubscribe() - var duration time.Duration - deadline, ok := t.Deadline() - if ok { - // make this timer end a minute before so that we don't hit the deadline - duration = deadline.Sub(time.Now().Add(-1 * time.Minute)) - } else { - duration = 5 * time.Minute - } - timer := time.NewTimer(duration) - defer timer.Stop() + timeout := time.NewTimer(tests.WaitTimeout(t)) + defer timeout.Stop() ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() for { @@ -377,9 +398,9 @@ func ConfirmCommitWithExpectedSeqNumRange( } case subErr := <-subscription.Err(): return nil, fmt.Errorf("subscription error: %w", subErr) - case <-timer.C: - return nil, fmt.Errorf("timed out after waiting %s duration for commit report on chain selector %d from source selector %d expected seq nr range %s", - duration.String(), dest.Selector, srcSelector, expectedSeqNumRange.String()) + case <-timeout.C: + return nil, fmt.Errorf("timed out after waiting for commit report on chain selector %d from source selector %d expected seq nr range %s", + dest.Selector, srcSelector, expectedSeqNumRange.String()) case report := <-sink: verified := verifyCommitReport(report) if verified { @@ -389,6 +410,126 @@ func ConfirmCommitWithExpectedSeqNumRange( } } +// Scan for events referencing address +func SolEventEmitter[T any]( + t *testing.T, + client *solrpc.Client, + address solana.PublicKey, + eventType string, + startSlot uint64, + done chan any, +) <-chan T { + ch := make(chan T) + go func() { + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + var until solana.Signature + for { + select { + case <-done: + return + case <-ticker.C: + // Scan for transactions referencing the address + ctx := context.Background() + txSigs, err := client.GetSignaturesForAddressWithOpts( + ctx, + address, + &solrpc.GetSignaturesForAddressOpts{ + Commitment: solrpc.CommitmentConfirmed, + Until: until, + }, + ) + require.NoError(t, err) + + if len(txSigs) == 0 { + continue + } + + // values are returned ordered newest to oldest, so we replay them backwards + for _, txSig := range slices.Backward(txSigs) { + if txSig.Err != nil { + // We're not interested in failed transactions. + continue + } + if txSig.Slot < startSlot { + // Skip any signatures that are before the starting slot + continue + } + tx, err := client.GetTransaction( + ctx, + txSig.Signature, + &solrpc.GetTransactionOpts{ + Commitment: solrpc.CommitmentConfirmed, + Encoding: solana.EncodingBase64, + }, + ) + require.NoError(t, err) + require.NotNil(t, tx) + + var event T + require.NoError(t, solcommon.ParseEvent(tx.Meta.LogMessages, eventType, &event, solconfig.PrintEvents)) + + select { + case ch <- event: + case <-done: + return + } + } + // next scan should stop at the newest signature we've received + until = txSigs[0].Signature + } + } + }() + + return ch +} + +func ConfirmCommitWithExpectedSeqNumRangeSol( + t *testing.T, + srcSelector uint64, + dest deployment.SolChain, + routerAddress solana.PublicKey, + startSlot uint64, + expectedSeqNumRange ccipocr3.SeqNumRange, + enforceSingleCommit bool, +) (bool, error) { + seenMessages := NewCommitReportTracker(srcSelector, expectedSeqNumRange) + + done := make(chan any) + defer close(done) + sink := SolEventEmitter[solccip.EventCommitReportAccepted](t, dest.Client, routerAddress, "CommitReportAccepted", startSlot, done) + + timeout := time.NewTimer(tests.WaitTimeout(t)) + defer timeout.Stop() + + for { + select { + case commitEvent := <-sink: + require.Equal(t, srcSelector, commitEvent.Report.SourceChainSelector) + + // TODO: commitEvent.Report.MerkleRoot ? do we need to recursive call? + + // TODO: this logic is duplicated with verifyCommitReport, share + mr := commitEvent.Report + seenMessages.visitCommitReport(mr.SourceChainSelector, mr.MinSeqNr, mr.MaxSeqNr) + if mr.SourceChainSelector == srcSelector && + uint64(expectedSeqNumRange.Start()) >= mr.MinSeqNr && + uint64(expectedSeqNumRange.End()) <= mr.MaxSeqNr { + t.Logf("All sequence numbers committed in a single report [%d, %d]", expectedSeqNumRange.Start(), expectedSeqNumRange.End()) + return true, nil + } + + if !enforceSingleCommit && seenMessages.allCommited(srcSelector) { + t.Logf("All sequence numbers already committed from range [%d, %d]", expectedSeqNumRange.Start(), expectedSeqNumRange.End()) + return true, nil + } + case <-timeout.C: + return false, fmt.Errorf("timed out after waiting for commit report on chain selector %d from source selector %d expected seq nr range %s", + dest.Selector, srcSelector, expectedSeqNumRange.String()) + } + } +} + // ConfirmExecWithSeqNrsForAll waits for all chains in the environment to execute the given expectedSeqNums. // If successful, it returns a map that maps the SourceDestPair to the expected sequence number // to its execution state. @@ -418,18 +559,41 @@ func ConfirmExecWithSeqNrsForAll( } wg.Go(func() error { - innerExecutionStates, err := ConfirmExecWithSeqNrs( - t, - srcChain, - e.Chains[dstChain], - state.Chains[dstChain].OffRamp, - startBlock, - seqRange, - ) + family, err := chainsel.GetSelectorFamily(dstChain) if err != nil { return err } + var innerExecutionStates map[uint64]int + switch family { + case chainsel.FamilyEVM: + innerExecutionStates, err = ConfirmExecWithSeqNrs( + t, + srcChain, + e.Chains[dstChain], + state.Chains[dstChain].OffRamp, + startBlock, + seqRange, + ) + if err != nil { + return err + } + case chainsel.FamilySolana: + innerExecutionStates, err = ConfirmExecWithSeqNrsSol( + t, + srcChain, + e.SolChains[dstChain], + state.SolChains[dstChain].Router, + *startBlock, + seqRange, + ) + if err != nil { + return err + } + default: + return fmt.Errorf("unsupported chain family; %v", family) + } + mx.Lock() executionStates[sourceDest] = innerExecutionStates mx.Unlock() @@ -458,8 +622,8 @@ func ConfirmExecWithSeqNrs( return nil, errors.New("no expected sequence numbers provided") } - timer := time.NewTimer(tests.WaitTimeout(t)) - defer timer.Stop() + timeout := time.NewTimer(tests.WaitTimeout(t)) + defer timeout.Stop() tick := time.NewTicker(3 * time.Second) defer tick.Stop() sink := make(chan *offramp.OffRampExecutionStateChanged) @@ -512,7 +676,7 @@ func ConfirmExecWithSeqNrs( return executionStates, nil } } - case <-timer.C: + case <-timeout.C: return nil, fmt.Errorf("timed out waiting for ExecutionStateChanged on chain %d (offramp %s) from chain %d with expected sequence numbers %+v", dest.Selector, offRamp.Address().String(), sourceSelector, expectedSeqNrs) case subErr := <-subscription.Err(): @@ -521,6 +685,51 @@ func ConfirmExecWithSeqNrs( } } +func ConfirmExecWithSeqNrsSol( + t *testing.T, + srcSelector uint64, + dest deployment.SolChain, + routerAddress solana.PublicKey, + startSlot uint64, + expectedSeqNrs []uint64, +) (executionStates map[uint64]int, err error) { + // TODO: share with EVM + // some state to efficiently track the execution states + // of all the expected sequence numbers. + executionStates = make(map[uint64]int) + seqNrsToWatch := make(map[uint64]struct{}) + for _, seqNr := range expectedSeqNrs { + seqNrsToWatch[seqNr] = struct{}{} + } + + done := make(chan any) + defer close(done) + sink := SolEventEmitter[solccip.EventExecutionStateChanged](t, dest.Client, routerAddress, "ExecutionStateChanged", startSlot, done) + + timeout := time.NewTimer(tests.WaitTimeout(t)) + defer timeout.Stop() + + for { + select { + case execEvent := <-sink: + // TODO: share with EVM + _, found := seqNrsToWatch[execEvent.SequenceNumber] + if found && execEvent.SourceChainSelector == srcSelector { + t.Logf("Received ExecutionStateChanged (state %s) on chain %d (offramp %s) from chain %d with expected sequence number %d", + execEvent.State.String(), dest.Selector, routerAddress.String(), srcSelector, execEvent.SequenceNumber) + executionStates[execEvent.SequenceNumber] = int(execEvent.State) + delete(seqNrsToWatch, execEvent.SequenceNumber) + if len(seqNrsToWatch) == 0 { + return executionStates, nil + } + } + case <-timeout.C: + return nil, fmt.Errorf("timed out waiting for ExecutionStateChanged on chain %d (offramp %s) from chain %d with expected sequence numbers %+v", + dest.Selector, routerAddress.String(), srcSelector, expectedSeqNrs) + } + } +} + func ConfirmNoExecConsistentlyWithSeqNr( t *testing.T, sourceSelector uint64, From f95de4393e300a90089e4585526def40680f52f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 23 Jan 2025 23:35:29 +0900 Subject: [PATCH 04/62] extract sourceDest --- .../testhelpers/messagingtest/helpers.go | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go index aacbd4f53b6..be5850ed27a 100644 --- a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go +++ b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go @@ -147,17 +147,15 @@ func Run(tc TestCase) (out TestCaseOutput) { FeeToken: common.HexToAddress("0x0"), ExtraArgs: tc.ExtraArgs, }) + sourceDest := testhelpers.SourceDestPair{ + SourceChainSelector: tc.SourceChain, + DestChainSelector: tc.DestChain, + } expectedSeqNum := map[testhelpers.SourceDestPair]uint64{ - { - SourceChainSelector: tc.SourceChain, - DestChainSelector: tc.DestChain, - }: msgSentEvent.SequenceNumber, + sourceDest: msgSentEvent.SequenceNumber, } expectedSeqNumExec := map[testhelpers.SourceDestPair][]uint64{ - { - SourceChainSelector: tc.SourceChain, - DestChainSelector: tc.DestChain, - }: {msgSentEvent.SequenceNumber}, + sourceDest: {msgSentEvent.SequenceNumber}, } out.MsgSentEvent = msgSentEvent @@ -179,17 +177,11 @@ func Run(tc TestCase) (out TestCaseOutput) { require.Equalf( tc.T, tc.ExpectedExecutionState, - execStates[testhelpers.SourceDestPair{ - SourceChainSelector: tc.SourceChain, - DestChainSelector: tc.DestChain, - }][msgSentEvent.SequenceNumber], + execStates[sourceDest][msgSentEvent.SequenceNumber], "wrong execution state for seq nr %d, expected %d, got %d", msgSentEvent.SequenceNumber, tc.ExpectedExecutionState, - execStates[testhelpers.SourceDestPair{ - SourceChainSelector: tc.SourceChain, - DestChainSelector: tc.DestChain, - }][msgSentEvent.SequenceNumber], + execStates[sourceDest][msgSentEvent.SequenceNumber], ) // check the sender latestNonce on the dest, should be incremented From 844a0622e0bbc8a5ff42df538aae75f7868225eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 24 Jan 2025 14:58:21 +0900 Subject: [PATCH 05/62] Implement getLatestNonce for Solana --- .../testhelpers/messagingtest/helpers.go | 21 ++++++++++++++----- .../changeset/testhelpers/test_assertions.go | 1 + 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go index be5850ed27a..b947721cd6e 100644 --- a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go +++ b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go @@ -1,14 +1,21 @@ package messagingtest import ( + "context" "testing" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/gagliardetto/solana-go" chain_selectors "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" + solconfig "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" + solccip "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/ccip" + solcommon "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/deployment" @@ -116,10 +123,14 @@ func getLatestNonce(tc TestCase) uint64 { }, tc.SourceChain, tc.Sender) require.NoError(tc.T, err) case chain_selectors.FamilySolana: - // var nonceCounterAccount ccip_router.Nonce - // err = common.GetAccountDataBorshInto(ctx, solanaGoClient, nonceEvmPDA, config.DefaultCommitment, &nonceCounterAccount) - // require.NoError(t, err, "failed to get account info") - // require.Equal(t, uint64(1), nonceCounterAccount.Counter) + ctx := context.Background() + client := tc.Env.SolChains[tc.DestChain].Client + noncePDA, err := solccip.NoncePDA(tc.SourceChain, solana.PublicKeyFromBytes(tc.Sender)) + require.NoError(tc.T, err) + var nonceCounterAccount ccip_router.Nonce + err = solcommon.GetAccountDataBorshInto(ctx, client, noncePDA, solconfig.DefaultCommitment, &nonceCounterAccount) + require.NoError(tc.T, err, "failed to get nonce account info") + latestNonce = nonceCounterAccount.Counter } return latestNonce } @@ -141,7 +152,7 @@ func Run(tc TestCase) (out TestCaseOutput) { tc.DestChain, tc.TestRouter, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(tc.Receiver.Bytes(), 32), + Receiver: common.LeftPadBytes(tc.Receiver, 32), Data: tc.MsgData, TokenAmounts: nil, FeeToken: common.HexToAddress("0x0"), diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index 5cebde22c9c..3d413ce124a 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math/big" + "slices" "sync" "testing" "time" From 06fc1720e50579aae91de9ce7f23509a4eed1df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 24 Jan 2025 15:38:16 +0900 Subject: [PATCH 06/62] Implement WaitForTheToken balance for Solana --- .../changeset/testhelpers/test_helpers.go | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index 45e3a2a8b88..82304c3bf71 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -1509,26 +1509,27 @@ func WaitForTheTokenBalance( }, tests.WaitTimeout(t), 100*time.Millisecond) } -func GetTokenBalance( +func WaitForTheTokenBalanceSol( ctx context.Context, t *testing.T, - token common.Address, - receiver common.Address, - chain deployment.Chain, -) *big.Int { - tokenContract, err := burn_mint_erc677.NewBurnMintERC677(token, chain.Client) - require.NoError(t, err) - - balance, err := tokenContract.BalanceOf(&bind.CallOpts{Context: ctx}, receiver) - require.NoError(t, err) - - t.Log("Getting token balance", - "actual", balance, - "token", token, - "receiver", receiver, - ) + token solana.PublicKey, + receiver solana.PublicKey, + chain deployment.SolChain, + expected uint64, +) { + require.Eventually(t, func() bool { + _, balance, berr := solTokenUtil.TokenBalance(ctx, chain.Client, receiver, solTestConfig.DefaultCommitment) + require.NoError(t, berr) + // TODO: validate receiver's token mint == token - return balance + t.Log("Waiting for the token balance", + "expected", expected, + "actual", balance, + "token", token, + "receiver", receiver, + ) + return uint64(balance) == expected + }, tests.WaitTimeout(t), 100*time.Millisecond) } func DefaultRouterMessage(receiverAddress common.Address) router.ClientEVM2AnyMessage { From ea542e93b12483f1d475981edb9673a1301bb837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 28 Jan 2025 17:31:02 +0900 Subject: [PATCH 07/62] Update ConfirmMultipleCommits with Solana support --- .../changeset/testhelpers/test_assertions.go | 44 ++++++++++++++----- .../smoke/ccip/ccip_token_transfer_test.go | 4 +- .../smoke/ccip/ccip_usdc_test.go | 4 +- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index 3d413ce124a..2d25b8c5aa1 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -288,8 +288,8 @@ func (c *CommitReportTracker) allCommited(sourceChainSelector uint64) bool { // Waiting is done in parallel per every sourceChain/destChain (lane) passed as argument. func ConfirmMultipleCommits( t *testing.T, - chains map[uint64]deployment.Chain, - state map[uint64]changeset.CCIPChainState, + env deployment.Environment, + state changeset.CCIPOnChainState, startBlocks map[uint64]*uint64, enforceSingleCommit bool, expectedSeqNums map[SourceDestPair]ccipocr3.SeqNumRange, @@ -302,16 +302,36 @@ func ConfirmMultipleCommits( destChain := sourceDest.DestChainSelector errGrp.Go(func() error { - _, err := ConfirmCommitWithExpectedSeqNumRange( - t, - srcChain, - chains[destChain], - state[destChain].OffRamp, - startBlocks[destChain], - seqRange, - enforceSingleCommit, - ) - return err + family, err := chainsel.GetSelectorFamily(destChain) + if err != nil { + return err + } + switch family { + case chainsel.FamilyEVM: + _, err := ConfirmCommitWithExpectedSeqNumRange( + t, + srcChain, + env.Chains[destChain], + state.Chains[destChain].OffRamp, + startBlocks[destChain], + seqRange, + enforceSingleCommit, + ) + return err + case chainsel.FamilySolana: + _, err := ConfirmCommitWithExpectedSeqNumRangeSol( + t, + srcChain, + env.SolChains[destChain], + state.SolChains[destChain].Router, + *startBlocks[destChain], + seqRange, + enforceSingleCommit, + ) + return err + default: + return fmt.Errorf("unsupported chain family; %v", family) + } }) } diff --git a/integration-tests/smoke/ccip/ccip_token_transfer_test.go b/integration-tests/smoke/ccip/ccip_token_transfer_test.go index 827047e193a..bff8b7d9820 100644 --- a/integration-tests/smoke/ccip/ccip_token_transfer_test.go +++ b/integration-tests/smoke/ccip/ccip_token_transfer_test.go @@ -200,8 +200,8 @@ func TestTokenTransfer(t *testing.T) { err = testhelpers.ConfirmMultipleCommits( t, - e.Chains, - state.Chains, + e, + state, startBlocks, false, expectedSeqNums, diff --git a/integration-tests/smoke/ccip/ccip_usdc_test.go b/integration-tests/smoke/ccip/ccip_usdc_test.go index e7af88ceedc..0950ebe50eb 100644 --- a/integration-tests/smoke/ccip/ccip_usdc_test.go +++ b/integration-tests/smoke/ccip/ccip_usdc_test.go @@ -216,8 +216,8 @@ func TestUSDCTokenTransfer(t *testing.T) { err = testhelpers.ConfirmMultipleCommits( t, - e.Chains, - state.Chains, + e, + state, startBlocks, false, expectedSeqNums, From 4b621b38c91b4651015e8717ea7558b05b0152c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 31 Jan 2025 16:09:50 +0900 Subject: [PATCH 08/62] wip --- .../ccip/changeset/testhelpers/messagingtest/helpers.go | 4 ++-- deployment/environment/memory/chain.go | 1 + integration-tests/smoke/ccip/ccip_messaging_test.go | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go index b947721cd6e..7adbfc85dcc 100644 --- a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go +++ b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go @@ -13,8 +13,8 @@ import ( solconfig "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" - solccip "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/ccip" solcommon "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common" + solstate "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/state" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" @@ -125,7 +125,7 @@ func getLatestNonce(tc TestCase) uint64 { case chain_selectors.FamilySolana: ctx := context.Background() client := tc.Env.SolChains[tc.DestChain].Client - noncePDA, err := solccip.NoncePDA(tc.SourceChain, solana.PublicKeyFromBytes(tc.Sender)) + noncePDA, err := solstate.FindNoncePDA(tc.SourceChain, solana.PublicKeyFromBytes(tc.Sender), tc.OnchainState.SolChains[tc.DestChain].Router) require.NoError(tc.T, err) var nonceCounterAccount ccip_router.Nonce err = solcommon.GetAccountDataBorshInto(ctx, client, noncePDA, solconfig.DefaultCommitment, &nonceCounterAccount) diff --git a/deployment/environment/memory/chain.go b/deployment/environment/memory/chain.go index 2c91c86ab8a..63a1fa79d43 100644 --- a/deployment/environment/memory/chain.go +++ b/deployment/environment/memory/chain.go @@ -254,6 +254,7 @@ func solChain(t *testing.T, chainID uint64, adminKey *solana.PrivateKey) (string port := freeport.GetOne(t) bcInput := &blockchain.Input{ + Image: "solanalabs/solana:v1.18.26", // TODO: workaround on linux Type: "solana", ChainID: strconv.FormatUint(chainID, 10), PublicKey: adminKey.PublicKey().String(), diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 4c025cca674..8ecc825f112 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -196,7 +196,7 @@ func Test_CCIPMessaging_Solana(t *testing.T) { allChainSelectors := maps.Keys(e.Env.Chains) allSolChainSelectors := maps.Keys(e.Env.SolChains) sourceChain := allChainSelectors[0] - destChain := allSolChainSelectors[1] + destChain := allSolChainSelectors[0] t.Log("All chain selectors:", allChainSelectors, ", sol chain selectors:", allSolChainSelectors, ", home chain selector:", e.HomeChainSel, @@ -223,7 +223,8 @@ func Test_CCIPMessaging_Solana(t *testing.T) { ) t.Run("message to contract implementing CCIPReceiver", func(t *testing.T) { - latestHead, err := e.Env.Chains[destChain].Client.HeaderByNumber(ctx, nil) + // TODO: abstract out into a helper + latestHead, err := e.Env.SolChains[destChain].Client.GetSlot(ctx, solconfig.DefaultCommitment) require.NoError(t, err) receiver := state.SolChains[destChain].Receiver.Bytes() out = runMessagingTestCase( @@ -237,9 +238,10 @@ func Test_CCIPMessaging_Solana(t *testing.T) { nil, // default extraArgs testhelpers.EXECUTION_STATE_SUCCESS, func(t *testing.T) { + // TODO: fix up, use the same code event filter does iter, err := state.Chains[destChain].Receiver.FilterMessageReceived(&bind.FilterOpts{ Context: ctx, - Start: latestHead.Number.Uint64(), + Start: latestHead, }) require.NoError(t, err) require.True(t, iter.Next()) From 40640e2c6b288167b3c5f9a33c3213bf509f14fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 3 Feb 2025 12:37:51 +0900 Subject: [PATCH 09/62] Include SVM extra args --- integration-tests/smoke/ccip/ccip_messaging_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 8ecc825f112..12c67217cb0 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + bin "github.com/gagliardetto/binary" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -227,6 +228,8 @@ func Test_CCIPMessaging_Solana(t *testing.T) { latestHead, err := e.Env.SolChains[destChain].Client.GetSlot(ctx, solconfig.DefaultCommitment) require.NoError(t, err) receiver := state.SolChains[destChain].Receiver.Bytes() + extraArgs, err := bin.MarshalBorsh(ccip_router.SVMExtraArgs{}) // SVM doesn't allow an empty extraArgs + require.NoError(t, err) out = runMessagingTestCase( messagingTestCase{ testCaseSetup: setup, @@ -235,7 +238,7 @@ func Test_CCIPMessaging_Solana(t *testing.T) { }, receiver, []byte("hello CCIPReceiver"), - nil, // default extraArgs + extraArgs, testhelpers.EXECUTION_STATE_SUCCESS, func(t *testing.T) { // TODO: fix up, use the same code event filter does From 738de237282eb73a5eadf25c497d2b47b56bbcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 3 Feb 2025 15:09:50 +0900 Subject: [PATCH 10/62] wip --- .../smoke/ccip/ccip_messaging_test.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 12c67217cb0..b0311c7cc71 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -2,6 +2,7 @@ package ccip import ( "context" + "fmt" "sync" "sync/atomic" "testing" @@ -9,10 +10,13 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - bin "github.com/gagliardetto/binary" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" + solconfig "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/ccip" + chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" @@ -223,13 +227,21 @@ func Test_CCIPMessaging_Solana(t *testing.T) { } ) + // message := ccip_router.SVM2AnyMessage{ + // Receiver: validReceiverAddress[:], + // FeeToken: wsol.mint, + // TokenAmounts: []ccip_router.SVMTokenAmount{{Token: token0.Mint.PublicKey(), Amount: 1}}, + // ExtraArgs: emptyEVMExtraArgsV2, + // } + t.Run("message to contract implementing CCIPReceiver", func(t *testing.T) { // TODO: abstract out into a helper latestHead, err := e.Env.SolChains[destChain].Client.GetSlot(ctx, solconfig.DefaultCommitment) require.NoError(t, err) receiver := state.SolChains[destChain].Receiver.Bytes() - extraArgs, err := bin.MarshalBorsh(ccip_router.SVMExtraArgs{}) // SVM doesn't allow an empty extraArgs + extraArgs, err := ccip.SerializeExtraArgs(ccip_router.SVMExtraArgs{}) // SVM doesn't allow an empty extraArgs require.NoError(t, err) + panic(extraArgs) out = runMessagingTestCase( messagingTestCase{ testCaseSetup: setup, From d21e7787004c9924e1543a38c2e43ae32dcd65f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 4 Feb 2025 15:31:35 +0900 Subject: [PATCH 11/62] fix for startBlock(s) potentially being nil --- .../changeset/testhelpers/test_assertions.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index 2d25b8c5aa1..c2ff89ab3f4 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -213,12 +213,16 @@ func ConfirmCommitForAllWithExpectedSeqNums( true, )) case chainsel.FamilySolana: + var startSlot uint64 + if startBlock != nil { + startSlot = *startBlock + } return commonutils.JustError(ConfirmCommitWithExpectedSeqNumRangeSol( t, srcChain, e.SolChains[dstChain], state.SolChains[dstChain].Router, - *startBlock, + startSlot, ccipocr3.SeqNumRange{ ccipocr3.SeqNum(expectedSeqNum), ccipocr3.SeqNum(expectedSeqNum), @@ -319,12 +323,16 @@ func ConfirmMultipleCommits( ) return err case chainsel.FamilySolana: + var startSlot uint64 + if startBlocks[destChain] != nil { + startSlot = *startBlocks[destChain] + } _, err := ConfirmCommitWithExpectedSeqNumRangeSol( t, srcChain, env.SolChains[destChain], state.SolChains[destChain].Router, - *startBlocks[destChain], + startSlot, seqRange, enforceSingleCommit, ) @@ -600,12 +608,16 @@ func ConfirmExecWithSeqNrsForAll( return err } case chainsel.FamilySolana: + var startSlot uint64 + if startBlock != nil { + startSlot = *startBlock + } innerExecutionStates, err = ConfirmExecWithSeqNrsSol( t, srcChain, e.SolChains[dstChain], state.SolChains[dstChain].Router, - *startBlock, + startSlot, seqRange, ) if err != nil { From 6ffd5a4a31ee1b982f19c7ad79c53608c5a808ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 4 Feb 2025 15:32:10 +0900 Subject: [PATCH 12/62] fix SVM extra args serialization --- .../smoke/ccip/ccip_messaging_test.go | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index b0311c7cc71..cec5feb27b1 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -10,12 +10,11 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" solconfig "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" - "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" - "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/ccip" chainsel "github.com/smartcontractkit/chain-selectors" @@ -25,6 +24,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment/ccip/manualexechelpers" "github.com/smartcontractkit/chainlink/deployment/environment/memory" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_6_0/message_hasher" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_6_0/offramp" ) @@ -190,6 +190,19 @@ func Test_CCIPMessaging(t *testing.T) { require.Equal(t, int32(0), ms.reExecutionsObserved.Load()) } +// NOTE: this is EVM specific (EVM->SVM) +const SVMExtraArgsV1Tag = "0x1f3b3aba" + +func SerializeSVMExtraArgs(data message_hasher.ClientSVMExtraArgsV1) ([]byte, error) { + tagBytes := hexutil.MustDecode(SVMExtraArgsV1Tag) + abi, err := message_hasher.MessageHasherMetaData.GetAbi() + if err != nil { + return nil, err + } + v, err := abi.Methods["encodeSVMExtraArgsV1"].Inputs.Pack(data) + return append(tagBytes, v...), err +} + func Test_CCIPMessaging_Solana(t *testing.T) { // Setup 2 chains (EVM and Solana) and a single lane. ctx := testhelpers.Context(t) @@ -239,9 +252,8 @@ func Test_CCIPMessaging_Solana(t *testing.T) { latestHead, err := e.Env.SolChains[destChain].Client.GetSlot(ctx, solconfig.DefaultCommitment) require.NoError(t, err) receiver := state.SolChains[destChain].Receiver.Bytes() - extraArgs, err := ccip.SerializeExtraArgs(ccip_router.SVMExtraArgs{}) // SVM doesn't allow an empty extraArgs + extraArgs, err := SerializeSVMExtraArgs(message_hasher.ClientSVMExtraArgsV1{}) // SVM doesn't allow an empty extraArgs require.NoError(t, err) - panic(extraArgs) out = runMessagingTestCase( messagingTestCase{ testCaseSetup: setup, From 6766aaa0c7eee2be3e6865da234af6473ccb5a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 4 Feb 2025 15:34:20 +0900 Subject: [PATCH 13/62] only replay blocks on EVM chains, this is EVM specific --- .../changeset/testhelpers/messagingtest/helpers.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go index 7adbfc85dcc..862ad61061b 100644 --- a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go +++ b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go @@ -102,12 +102,17 @@ type TestCaseOutput struct { MsgSentEvent *onramp.OnRampCCIPMessageSent } -func sleepAndReplay(t *testing.T, e testhelpers.DeployedEnv, sourceChain, destChain uint64) { +func sleepAndReplay(t *testing.T, e testhelpers.DeployedEnv, chainSelectors ...uint64) { time.Sleep(30 * time.Second) replayBlocks := make(map[uint64]uint64) - replayBlocks[sourceChain] = 1 - replayBlocks[destChain] = 1 - + for _, selector := range chainSelectors { + family, err := chain_selectors.GetSelectorFamily(selector) + require.NoError(t, err) + // log replay is only available on EVM + if family == chain_selectors.FamilyEVM { + replayBlocks[selector] = 1 + } + } testhelpers.ReplayLogs(t, e.Env.Offchain, replayBlocks) } From 1400e3d7a608622e82576b1c284e5b7f3f2241cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 4 Feb 2025 16:41:39 +0900 Subject: [PATCH 14/62] Add a LatestBlock helper --- .../changeset/testhelpers/test_helpers.go | 28 ++++++++++++++++--- .../v1_6/cs_active_candidate_test.go | 3 +- .../ccip/changeset/v1_6/cs_add_lane_test.go | 3 +- .../smoke/ccip/ccip_messaging_test.go | 10 +++---- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index 82304c3bf71..6ce4ca8022a 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -151,7 +151,29 @@ func DeployTestContracts(t *testing.T, } } +func LatestBlock(ctx context.Context, env deployment.Environment, chainSelector uint64) (uint64, error) { + family, err := chainsel.GetSelectorFamily(chainSelector) + if err != nil { + return 0, err + } + + switch family { + case chainsel.FamilyEVM: + latesthdr, err := env.Chains[chainSelector].Client.HeaderByNumber(ctx, nil) + if err != nil { + return 0, errors.Wrapf(err, "failed to get latest header for chain %d", chainSelector) + } + block := latesthdr.Number.Uint64() + return block, nil + case chainsel.FamilySolana: + return env.SolChains[chainSelector].Client.GetSlot(ctx, solTestConfig.DefaultCommitment) + default: + return 0, errors.New("unsupported chain family") + } +} + func LatestBlocksByChain(ctx context.Context, chains map[uint64]deployment.Chain) (map[uint64]uint64, error) { + // TODO: use LatestBlock and include solchains latestBlocks := make(map[uint64]uint64) for _, chain := range chains { latesthdr, err := chain.Client.HeaderByNumber(ctx, nil) @@ -742,9 +764,8 @@ func deploySingleFeed( } func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, state changeset.CCIPOnChainState, sourceCS, destCS, expectedSeqNr uint64) error { - latesthdr, err := env.Chains[destCS].Client.HeaderByNumber(testcontext.Get(t), nil) + startBlock, err := LatestBlock(testcontext.Get(t), env, destCS) require.NoError(t, err) - startBlock := latesthdr.Number.Uint64() fmt.Printf("startblock %d", startBlock) msgSentEvent := TestSendRequest(t, env, state, sourceCS, destCS, false, router.ClientEVM2AnyMessage{ Receiver: common.LeftPadBytes(state.Chains[destCS].Receiver.Address().Bytes(), 32), @@ -1309,9 +1330,8 @@ func Transfer( ) (*onramp.OnRampCCIPMessageSent, map[uint64]*uint64) { startBlocks := make(map[uint64]*uint64) - latesthdr, err := env.Chains[destChain].Client.HeaderByNumber(ctx, nil) + block, err := LatestBlock(ctx, env, destChain) require.NoError(t, err) - block := latesthdr.Number.Uint64() startBlocks[destChain] = &block msgSentEvent := TestSendRequest(t, env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ diff --git a/deployment/ccip/changeset/v1_6/cs_active_candidate_test.go b/deployment/ccip/changeset/v1_6/cs_active_candidate_test.go index 55326fd7111..875a0e7bc00 100644 --- a/deployment/ccip/changeset/v1_6/cs_active_candidate_test.go +++ b/deployment/ccip/changeset/v1_6/cs_active_candidate_test.go @@ -138,9 +138,8 @@ func Test_ActiveCandidate(t *testing.T) { testhelpers.AssertTimelockOwnership(t, tenv, allChains, state) sendMsg := func() { - latesthdr, err := tenv.Env.Chains[dest].Client.HeaderByNumber(testcontext.Get(t), nil) + block, err := testhelpers.LatestBlock(testcontext.Get(t), tenv.Env, dest) require.NoError(t, err) - block := latesthdr.Number.Uint64() msgSentEvent := testhelpers.TestSendRequest(t, tenv.Env, state, source, dest, false, router.ClientEVM2AnyMessage{ Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), Data: []byte("hello world"), diff --git a/deployment/ccip/changeset/v1_6/cs_add_lane_test.go b/deployment/ccip/changeset/v1_6/cs_add_lane_test.go index 175a16e9c31..7f7b239b8d0 100644 --- a/deployment/ccip/changeset/v1_6/cs_add_lane_test.go +++ b/deployment/ccip/changeset/v1_6/cs_add_lane_test.go @@ -27,9 +27,8 @@ func TestAddLanesWithTestRouter(t *testing.T) { startBlocks := make(map[uint64]*uint64) // Send a message from each chain to every other chain. expectedSeqNumExec := make(map[testhelpers.SourceDestPair][]uint64) - latesthdr, err := e.Env.Chains[chain2].Client.HeaderByNumber(testcontext.Get(t), nil) + block, err := testhelpers.LatestBlock(testcontext.Get(t), e.Env, chain2) require.NoError(t, err) - block := latesthdr.Number.Uint64() startBlocks[chain2] = &block msgSentEvent := testhelpers.TestSendRequest(t, e.Env, state, chain1, chain2, true, router.ClientEVM2AnyMessage{ Receiver: common.LeftPadBytes(state.Chains[chain2].Receiver.Address().Bytes(), 32), diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index cec5feb27b1..4182b0d3bd2 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -14,8 +14,6 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/exp/maps" - solconfig "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" - chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" @@ -126,7 +124,7 @@ func Test_CCIPMessaging(t *testing.T) { }) t.Run("message to contract implementing CCIPReceiver", func(t *testing.T) { - latestHead, err := e.Env.Chains[destChain].Client.HeaderByNumber(ctx, nil) + latestHead, err := testhelpers.LatestBlock(ctx, e.Env, destChain) require.NoError(t, err) out = mt.Run( mt.TestCase{ @@ -141,7 +139,7 @@ func Test_CCIPMessaging(t *testing.T) { func(t *testing.T) { iter, err := state.Chains[destChain].Receiver.FilterMessageReceived(&bind.FilterOpts{ Context: ctx, - Start: latestHead.Number.Uint64(), + Start: latestHead, }) require.NoError(t, err) require.True(t, iter.Next()) @@ -249,7 +247,7 @@ func Test_CCIPMessaging_Solana(t *testing.T) { t.Run("message to contract implementing CCIPReceiver", func(t *testing.T) { // TODO: abstract out into a helper - latestHead, err := e.Env.SolChains[destChain].Client.GetSlot(ctx, solconfig.DefaultCommitment) + latestSlot, err := testhelpers.LatestBlock(ctx, e.Env, destChain) require.NoError(t, err) receiver := state.SolChains[destChain].Receiver.Bytes() extraArgs, err := SerializeSVMExtraArgs(message_hasher.ClientSVMExtraArgsV1{}) // SVM doesn't allow an empty extraArgs @@ -268,7 +266,7 @@ func Test_CCIPMessaging_Solana(t *testing.T) { // TODO: fix up, use the same code event filter does iter, err := state.Chains[destChain].Receiver.FilterMessageReceived(&bind.FilterOpts{ Context: ctx, - Start: latestHead, + Start: latestSlot, }) require.NoError(t, err) require.True(t, iter.Next()) From 1c84eb4b020f938388f04f5b6dc68ba268b29f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 5 Feb 2025 14:34:51 +0900 Subject: [PATCH 15/62] Fix post-rebase --- .../smoke/ccip/ccip_messaging_test.go | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 4182b0d3bd2..71060e09412 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -227,15 +227,17 @@ func Test_CCIPMessaging_Solana(t *testing.T) { replayed bool nonce uint64 sender = common.LeftPadBytes(e.Env.Chains[sourceChain].DeployerKey.From.Bytes(), 32) - out messagingTestCaseOutput - setup = testCaseSetup{ - t: t, - sender: sender, - deployedEnv: e, - onchainState: state, - sourceChain: sourceChain, - destChain: destChain, - } + out mt.TestCaseOutput + setup = mt.NewTestSetupWithDeployedEnv( + t, + e, + state, + sourceChain, + destChain, + sender, + false, // testRouter + true, // validateResp + ) ) // message := ccip_router.SVM2AnyMessage{ @@ -246,31 +248,34 @@ func Test_CCIPMessaging_Solana(t *testing.T) { // } t.Run("message to contract implementing CCIPReceiver", func(t *testing.T) { - // TODO: abstract out into a helper latestSlot, err := testhelpers.LatestBlock(ctx, e.Env, destChain) require.NoError(t, err) receiver := state.SolChains[destChain].Receiver.Bytes() extraArgs, err := SerializeSVMExtraArgs(message_hasher.ClientSVMExtraArgsV1{}) // SVM doesn't allow an empty extraArgs require.NoError(t, err) - out = runMessagingTestCase( - messagingTestCase{ - testCaseSetup: setup, - replayed: replayed, - nonce: nonce, - }, - receiver, - []byte("hello CCIPReceiver"), - extraArgs, - testhelpers.EXECUTION_STATE_SUCCESS, - func(t *testing.T) { - // TODO: fix up, use the same code event filter does - iter, err := state.Chains[destChain].Receiver.FilterMessageReceived(&bind.FilterOpts{ - Context: ctx, - Start: latestSlot, - }) - require.NoError(t, err) - require.True(t, iter.Next()) - // MessageReceived doesn't emit the data unfortunately, so can't check that. + out = mt.Run( + mt.TestCase{ + TestSetup: setup, + Replayed: replayed, + Nonce: nonce, + Receiver: receiver, + MsgData: []byte("hello CCIPReceiver"), + ExtraArgs: extraArgs, + ExpectedExecutionState: testhelpers.EXECUTION_STATE_SUCCESS, + ExtraAssertions: []func(t *testing.T){ + func(t *testing.T) { + // TODO: lookup event state, assert counter incremented + // state.SolChains[destChain].Receiver + // TODO: fix up, use the same code event filter does + iter, err := state.Chains[destChain].Receiver.FilterMessageReceived(&bind.FilterOpts{ + Context: ctx, + Start: latestSlot, + }) + require.NoError(t, err) + require.True(t, iter.Next()) + // MessageReceived doesn't emit the data unfortunately, so can't check that. + }, + }, }, ) }) From f607e222698d9659cd7323c6e3f4df28dfa1f2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 6 Feb 2025 23:36:05 +0900 Subject: [PATCH 16/62] Refactor: use []byte for addresses when asserting balances --- .../changeset/testhelpers/test_helpers.go | 116 +++++++++--------- .../smoke/ccip/ccip_ooo_execution_test.go | 8 +- .../smoke/ccip/ccip_token_transfer_test.go | 37 +++--- .../smoke/ccip/ccip_usdc_test.go | 41 +++---- 4 files changed, 100 insertions(+), 102 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index 6ce4ca8022a..4187d568d4c 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -7,6 +7,7 @@ import ( "math/big" "net/http" "net/http/httptest" + "slices" "sort" "strings" "testing" @@ -1325,7 +1326,7 @@ func Transfer( state changeset.CCIPOnChainState, sourceChain, destChain uint64, tokens []router.ClientEVMTokenAmount, - receiver common.Address, + receiver []byte, data, extraArgs []byte, ) (*onramp.OnRampCCIPMessageSent, map[uint64]*uint64) { startBlocks := make(map[uint64]*uint64) @@ -1335,7 +1336,7 @@ func Transfer( startBlocks[destChain] = &block msgSentEvent := TestSendRequest(t, env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(receiver.Bytes(), 32), + Receiver: common.LeftPadBytes(receiver, 32), Data: data, TokenAmounts: tokens, FeeToken: common.HexToAddress("0x0"), @@ -1347,13 +1348,13 @@ func Transfer( type TestTransferRequest struct { Name string SourceChain, DestChain uint64 - Receiver common.Address + Receiver []byte ExpectedStatus int // optional Tokens []router.ClientEVMTokenAmount Data []byte ExtraArgs []byte - ExpectedTokenBalances map[common.Address]*big.Int + ExpectedTokenBalances []ExpectedBalance } // TransferMultiple sends multiple CCIPMessages (represented as TestTransferRequest) sequentially. @@ -1373,7 +1374,7 @@ func TransferMultiple( map[uint64]*uint64, map[SourceDestPair]cciptypes.SeqNumRange, map[SourceDestPair]map[uint64]int, - map[uint64]map[TokenReceiverIdentifier]*big.Int, + map[uint64][]ExpectedTokenBalance, ) { startBlocks := make(map[uint64]*uint64) expectedSeqNums := make(map[SourceDestPair]cciptypes.SeqNumRange) @@ -1416,67 +1417,49 @@ func TransferMultiple( return startBlocks, expectedSeqNums, expectedExecutionStates, expectedTokenBalances } -// TransferAndWaitForSuccess sends a message from sourceChain to destChain and waits for it to be executed -func TransferAndWaitForSuccess( - ctx context.Context, - t *testing.T, - env deployment.Environment, - state changeset.CCIPOnChainState, - sourceChain, destChain uint64, - tokens []router.ClientEVMTokenAmount, - receiver common.Address, - data []byte, - expectedStatus int, - extraArgs []byte, -) { - identifier := SourceDestPair{ - SourceChainSelector: sourceChain, - DestChainSelector: destChain, - } - - expectedSeqNum := make(map[SourceDestPair]uint64) - expectedSeqNumExec := make(map[SourceDestPair][]uint64) - - msgSentEvent, startBlocks := Transfer(ctx, t, env, state, sourceChain, destChain, tokens, receiver, data, extraArgs) - expectedSeqNum[identifier] = msgSentEvent.SequenceNumber - expectedSeqNumExec[identifier] = []uint64{msgSentEvent.SequenceNumber} - - // Wait for all commit reports to land. - ConfirmCommitForAllWithExpectedSeqNums(t, env, state, expectedSeqNum, startBlocks) - - // Wait for all exec reports to land - states := ConfirmExecWithSeqNrsForAll(t, env, state, expectedSeqNumExec, startBlocks) - require.Equal(t, expectedStatus, states[identifier][msgSentEvent.SequenceNumber]) -} - // TokenBalanceAccumulator is a convenient accumulator to aggregate expected balances of different tokens // used across the tests. You can iterate over your test cases and build the final "expected" balances for tokens (per chain, per sender) // For instance, if your test runs multiple transfers for the same token, and you want to verify the balance of tokens at // the end of the execution, you can simply use that struct for aggregating expected tokens // Please also see WaitForTokenBalances to better understand how you can assert token balances -type TokenBalanceAccumulator map[uint64]map[TokenReceiverIdentifier]*big.Int +type TokenBalanceAccumulator map[uint64][]ExpectedTokenBalance func (t TokenBalanceAccumulator) add( destChain uint64, - receiver common.Address, - expectedBalance map[common.Address]*big.Int) { - for token, balance := range expectedBalance { + receiver []byte, + expectedBalances []ExpectedBalance) { + for _, expected := range expectedBalances { + token := expected.Token + balance := expected.Amount tkIdentifier := TokenReceiverIdentifier{token, receiver} - if _, ok := t[destChain]; !ok { - t[destChain] = make(map[TokenReceiverIdentifier]*big.Int) - } - actual, ok := t[destChain][tkIdentifier] - if !ok { - actual = big.NewInt(0) + idx := slices.IndexFunc(t[destChain], func(b ExpectedTokenBalance) bool { + return slices.Equal(b.Receiver.receiver, tkIdentifier.receiver) && slices.Equal(b.Receiver.token, tkIdentifier.token) + }) + + if idx < 0 { + t[destChain] = append(t[destChain], ExpectedTokenBalance{ + Receiver: tkIdentifier, + Amount: balance, + }) + } else { + t[destChain][idx].Amount = new(big.Int).Add(t[destChain][idx].Amount, balance) } - t[destChain][tkIdentifier] = new(big.Int).Add(actual, balance) } } +type ExpectedBalance struct { + Token []byte + Amount *big.Int +} + +type ExpectedTokenBalance struct { + Receiver TokenReceiverIdentifier + Amount *big.Int +} type TokenReceiverIdentifier struct { - token common.Address - receiver common.Address + token []byte + receiver []byte } // WaitForTokenBalances waits for multiple ERC20 tokens to reach a particular balance @@ -1486,16 +1469,33 @@ type TokenReceiverIdentifier struct { func WaitForTokenBalances( ctx context.Context, t *testing.T, - chains map[uint64]deployment.Chain, - expectedBalances map[uint64]map[TokenReceiverIdentifier]*big.Int, + env deployment.Environment, + expectedBalances map[uint64][]ExpectedTokenBalance, ) { errGrp := &errgroup.Group{} - for chainID, tokens := range expectedBalances { - for id, balance := range tokens { - id := id - balance := balance + for chainSelector, tokens := range expectedBalances { + for _, expected := range tokens { + id := expected.Receiver + balance := expected.Amount errGrp.Go(func() error { - WaitForTheTokenBalance(ctx, t, id.token, id.receiver, chains[chainID], balance) + family, err := chainsel.GetSelectorFamily(chainSelector) + if err != nil { + return err + } + + switch family { + case chainsel.FamilyEVM: + token := common.BytesToAddress(id.token) + receiver := common.BytesToAddress(id.receiver) + WaitForTheTokenBalance(ctx, t, token, receiver, env.Chains[chainSelector], balance) + case chainsel.FamilySolana: + expectedBalance := balance.Uint64() + // TODO: need to pass env rather than chains + token := solana.PublicKeyFromBytes(id.token) + receiver := solana.PublicKeyFromBytes(id.receiver) + WaitForTheTokenBalanceSol(ctx, t, token, receiver, env.SolChains[chainSelector], expectedBalance) + default: + } return nil }) } diff --git a/integration-tests/smoke/ccip/ccip_ooo_execution_test.go b/integration-tests/smoke/ccip/ccip_ooo_execution_test.go index aea97dcaf64..ba4c54e9c86 100644 --- a/integration-tests/smoke/ccip/ccip_ooo_execution_test.go +++ b/integration-tests/smoke/ccip/ccip_ooo_execution_test.go @@ -126,7 +126,7 @@ func Test_OutOfOrderExecution(t *testing.T) { sourceChain, destChain, tokenTransfer, - firstReceiver, + firstReceiver.Bytes(), nil, testhelpers.MakeEVMExtraArgsV2(0, true), ) @@ -145,7 +145,7 @@ func Test_OutOfOrderExecution(t *testing.T) { sourceChain, destChain, usdcTransfer, - secondReceiver, + secondReceiver.Bytes(), nil, nil, ) @@ -163,7 +163,7 @@ func Test_OutOfOrderExecution(t *testing.T) { sourceChain, destChain, tokenTransfer, - thirdReceiver, + thirdReceiver.Bytes(), nil, testhelpers.MakeEVMExtraArgsV2(0, false), ) @@ -181,7 +181,7 @@ func Test_OutOfOrderExecution(t *testing.T) { sourceChain, destChain, tokenTransfer, - fourthReceiver, + fourthReceiver.Bytes(), []byte("this message has enough gas to execute"), testhelpers.MakeEVMExtraArgsV2(300_000, true), ) diff --git a/integration-tests/smoke/ccip/ccip_token_transfer_test.go b/integration-tests/smoke/ccip/ccip_token_transfer_test.go index bff8b7d9820..6761394ff43 100644 --- a/integration-tests/smoke/ccip/ccip_token_transfer_test.go +++ b/integration-tests/smoke/ccip/ccip_token_transfer_test.go @@ -6,7 +6,6 @@ import ( "golang.org/x/exp/maps" - "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" @@ -100,9 +99,9 @@ func TestTokenTransfer(t *testing.T) { Amount: oneE18, }, }, - Receiver: utils.RandomAddress(), - ExpectedTokenBalances: map[common.Address]*big.Int{ - destToken.Address(): oneE18, + Receiver: utils.RandomAddress().Bytes(), + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {destToken.Address().Bytes(), oneE18}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, @@ -116,9 +115,9 @@ func TestTokenTransfer(t *testing.T) { Amount: oneE18, }, }, - Receiver: state.Chains[destChain].Receiver.Address(), - ExpectedTokenBalances: map[common.Address]*big.Int{ - destToken.Address(): oneE18, + Receiver: state.Chains[destChain].Receiver.Address().Bytes(), + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {destToken.Address().Bytes(), oneE18}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, @@ -140,11 +139,11 @@ func TestTokenTransfer(t *testing.T) { Amount: oneE18, }, }, - Receiver: state.Chains[sourceChain].Receiver.Address(), + Receiver: state.Chains[sourceChain].Receiver.Address().Bytes(), ExtraArgs: testhelpers.MakeEVMExtraArgsV2(300_000, false), - ExpectedTokenBalances: map[common.Address]*big.Int{ - selfServeSrcToken.Address(): new(big.Int).Add(oneE18, oneE18), - srcToken.Address(): oneE18, + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {selfServeSrcToken.Address().Bytes(), new(big.Int).Add(oneE18, oneE18)}, + {srcToken.Address().Bytes(), oneE18}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, @@ -162,11 +161,11 @@ func TestTokenTransfer(t *testing.T) { Amount: new(big.Int).Add(oneE18, oneE18), }, }, - Receiver: utils.RandomAddress(), + Receiver: utils.RandomAddress().Bytes(), ExtraArgs: testhelpers.MakeEVMExtraArgsV2(1, false), - ExpectedTokenBalances: map[common.Address]*big.Int{ - selfServeSrcToken.Address(): oneE18, - srcToken.Address(): new(big.Int).Add(oneE18, oneE18), + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {selfServeSrcToken.Address().Bytes(), oneE18}, + {srcToken.Address().Bytes(), new(big.Int).Add(oneE18, oneE18)}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, @@ -184,12 +183,12 @@ func TestTokenTransfer(t *testing.T) { Amount: oneE18, }, }, - Receiver: state.Chains[sourceChain].Receiver.Address(), + Receiver: state.Chains[sourceChain].Receiver.Address().Bytes(), Data: []byte("this should be reverted because gasLimit is too low, no tokens are transferred as well"), ExtraArgs: testhelpers.MakeEVMExtraArgsV2(1, false), - ExpectedTokenBalances: map[common.Address]*big.Int{ - selfServeSrcToken.Address(): big.NewInt(0), - srcToken.Address(): big.NewInt(0), + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {selfServeSrcToken.Address().Bytes(), big.NewInt(0)}, + {srcToken.Address().Bytes(), big.NewInt(0)}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_FAILURE, }, diff --git a/integration-tests/smoke/ccip/ccip_usdc_test.go b/integration-tests/smoke/ccip/ccip_usdc_test.go index 0950ebe50eb..ea21ad1bb93 100644 --- a/integration-tests/smoke/ccip/ccip_usdc_test.go +++ b/integration-tests/smoke/ccip/ccip_usdc_test.go @@ -4,7 +4,6 @@ import ( "math/big" "testing" - "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -102,7 +101,7 @@ func TestUSDCTokenTransfer(t *testing.T) { tcs := []testhelpers.TestTransferRequest{ { Name: "single USDC token transfer to EOA", - Receiver: utils.RandomAddress(), + Receiver: utils.RandomAddress().Bytes(), SourceChain: chainC, DestChain: chainA, Tokens: []router.ClientEVMTokenAmount{ @@ -110,14 +109,14 @@ func TestUSDCTokenTransfer(t *testing.T) { Token: cChainUSDC.Address(), Amount: tinyOneCoin, }}, - ExpectedTokenBalances: map[common.Address]*big.Int{ - aChainUSDC.Address(): tinyOneCoin, + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {aChainUSDC.Address().Bytes(), tinyOneCoin}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, { Name: "multiple USDC tokens within the same message", - Receiver: utils.RandomAddress(), + Receiver: utils.RandomAddress().Bytes(), SourceChain: chainC, DestChain: chainA, Tokens: []router.ClientEVMTokenAmount{ @@ -130,15 +129,15 @@ func TestUSDCTokenTransfer(t *testing.T) { Amount: tinyOneCoin, }, }, - ExpectedTokenBalances: map[common.Address]*big.Int{ + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ // 2 coins because of the same Receiver - aChainUSDC.Address(): new(big.Int).Add(tinyOneCoin, tinyOneCoin), + {aChainUSDC.Address().Bytes(), new(big.Int).Add(tinyOneCoin, tinyOneCoin)}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, { Name: "USDC token together with another token transferred to EOA", - Receiver: utils.RandomAddress(), + Receiver: utils.RandomAddress().Bytes(), SourceChain: chainA, DestChain: chainC, Tokens: []router.ClientEVMTokenAmount{ @@ -151,15 +150,15 @@ func TestUSDCTokenTransfer(t *testing.T) { Amount: new(big.Int).Mul(tinyOneCoin, big.NewInt(10)), }, }, - ExpectedTokenBalances: map[common.Address]*big.Int{ - cChainUSDC.Address(): tinyOneCoin, - cChainToken.Address(): new(big.Int).Mul(tinyOneCoin, big.NewInt(10)), + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {cChainUSDC.Address().Bytes(), tinyOneCoin}, + {cChainToken.Address().Bytes(), new(big.Int).Mul(tinyOneCoin, big.NewInt(10))}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, { Name: "USDC programmable token transfer to valid contract receiver", - Receiver: state.Chains[chainC].Receiver.Address(), + Receiver: state.Chains[chainC].Receiver.Address().Bytes(), SourceChain: chainA, DestChain: chainC, Tokens: []router.ClientEVMTokenAmount{ @@ -169,14 +168,14 @@ func TestUSDCTokenTransfer(t *testing.T) { }, }, Data: []byte("hello world"), - ExpectedTokenBalances: map[common.Address]*big.Int{ - cChainUSDC.Address(): tinyOneCoin, + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {cChainUSDC.Address().Bytes(), tinyOneCoin}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, { Name: "USDC programmable token transfer with too little gas", - Receiver: state.Chains[chainB].Receiver.Address(), + Receiver: state.Chains[chainB].Receiver.Address().Bytes(), SourceChain: chainC, DestChain: chainB, Tokens: []router.ClientEVMTokenAmount{ @@ -186,15 +185,15 @@ func TestUSDCTokenTransfer(t *testing.T) { }, }, Data: []byte("gimme more gas to execute that!"), - ExpectedTokenBalances: map[common.Address]*big.Int{ - bChainUSDC.Address(): new(big.Int).SetUint64(0), + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {bChainUSDC.Address().Bytes(), new(big.Int).SetUint64(0)}, }, ExtraArgs: testhelpers.MakeEVMExtraArgsV2(1, false), ExpectedStatus: testhelpers.EXECUTION_STATE_FAILURE, }, { Name: "USDC token transfer from a different source chain", - Receiver: utils.RandomAddress(), + Receiver: utils.RandomAddress().Bytes(), SourceChain: chainB, DestChain: chainC, Tokens: []router.ClientEVMTokenAmount{ @@ -204,8 +203,8 @@ func TestUSDCTokenTransfer(t *testing.T) { }, }, Data: nil, - ExpectedTokenBalances: map[common.Address]*big.Int{ - cChainUSDC.Address(): tinyOneCoin, + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {cChainUSDC.Address().Bytes(), tinyOneCoin}, }, ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, }, @@ -233,7 +232,7 @@ func TestUSDCTokenTransfer(t *testing.T) { ) require.Equal(t, expectedExecutionStates, execStates) - testhelpers.WaitForTokenBalances(ctx, t, e.Chains, expectedTokenBalances) + testhelpers.WaitForTokenBalances(ctx, t, e, expectedTokenBalances) } func updateFeeQuoters( From 70297c51c72af9cec0b275f54864d655f7bada35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 7 Feb 2025 14:28:54 +0900 Subject: [PATCH 17/62] Add TestTokenTransfer_Solana --- .../smoke/ccip/ccip_token_transfer_test.go | 146 +++++++++++++++++- 1 file changed, 145 insertions(+), 1 deletion(-) diff --git a/integration-tests/smoke/ccip/ccip_token_transfer_test.go b/integration-tests/smoke/ccip/ccip_token_transfer_test.go index 6761394ff43..9667be0027f 100644 --- a/integration-tests/smoke/ccip/ccip_token_transfer_test.go +++ b/integration-tests/smoke/ccip/ccip_token_transfer_test.go @@ -216,5 +216,149 @@ func TestTokenTransfer(t *testing.T) { ) require.Equal(t, expectedExecutionStates, execStates) - testhelpers.WaitForTokenBalances(ctx, t, e.Chains, expectedTokenBalances) + testhelpers.WaitForTokenBalances(ctx, t, e, expectedTokenBalances) +} + +func TestTokenTransfer_Solana(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := tests.Context(t) + + tenv, _, _ := testsetups.NewIntegrationEnvironment(t, + testhelpers.WithNumOfUsersPerChain(3), + testhelpers.WithSolChains(1)) + + e := tenv.Env + state, err := changeset.LoadOnchainState(e) + require.NoError(t, err) + require.GreaterOrEqual(t, len(e.Chains), 2) + + allChainSelectors := maps.Keys(e.Chains) + allSolChainSelectors := maps.Keys(e.SolChains) + sourceChain, destChain := allChainSelectors[0], allSolChainSelectors[0] + ownerSourceChain := e.Chains[sourceChain].DeployerKey + // ownerDestChain := e.SolChains[destChain].DeployerKey + + require.GreaterOrEqual(t, len(tenv.Users[sourceChain]), 2) + // require.GreaterOrEqual(t, len(tenv.Users[destChain]), 2) TODO: ??? + // selfServeSrcTokenPoolDeployer := tenv.Users[sourceChain][1] + // selfServeDestTokenPoolDeployer := tenv.Users[destChain][1] + + oneE18 := new(big.Int).SetUint64(1e18) + + // Deploy tokens and pool by CCIP Owner + srcToken, _, destToken, err := testhelpers.DeployTransferableTokenSolana( + t, + lggr, + e, + sourceChain, + destChain, + ownerSourceChain, + e.ExistingAddresses, + "OWNER_TOKEN", + ) + require.NoError(t, err) + + // TODO: we need to initialize ATA for receiver? + + // Deploy Self Serve tokens and pool + // selfServeSrcToken, _, selfServeDestToken, _, err := testhelpers.DeployTransferableToken( + // lggr, + // tenv.Env.Chains, + // sourceChain, + // destChain, + // selfServeSrcTokenPoolDeployer, + // selfServeDestTokenPoolDeployer, + // state, + // e.ExistingAddresses, + // "SELF_SERVE_TOKEN", + // ) + // require.NoError(t, err) + // + testhelpers.AddLanesForAll(t, &tenv, state) + + testhelpers.MintAndAllow( + t, + e, + state, + map[uint64][]testhelpers.MintTokenInfo{ + sourceChain: { + // testhelpers.NewMintTokenInfo(selfServeSrcTokenPoolDeployer, selfServeSrcToken), + testhelpers.NewMintTokenInfo(ownerSourceChain, srcToken), + }, + // destChain: { + // // testhelpers.NewMintTokenInfo(selfServeDestTokenPoolDeployer, selfServeDestToken), + // testhelpers.NewMintTokenInfo(ownerDestChain, destToken), + // }, + }, + ) + // TODO: how to do MintAndAllow on Solana? + + tcs := []testhelpers.TestTransferRequest{ + { + Name: "Send token to contract", + SourceChain: sourceChain, + DestChain: destChain, + Tokens: []router.ClientEVMTokenAmount{ + { + Token: srcToken.Address(), + Amount: oneE18, + }, + }, + Receiver: state.Chains[destChain].Receiver.Address().Bytes(), + ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + {destToken.Bytes(), oneE18}, + }, + ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, + }, + // { + // Name: "Send N tokens to contract", + // SourceChain: destChain, + // DestChain: sourceChain, + // Tokens: []router.ClientEVMTokenAmount{ + // { + // Token: selfServeDestToken.Address(), + // Amount: oneE18, + // }, + // { + // Token: destToken.Address(), + // Amount: oneE18, + // }, + // { + // Token: selfServeDestToken.Address(), + // Amount: oneE18, + // }, + // }, + // Receiver: state.Chains[sourceChain].Receiver.Address().Bytes(), + // ExtraArgs: testhelpers.MakeEVMExtraArgsV2(300_000, false), + // ExpectedTokenBalances: []testhelpers.ExpectedBalance{ + // {selfServeSrcToken.Address().Bytes(), new(big.Int).Add(oneE18, oneE18)}, + // {srcToken.Address().Bytes(), oneE18}, + // }, + // ExpectedStatus: testhelpers.EXECUTION_STATE_SUCCESS, + // }, + } + + startBlocks, expectedSeqNums, expectedExecutionStates, expectedTokenBalances := + testhelpers.TransferMultiple(ctx, t, e, state, tcs) + + err = testhelpers.ConfirmMultipleCommits( + t, + e, + state, + startBlocks, + false, + expectedSeqNums, + ) + require.NoError(t, err) + + execStates := testhelpers.ConfirmExecWithSeqNrsForAll( + t, + e, + state, + testhelpers.SeqNumberRangeToSlice(expectedSeqNums), + startBlocks, + ) + require.Equal(t, expectedExecutionStates, execStates) + + testhelpers.WaitForTokenBalances(ctx, t, e, expectedTokenBalances) } From b8ef636f9fa7aae22227ef07e95a12a3a76e11d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 10 Feb 2025 14:42:46 +0900 Subject: [PATCH 18/62] nonce account may not exist yet --- deployment/ccip/changeset/testhelpers/messagingtest/helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go index 862ad61061b..f1ced004140 100644 --- a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go +++ b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go @@ -134,7 +134,7 @@ func getLatestNonce(tc TestCase) uint64 { require.NoError(tc.T, err) var nonceCounterAccount ccip_router.Nonce err = solcommon.GetAccountDataBorshInto(ctx, client, noncePDA, solconfig.DefaultCommitment, &nonceCounterAccount) - require.NoError(tc.T, err, "failed to get nonce account info") + // require.NoError(tc.T, err, "failed to get nonce account info") TODO: this account could be missing before first call? latestNonce = nonceCounterAccount.Counter } return latestNonce From 60d871db20cb57a81418528c429185d5b1c859b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 11 Feb 2025 15:24:56 +0900 Subject: [PATCH 19/62] TEMP: disable Solana token transfer test until code is merged --- .../smoke/ccip/ccip_token_transfer_test.go | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_token_transfer_test.go b/integration-tests/smoke/ccip/ccip_token_transfer_test.go index 9667be0027f..53d61e6af05 100644 --- a/integration-tests/smoke/ccip/ccip_token_transfer_test.go +++ b/integration-tests/smoke/ccip/ccip_token_transfer_test.go @@ -6,6 +6,7 @@ import ( "golang.org/x/exp/maps" + "github.com/gagliardetto/solana-go" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" @@ -15,7 +16,9 @@ import ( "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_2_0/router" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -220,7 +223,7 @@ func TestTokenTransfer(t *testing.T) { } func TestTokenTransfer_Solana(t *testing.T) { - lggr := logger.TestLogger(t) + // lggr := logger.TestLogger(t) ctx := tests.Context(t) tenv, _, _ := testsetups.NewIntegrationEnvironment(t, @@ -246,17 +249,20 @@ func TestTokenTransfer_Solana(t *testing.T) { oneE18 := new(big.Int).SetUint64(1e18) // Deploy tokens and pool by CCIP Owner - srcToken, _, destToken, err := testhelpers.DeployTransferableTokenSolana( - t, - lggr, - e, - sourceChain, - destChain, - ownerSourceChain, - e.ExistingAddresses, - "OWNER_TOKEN", - ) - require.NoError(t, err) + var srcToken *burn_mint_erc677.BurnMintERC677 + var destToken solana.PublicKey + // TODO: + // srcToken, _, destToken, err := testhelpers.DeployTransferableTokenSolana( + // t, + // lggr, + // e, + // sourceChain, + // destChain, + // ownerSourceChain, + // e.ExistingAddresses, + // "OWNER_TOKEN", + // ) + // require.NoError(t, err) // TODO: we need to initialize ATA for receiver? From 53b827630831b8d771d1b19f757b97dc9c7d4b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 11 Feb 2025 16:52:36 +0900 Subject: [PATCH 20/62] fix: Use correct chain family when fetching CR config --- core/capabilities/ccip/oraclecreator/plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index e11d913ed52..1c5c76a9b13 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -427,7 +427,7 @@ func (i *pluginOracleCreator) createReadersAndWriters( return nil, nil, fmt.Errorf("failed to get chain selector from chain ID %s: %w", chainID, err1) } - chainReaderConfig, err1 := getChainReaderConfig(i.lggr, chainID, destChainID, homeChainID, ofc, chainSelector, destChainFamily) + chainReaderConfig, err1 := getChainReaderConfig(i.lggr, chainID, destChainID, homeChainID, ofc, chainSelector, relayChainFamily) if err1 != nil { return nil, nil, fmt.Errorf("failed to get chain reader config: %w", err1) } From 5201829e57b8629911452984de77fc3b30a86d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 11 Feb 2025 16:52:53 +0900 Subject: [PATCH 21/62] oraclecreator: Only initialize CR/CW instances for relayers with CCIP support --- core/capabilities/ccip/oraclecreator/plugin.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index 1c5c76a9b13..dcc8feb2a17 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -427,6 +427,11 @@ func (i *pluginOracleCreator) createReadersAndWriters( return nil, nil, fmt.Errorf("failed to get chain selector from chain ID %s: %w", chainID, err1) } + if _, exists := plugins[relayChainFamily]; !exists { + i.lggr.Debugw("createReadersAndWriters: skipping unsupported relayer", "chainID", chainID, "family", relayChainFamily) + continue + } + chainReaderConfig, err1 := getChainReaderConfig(i.lggr, chainID, destChainID, homeChainID, ofc, chainSelector, relayChainFamily) if err1 != nil { return nil, nil, fmt.Errorf("failed to get chain reader config: %w", err1) From 9de9b85f53368c14b4a25324b0cb95a9b48bb950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 11 Feb 2025 19:41:38 +0900 Subject: [PATCH 22/62] Rename DoSendRequest to SendRequest --- .../changeset/testhelpers/test_helpers.go | 23 +++++++++---------- .../smoke/ccip/ccip_disable_lane_test.go | 2 +- .../ccip/ccip_message_limitations_test.go | 2 +- .../ccip/ccip_migration_to_v_1_6_test.go | 2 +- .../smoke/ccip/ccip_ooo_execution_test.go | 2 +- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index 4187d568d4c..cab57b32828 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -230,13 +230,7 @@ func CCIPSendRequest( state changeset.CCIPOnChainState, cfg *CCIPSendReqConfig, ) (*types.Transaction, uint64, error) { - msg := router.ClientEVM2AnyMessage{ - Receiver: cfg.Evm2AnyMessage.Receiver, - Data: cfg.Evm2AnyMessage.Data, - TokenAmounts: cfg.Evm2AnyMessage.TokenAmounts, - FeeToken: cfg.Evm2AnyMessage.FeeToken, - ExtraArgs: cfg.Evm2AnyMessage.ExtraArgs, - } + msg := cfg.Evm2AnyMessage r := state.Chains[cfg.SourceChain].Router if cfg.IsTestRouter { r = state.Chains[cfg.SourceChain].TestRouter @@ -309,20 +303,25 @@ func CCIPSendCalldata( return calldata, nil } +// testhelpers.SendRequest(t, e, state, src, dest, msg, opts...) +// opts being testRouter, sender +// always return error +// note: there's also DoSendRequest vs SendRequest duplication, v1.6 vs v1.5 + func TestSendRequest( t *testing.T, e deployment.Environment, state changeset.CCIPOnChainState, src, dest uint64, testRouter bool, - evm2AnyMessage router.ClientEVM2AnyMessage, + msg any, ) (msgSentEvent *onramp.OnRampCCIPMessageSent) { - msgSentEvent, err := DoSendRequest(t, e, state, + msgSentEvent, err := SendRequest(t, e, state, WithSender(e.Chains[src].DeployerKey), WithSourceChain(src), WithDestChain(dest), WithTestRouter(testRouter), - WithEvm2AnyMessage(evm2AnyMessage)) + WithEvm2AnyMessage(msg.(router.ClientEVM2AnyMessage))) require.NoError(t, err) return msgSentEvent } @@ -367,8 +366,8 @@ func WithDestChain(destChain uint64) SendReqOpts { } } -// DoSendRequest similar to TestSendRequest but returns an error. -func DoSendRequest( +// SendRequest similar to TestSendRequest but returns an error. +func SendRequest( t *testing.T, e deployment.Environment, state changeset.CCIPOnChainState, diff --git a/integration-tests/smoke/ccip/ccip_disable_lane_test.go b/integration-tests/smoke/ccip/ccip_disable_lane_test.go index e6381100d3a..b19e05a5477 100644 --- a/integration-tests/smoke/ccip/ccip_disable_lane_test.go +++ b/integration-tests/smoke/ccip/ccip_disable_lane_test.go @@ -46,7 +46,7 @@ func TestDisableLane(t *testing.T) { wethPrice = deployment.E18Mult(4000) noOfRequests = 3 sendmessage = func(src, dest uint64, deployer *bind.TransactOpts) (*onramp.OnRampCCIPMessageSent, error) { - return testhelpers.DoSendRequest( + return testhelpers.SendRequest( t, e, state, diff --git a/integration-tests/smoke/ccip/ccip_message_limitations_test.go b/integration-tests/smoke/ccip/ccip_message_limitations_test.go index 11682f8003c..c357555adb4 100644 --- a/integration-tests/smoke/ccip/ccip_message_limitations_test.go +++ b/integration-tests/smoke/ccip/ccip_message_limitations_test.go @@ -152,7 +152,7 @@ func Test_CCIPMessageLimitations(t *testing.T) { t.Logf("Sending msg: %s", msg.name) require.NotEqual(t, msg.fromChain, msg.toChain, "fromChain and toChain cannot be the same") startBlocks[msg.toChain] = nil - msgSentEvent, err := testhelpers.DoSendRequest( + msgSentEvent, err := testhelpers.SendRequest( t, testEnv.Env, onChainState, testhelpers.WithSourceChain(msg.fromChain), testhelpers.WithDestChain(msg.toChain), diff --git a/integration-tests/smoke/ccip/ccip_migration_to_v_1_6_test.go b/integration-tests/smoke/ccip/ccip_migration_to_v_1_6_test.go index ec689da2a13..b3a2a2f0542 100644 --- a/integration-tests/smoke/ccip/ccip_migration_to_v_1_6_test.go +++ b/integration-tests/smoke/ccip/ccip_migration_to_v_1_6_test.go @@ -251,7 +251,7 @@ func TestMigrateFromV1_5ToV1_6(t *testing.T) { startBlocks[dest] = &block expectedSeqNumExec := make(map[testhelpers.SourceDestPair][]uint64) expectedSeqNums := make(map[testhelpers.SourceDestPair]uint64) - msgSentEvent, err := testhelpers.DoSendRequest( + msgSentEvent, err := testhelpers.SendRequest( t, e.Env, state, testhelpers.WithSourceChain(src1), testhelpers.WithDestChain(dest), diff --git a/integration-tests/smoke/ccip/ccip_ooo_execution_test.go b/integration-tests/smoke/ccip/ccip_ooo_execution_test.go index ba4c54e9c86..010aa49ca45 100644 --- a/integration-tests/smoke/ccip/ccip_ooo_execution_test.go +++ b/integration-tests/smoke/ccip/ccip_ooo_execution_test.go @@ -192,7 +192,7 @@ func Test_OutOfOrderExecution(t *testing.T) { // Ordered token transfer, but using different sender, should be executed fifthReceiver := utils.RandomAddress() - fifthMessage, err := testhelpers.DoSendRequest(t, e, state, + fifthMessage, err := testhelpers.SendRequest(t, e, state, testhelpers.WithSender(anotherSender), testhelpers.WithSourceChain(sourceChain), testhelpers.WithDestChain(destChain), From 2f7ca515577d0cb070ef3fa3d92013613ad2efa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 12 Feb 2025 18:18:05 +0900 Subject: [PATCH 23/62] Avoid panic on solana getting passed EVm offramp address --- core/capabilities/ccip/oraclecreator/plugin.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index dcc8feb2a17..4bbe99743cf 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -594,7 +594,7 @@ func createChainWriter( transmitters map[types.RelayID][]string, execBatchGasLimit uint64, chainFamily string, - offrampProgramAddress []byte, + offrampAddress []byte, destChainSelector uint64, ) (types.ContractWriter, error) { var err error @@ -604,8 +604,12 @@ func createChainWriter( switch chainFamily { case relay.NetworkSolana: var solConfig chainwriter.ChainWriterConfig - offrampAddress := solana.PublicKeyFromBytes(offrampProgramAddress) - if solConfig, err = solanaconfig.GetSolanaChainWriterConfig(offrampAddress.String(), transmitter[0], destChainSelector); err == nil { + var offrampProgramAddress solana.PublicKey + // TODO: this function can still be called with EVM inputs, and PublicKeyFromBytes will panic on addresses with len=20 + if len(offrampAddress) == solana.PublicKeyLength { + offrampProgramAddress = solana.PublicKeyFromBytes(offrampAddress) + } + if solConfig, err = solanaconfig.GetSolanaChainWriterConfig(offrampProgramAddress.String(), transmitter[0], destChainSelector); err != nil { return nil, fmt.Errorf("failed to get Solana chain writer config: %w", err) } if chainWriterConfig, err = json.Marshal(solConfig); err != nil { From 66078bff97c16e0b67953b6dceb343bb4f09f48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 13 Feb 2025 16:44:16 +0900 Subject: [PATCH 24/62] fix: use string encoding for EVM accounts too, fixes Solana addrs being EVM encoded --- core/capabilities/ccip/ocrimpls/config_tracker.go | 5 ++--- deployment/ccip/changeset/internal/deploy_home_chain.go | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/core/capabilities/ccip/ocrimpls/config_tracker.go b/core/capabilities/ccip/ocrimpls/config_tracker.go index c70aa50030a..c5bf991e7c3 100644 --- a/core/capabilities/ccip/ocrimpls/config_tracker.go +++ b/core/capabilities/ccip/ocrimpls/config_tracker.go @@ -5,7 +5,6 @@ import ( cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - gethcommon "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) @@ -75,8 +74,8 @@ func toOnchainPublicKeys(signers [][]byte) []types.OnchainPublicKey { func toOCRAccounts(transmitters [][]byte) []types.Account { accounts := make([]types.Account, len(transmitters)) for i, transmitter := range transmitters { - // TODO: string-encode the transmitter appropriately to the dest chain family. - accounts[i] = types.Account(gethcommon.BytesToAddress(transmitter).Hex()) + // transmitters have chain family specific encoding so we keep them as strings + accounts[i] = types.Account(transmitter) } return accounts } diff --git a/deployment/ccip/changeset/internal/deploy_home_chain.go b/deployment/ccip/changeset/internal/deploy_home_chain.go index ed693c9c693..9b2968553b0 100644 --- a/deployment/ccip/changeset/internal/deploy_home_chain.go +++ b/deployment/ccip/changeset/internal/deploy_home_chain.go @@ -408,11 +408,7 @@ func BuildOCR3ConfigForCCIPHome( transmittersBytes := make([][]byte, len(transmitters)) for i, transmitter := range transmitters { - parsed, err2 := common.ParseHexOrString(string(transmitter)) - if err2 != nil { - return nil, err2 - } - transmittersBytes[i] = parsed + transmittersBytes[i] = []byte(transmitter) } // validate ocr3 params correctness _, err := ocr3confighelper.PublicConfigFromContractConfig(false, ocrtypes.ContractConfig{ From 2ce68ee8d92332059628a2a2184938dc958364f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 13 Feb 2025 16:44:46 +0900 Subject: [PATCH 25/62] Don't fail if the block doesn't contain that event type --- deployment/ccip/changeset/testhelpers/test_assertions.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index c2ff89ab3f4..d6494e49c4d 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -6,6 +6,7 @@ import ( "fmt" "math/big" "slices" + "strings" "sync" "testing" "time" @@ -496,7 +497,11 @@ func SolEventEmitter[T any]( require.NotNil(t, tx) var event T - require.NoError(t, solcommon.ParseEvent(tx.Meta.LogMessages, eventType, &event, solconfig.PrintEvents)) + err = solcommon.ParseEvent(tx.Meta.LogMessages, eventType, &event, solconfig.PrintEvents) + if err != nil && strings.Contains(err.Error(), "event not found") { + continue + } + require.NoError(t, err) select { case ch <- event: From 72a773794425d11a036ce8703e9ce40e6e037775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 14 Feb 2025 13:30:37 +0900 Subject: [PATCH 26/62] solana: Handle OCR3 producing empty reports --- core/capabilities/ccip/ccipsolana/executecodec.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/capabilities/ccip/ccipsolana/executecodec.go b/core/capabilities/ccip/ccipsolana/executecodec.go index ad6efebf04f..dd38197fe00 100644 --- a/core/capabilities/ccip/ccipsolana/executecodec.go +++ b/core/capabilities/ccip/ccipsolana/executecodec.go @@ -30,6 +30,13 @@ func NewExecutePluginCodecV1(extraDataCodec common.ExtraDataCodec) *ExecutePlugi } func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.ExecutePluginReport) ([]byte, error) { + if len(report.ChainReports) == 0 { + // OCR3 runs in a constant loop and will produce empty reports, so we need to handle this case + // return an empty report, CCIP will discard it on ShouldAcceptAttestedReport/ShouldTransmitAcceptedReport + // via validateReport before attempting to decode + return nil, nil + } + if len(report.ChainReports) != 1 { return nil, fmt.Errorf("unexpected chain report length: %d", len(report.ChainReports)) } From 8555f5f6ac9b74c6fc78ce17dd5782922ed3b321 Mon Sep 17 00:00:00 2001 From: Terry Tata Date: Fri, 14 Feb 2025 16:39:07 -0500 Subject: [PATCH 27/62] image hotfix --- deployment/environment/memory/chain.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deployment/environment/memory/chain.go b/deployment/environment/memory/chain.go index 63a1fa79d43..1c5105f2978 100644 --- a/deployment/environment/memory/chain.go +++ b/deployment/environment/memory/chain.go @@ -8,6 +8,7 @@ import ( "math/big" "os" "path/filepath" + "runtime" "strconv" "sync" "testing" @@ -253,8 +254,13 @@ func solChain(t *testing.T, chainID uint64, adminKey *solana.PrivateKey) (string for i := 0; i < maxRetries; i++ { port := freeport.GetOne(t) + image := "" + if runtime.GOOS == "linux" { + image = "solanalabs/solana:v1.18.26" // TODO: workaround on linux + } + bcInput := &blockchain.Input{ - Image: "solanalabs/solana:v1.18.26", // TODO: workaround on linux + Image: image, Type: "solana", ChainID: strconv.FormatUint(chainID, 10), PublicKey: adminKey.PublicKey().String(), From 3702e7005ad6965141a791a6c132833d6925638c Mon Sep 17 00:00:00 2001 From: ilija Date: Sat, 15 Feb 2025 10:31:14 +0100 Subject: [PATCH 28/62] Fix codec modifiers for OffRamp Source Chain Config --- core/capabilities/ccip/configs/solana/contract_reader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 3896b2ae9fa..1bfa945afde 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -127,7 +127,7 @@ func DestContractReaderConfig() (config.ContractReader, error) { // // OnRamp addresses supported from the source chain, each of them has a 64 byte address. So this can hold 2 addresses. // // If only one address is configured, then the space for the second address must be zeroed. // // Each address must be right padded with zeros if it is less than 64 bytes. - &codec.ElementExtractorModifierConfig{Extractions: map[string]*codec.ElementExtractorLocation{"OnRamp": &locationFirst}}, + &codec.ElementExtractorFromOnchainModifierConfig{Extractions: map[string]*codec.ElementExtractorLocation{"OnRamp": &locationFirst}}, }, MultiReader: &config.MultiReader{ ReuseParams: true, From a8c66ae008a6cc3c4ffdaaac0fe8769d3f8be5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 16 Feb 2025 17:51:29 +0900 Subject: [PATCH 29/62] Allow commit with price updates only, no merkle roots --- core/capabilities/ccip/ccipsolana/commitcodec.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/capabilities/ccip/ccipsolana/commitcodec.go b/core/capabilities/ccip/ccipsolana/commitcodec.go index 06f8b7d7845..151c3a5bd68 100644 --- a/core/capabilities/ccip/ccipsolana/commitcodec.go +++ b/core/capabilities/ccip/ccipsolana/commitcodec.go @@ -28,11 +28,17 @@ func (c *CommitPluginCodecV1) Encode(ctx context.Context, report cciptypes.Commi encoder := agbinary.NewBorshEncoder(&buf) combinedRoots := report.BlessedMerkleRoots combinedRoots = append(combinedRoots, report.UnblessedMerkleRoots...) - if len(combinedRoots) != 1 { + var merkleRoot cciptypes.MerkleRootChain + switch len(combinedRoots) { + case 0: + // price updates only, zero the root + case 1: + // valid + merkleRoot = combinedRoots[0] + default: return nil, fmt.Errorf("unexpected merkle root length in report: %d", len(combinedRoots)) } - merkleRoot := combinedRoots[0] mr := &ccip_offramp.MerkleRoot{ SourceChainSelector: uint64(merkleRoot.ChainSel), OnRampAddress: merkleRoot.OnRampAddress, From bfcd6a06ded0502e820a17520ad23601deff3657 Mon Sep 17 00:00:00 2001 From: ilija Date: Sun, 16 Feb 2025 11:10:44 +0100 Subject: [PATCH 30/62] Fix MethodNameGetLatestPriceSequenceNumber cfg --- core/capabilities/ccip/configs/solana/contract_reader.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 1bfa945afde..642dd122adb 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -72,9 +72,10 @@ func DestContractReaderConfig() (config.ContractReader, error) { ReadType: config.Account, PDADefinition: solanacodec.PDATypeDef{Prefix: []byte("state")}, OutputModifications: codec.ModifiersConfig{ - &codec.RenameModifierConfig{ - Fields: map[string]string{"LatestPriceSequenceNumber": "LatestSeqNr"}, - }}, + &codec.PropertyExtractorConfig{ + FieldName: "LatestPriceSequenceNumber", + }, + }, }, consts.MethodNameOffRampGetStaticConfig: { ChainSpecificName: "Config", From 65789f891a0ecfa62f46a9815dfab3842c082bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 17 Feb 2025 14:31:32 +0900 Subject: [PATCH 31/62] Disable RMN verification on Solana destination --- core/capabilities/ccip/configs/solana/contract_reader.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 642dd122adb..8302cec1ade 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -101,6 +101,8 @@ func DestContractReaderConfig() (config.ContractReader, error) { &codec.RenameModifierConfig{ Fields: map[string]string{"EnableManualExecutionAfter": "PermissionLessExecutionThresholdSeconds"}, }, + // TODO: figure out how this will be properly configured, if it has to be added to SVM state + &codec.HardCodeModifierConfig{OffChainValues: map[string]any{"IsRMNVerificationDisabled": true}}, }, MultiReader: &config.MultiReader{ Reads: []config.ReadDefinition{ @@ -129,6 +131,8 @@ func DestContractReaderConfig() (config.ContractReader, error) { // // If only one address is configured, then the space for the second address must be zeroed. // // Each address must be right padded with zeros if it is less than 64 bytes. &codec.ElementExtractorFromOnchainModifierConfig{Extractions: map[string]*codec.ElementExtractorLocation{"OnRamp": &locationFirst}}, + // TODO: figure out how this will be properly configured, if it has to be added to SVM state + &codec.HardCodeModifierConfig{OffChainValues: map[string]any{"IsRMNVerificationDisabled": true}}, }, MultiReader: &config.MultiReader{ ReuseParams: true, From e0b3019e810d495168172daddb92a5ec65d592a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 17 Feb 2025 18:27:40 +0900 Subject: [PATCH 32/62] CR needs to read both Config and State properties of the config --- .../ccip/configs/solana/contract_reader.go | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 8302cec1ade..ea3def6fb31 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -144,6 +144,19 @@ func DestContractReaderConfig() (config.ContractReader, error) { Prefix: []byte("reference_addresses"), }, }, + { + // this seems like a hack to extract both State and Config fields? + ChainSpecificName: "SourceChain", + ReadType: config.Account, + PDADefinition: solanacodec.PDATypeDef{ + Prefix: []byte("source_chain_state"), + Seeds: []solanacodec.PDASeed{{Name: "NewChainSelector", Type: solanacodec.IdlType{AsString: solanacodec.IdlTypeU64}}}, + }, + InputModifications: codec.ModifiersConfig{&codec.RenameModifierConfig{Fields: map[string]string{"NewChainSelector": "SourceChainSelector"}}}, + OutputModifications: codec.ModifiersConfig{ + &codec.PropertyExtractorConfig{FieldName: "State"}, + }, + }, }, }, }, @@ -226,6 +239,23 @@ func DestContractReaderConfig() (config.ContractReader, error) { }, }, }, + MultiReader: &config.MultiReader{ + ReuseParams: true, + Reads: []config.ReadDefinition{ + { + // this seems like a hack to extract both State and Config fields? + ChainSpecificName: "DestChain", + PDADefinition: solanacodec.PDATypeDef{ + Prefix: []byte("dest_chain"), + Seeds: []solanacodec.PDASeed{{Name: "DestinationChainSelector", Type: solanacodec.IdlType{AsString: solanacodec.IdlTypeU64}}}, + }, + InputModifications: codec.ModifiersConfig{&codec.RenameModifierConfig{Fields: map[string]string{"DestinationChainSelector": "DestChainSelector"}}}, + OutputModifications: codec.ModifiersConfig{ + &codec.PropertyExtractorConfig{FieldName: "State"}, + }, + }, + }, + }, }, }, }, @@ -340,6 +370,7 @@ func SourceContractReaderConfig() (config.ContractReader, error) { MultiReader: &config.MultiReader{ ReuseParams: true, Reads: []config.ReadDefinition{ + // this seems like a hack to extract both State and Config fields? { ChainSpecificName: "DestChain", ReadType: config.Account, From 0066e0bd66c376715dbda0013b17599d9086165d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 17 Feb 2025 18:48:47 +0900 Subject: [PATCH 33/62] ccip: solan: Handle merkle root being zeroed on price updates --- core/capabilities/ccip/ccipsolana/commitcodec.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/capabilities/ccip/ccipsolana/commitcodec.go b/core/capabilities/ccip/ccipsolana/commitcodec.go index 151c3a5bd68..d080505dceb 100644 --- a/core/capabilities/ccip/ccipsolana/commitcodec.go +++ b/core/capabilities/ccip/ccipsolana/commitcodec.go @@ -116,8 +116,10 @@ func (c *CommitPluginCodecV1) Decode(ctx context.Context, bytes []byte) (cciptyp return cciptypes.CommitPluginReport{}, err } - merkleRoots := []cciptypes.MerkleRootChain{ - { + merkleRoots := make([]cciptypes.MerkleRootChain, 0, 1) + // if the merkle root is zeroed, ignore it + if commitReport.MerkleRoot.MerkleRoot != [32]byte{} { + merkleRoots = append(merkleRoots, cciptypes.MerkleRootChain{ ChainSel: cciptypes.ChainSelector(commitReport.MerkleRoot.SourceChainSelector), OnRampAddress: commitReport.MerkleRoot.OnRampAddress, SeqNumsRange: cciptypes.NewSeqNumRange( @@ -125,7 +127,7 @@ func (c *CommitPluginCodecV1) Decode(ctx context.Context, bytes []byte) (cciptyp cciptypes.SeqNum(commitReport.MerkleRoot.MaxSeqNr), ), MerkleRoot: commitReport.MerkleRoot.MerkleRoot, - }, + }) } tokenPriceUpdates := make([]cciptypes.TokenPrice, 0, len(commitReport.PriceUpdates.TokenPriceUpdates)) From 2adbeb9f1cf981acc963efa0fc45fd3b72a81eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 18 Feb 2025 00:01:20 +0900 Subject: [PATCH 34/62] Configure TokenInfo for Solana --- .../ccip/changeset/testhelpers/test_environment.go | 12 +++++++++--- deployment/ccip/changeset/v1_6/cs_ccip_home.go | 11 +++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_environment.go b/deployment/ccip/changeset/testhelpers/test_environment.go index cd8f009060c..2fcaea9200e 100644 --- a/deployment/ccip/changeset/testhelpers/test_environment.go +++ b/deployment/ccip/changeset/testhelpers/test_environment.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + solanago "github.com/gagliardetto/solana-go" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" @@ -22,9 +23,9 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" solBinary "github.com/gagliardetto/binary" + solFeeQuoter "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/fee_quoter" "github.com/smartcontractkit/chainlink-ccip/chainconfig" - solFeeQuoter "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/fee_quoter" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" @@ -702,10 +703,15 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn } for _, chain := range solChains { + // TODO: this is a workaround for tokenConfig.GetTokenInfo + tokenInfo := map[cciptypes.UnknownEncodedAddress]pluginconfig.TokenInfo{} + tokenInfo[cciptypes.UnknownEncodedAddress(state.SolChains[chain].LinkToken.String())] = tokenConfig.TokenSymbolToInfo[changeset.LinkSymbol] + // TODO: point this to proper SOL feed, apparently 0 signified SOL + tokenInfo[cciptypes.UnknownEncodedAddress(solanago.SolMint.String())] = tokenConfig.TokenSymbolToInfo[changeset.WethSymbol] + ocrOverride := tc.OCRConfigOverride ocrParams := v1_6.DeriveCCIPOCRParams( - // TODO: tokenInfo is nil for solana - v1_6.WithDefaultCommitOffChainConfig(e.FeedChainSel, nil), + v1_6.WithDefaultCommitOffChainConfig(e.FeedChainSel, tokenInfo), v1_6.WithDefaultExecuteOffChainConfig(tokenDataProviders), v1_6.WithOCRParamOverride(ocrOverride), ) diff --git a/deployment/ccip/changeset/v1_6/cs_ccip_home.go b/deployment/ccip/changeset/v1_6/cs_ccip_home.go index ca8fe58e13b..96c476c5e2e 100644 --- a/deployment/ccip/changeset/v1_6/cs_ccip_home.go +++ b/deployment/ccip/changeset/v1_6/cs_ccip_home.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + chain_selectors "github.com/smartcontractkit/chain-selectors" "golang.org/x/exp/maps" mcmslib "github.com/smartcontractkit/mcms" @@ -96,6 +97,16 @@ func validateCommitOffchainConfig(c *pluginconfig.CommitOffchainConfig, selector if err := c.Validate(); err != nil { return fmt.Errorf("invalid commit off-chain config: %w", err) } + + family, err := chain_selectors.GetSelectorFamily(selector) + if err != nil { + return err + } + if family != chain_selectors.FamilyEVM { + // TODO: implement more proper validation + return nil + } + for tokenAddr, tokenConfig := range c.TokenInfo { tokenUnknownAddr, err := ccipocr3.NewUnknownAddressFromHex(string(tokenAddr)) if err != nil { From 888ca785719ae15f458c9a8c762b092807633278 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Fri, 14 Feb 2025 16:34:08 -0500 Subject: [PATCH 35/62] Option to send price only commit reports to a different method. --- .../ccip/ocrimpls/contract_transmitter.go | 124 +++++++++--------- .../capabilities/ccip/oraclecreator/plugin.go | 5 + 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter.go b/core/capabilities/ccip/ocrimpls/contract_transmitter.go index 1876ac60b2f..09561c3817a 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter.go @@ -23,46 +23,55 @@ type ToCalldataFunc func( report ocr3types.ReportWithInfo[[]byte], rs, ss [][32]byte, vs [32]byte, -) (any, error) +) (string, string, any, error) + +func NewToCommitCalldata(defaultMethod, priceOnlyMethod string) ToCalldataFunc { + return func( + rawReportCtx [2][32]byte, + report ocr3types.ReportWithInfo[[]byte], + rs, ss [][32]byte, + vs [32]byte, + ) (string, string, any, error) { + // Note that the name of the struct field is very important, since the encoder used + // by the chainwriter uses mapstructure, which will use the struct field name to map + // to the argument name in the function call. + // If, for whatever reason, we want to change the field name, make sure to add a `mapstructure:""` tag + // for that field. + var info ccipocr3.CommitReportInfo + if len(report.Info) != 0 { + var err error + info, err = ccipocr3.DecodeCommitReportInfo(report.Info) + if err != nil { + return "", "", nil, err + } + } -func ToCommitCalldata( - rawReportCtx [2][32]byte, - report ocr3types.ReportWithInfo[[]byte], - rs, ss [][32]byte, - vs [32]byte, -) (any, error) { - // Note that the name of the struct field is very important, since the encoder used - // by the chainwriter uses mapstructure, which will use the struct field name to map - // to the argument name in the function call. - // If, for whatever reason, we want to change the field name, make sure to add a `mapstructure:""` tag - // for that field. - var info ccipocr3.CommitReportInfo - if len(report.Info) != 0 { - var err error - info, err = ccipocr3.DecodeCommitReportInfo(report.Info) - if err != nil { - return nil, err + method := defaultMethod + if len(priceOnlyMethod) > 0 && len(info.MerkleRoots) == 0 && len(info.TokenPrices) > 0 { + method = priceOnlyMethod } - } - // WARNING: Be careful if you change the data types. - // Using a different type e.g. `type Foo [32]byte` instead of `[32]byte` - // will trigger undefined chainWriter behavior, e.g. transactions submitted with wrong arguments. - return struct { - ReportContext [2][32]byte - Report []byte - Rs [][32]byte - Ss [][32]byte - RawVs [32]byte - Info ccipocr3.CommitReportInfo - }{ - ReportContext: rawReportCtx, - Report: report.Report, - Rs: rs, - Ss: ss, - RawVs: vs, - Info: info, - }, nil + // WARNING: Be careful if you change the data types. + // Using a different type e.g. `type Foo [32]byte` instead of `[32]byte` + // will trigger undefined chainWriter behavior, e.g. transactions submitted with wrong arguments. + return consts.ContractNameOffRamp, + method, + struct { + ReportContext [2][32]byte + Report []byte + Rs [][32]byte + Ss [][32]byte + RawVs [32]byte + Info ccipocr3.CommitReportInfo + }{ + ReportContext: rawReportCtx, + Report: report.Report, + Rs: rs, + Ss: ss, + RawVs: vs, + Info: info, + }, nil + } } func ToExecCalldata( @@ -70,7 +79,7 @@ func ToExecCalldata( report ocr3types.ReportWithInfo[[]byte], _, _ [][32]byte, _ [32]byte, -) (any, error) { +) (string, string, any, error) { // Note that the name of the struct field is very important, since the encoder used // by the chainwriter uses mapstructure, which will use the struct field name to map // to the argument name in the function call. @@ -85,19 +94,21 @@ func ToExecCalldata( var err error info, err = ccipocr3.DecodeExecuteReportInfo(report.Info) if err != nil { - return nil, err + return "", "", nil, err } } - return struct { - ReportContext [2][32]byte - Report []byte - Info ccipocr3.ExecuteReportInfo - }{ - ReportContext: rawReportCtx, - Report: report.Report, - Info: info, - }, nil + return consts.ContractNameOffRamp, + consts.MethodExecute, + struct { + ReportContext [2][32]byte + Report []byte + Info ccipocr3.ExecuteReportInfo + }{ + ReportContext: rawReportCtx, + Report: report.Report, + Info: info, + }, nil } var _ ocr3types.ContractTransmitter[[]byte] = &commitTransmitter{} @@ -105,8 +116,6 @@ var _ ocr3types.ContractTransmitter[[]byte] = &commitTransmitter{} type commitTransmitter struct { cw commontypes.ContractWriter fromAccount ocrtypes.Account - contractName string - method string offrampAddress string toCalldataFn ToCalldataFunc } @@ -114,16 +123,12 @@ type commitTransmitter struct { func XXXNewContractTransmitterTestsOnly( cw commontypes.ContractWriter, fromAccount ocrtypes.Account, - contractName string, - method string, offrampAddress string, toCalldataFn ToCalldataFunc, ) ocr3types.ContractTransmitter[[]byte] { return &commitTransmitter{ cw: cw, fromAccount: fromAccount, - contractName: contractName, - method: method, offrampAddress: offrampAddress, toCalldataFn: toCalldataFn, } @@ -133,14 +138,13 @@ func NewCommitContractTransmitter( cw commontypes.ContractWriter, fromAccount ocrtypes.Account, offrampAddress string, + defaultMethod, priceOnlyMethod string, ) ocr3types.ContractTransmitter[[]byte] { return &commitTransmitter{ cw: cw, fromAccount: fromAccount, - contractName: consts.ContractNameOffRamp, - method: consts.MethodCommit, offrampAddress: offrampAddress, - toCalldataFn: ToCommitCalldata, + toCalldataFn: NewToCommitCalldata(defaultMethod, priceOnlyMethod), } } @@ -152,8 +156,6 @@ func NewExecContractTransmitter( return &commitTransmitter{ cw: cw, fromAccount: fromAccount, - contractName: consts.ContractNameOffRamp, - method: consts.MethodExecute, offrampAddress: offrampAddress, toCalldataFn: ToExecCalldata, } @@ -198,7 +200,7 @@ func (c *commitTransmitter) Transmit( } // chain writer takes in the raw calldata and packs it on its own. - args, err := c.toCalldataFn(rawReportCtx, reportWithInfo, rs, ss, vs) + contract, method, args, err := c.toCalldataFn(rawReportCtx, reportWithInfo, rs, ss, vs) if err != nil { return fmt.Errorf("failed to generate call data: %w", err) } @@ -211,7 +213,7 @@ func (c *commitTransmitter) Transmit( return fmt.Errorf("failed to generate UUID: %w", err) } zero := big.NewInt(0) - if err := c.cw.SubmitTransaction(ctx, c.contractName, c.method, args, fmt.Sprintf("%s-%s-%s", c.contractName, c.offrampAddress, txID.String()), c.offrampAddress, &meta, zero); err != nil { + if err := c.cw.SubmitTransaction(ctx, contract, method, args, fmt.Sprintf("%s-%s-%s", contract, c.offrampAddress, txID.String()), c.offrampAddress, &meta, zero); err != nil { return fmt.Errorf("failed to submit transaction thru chainwriter: %w", err) } diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index 4bbe99743cf..b734f3cd05c 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -79,6 +79,7 @@ var plugins = map[string]plugin{ TokenDataEncoder: ccipsolana.NewSolanaTokenDataEncoder(), GasEstimateProvider: ccipsolana.NewGasEstimateProvider(), RMNCrypto: func(lggr logger.Logger) cciptypes.RMNCrypto { return nil }, + PriceOnlyCommitFn: consts.MethodCommitPriceOnly, }, } @@ -94,6 +95,8 @@ type plugin struct { TokenDataEncoder cciptypes.TokenDataEncoder GasEstimateProvider cciptypes.EstimateProvider RMNCrypto func(lggr logger.Logger) cciptypes.RMNCrypto + // PriceOnlyCommitFn optional method override for price only commit reports. + PriceOnlyCommitFn string } // pluginOracleCreator creates oracles that reference plugins running @@ -354,6 +357,8 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter( transmitter = ocrimpls.NewCommitContractTransmitter(destChainWriter, ocrtypes.Account(destFromAccounts[0]), offrampAddrStr, + consts.MethodCommit, + plugins[chainFamily].PriceOnlyCommitFn, ) } else if config.Config.PluginType == uint8(cctypes.PluginTypeCCIPExec) { factory = execocr3.NewExecutePluginFactory( From 7cd023689ed800cec3b66b06fd16980878a1003d Mon Sep 17 00:00:00 2001 From: Will Winder Date: Fri, 14 Feb 2025 17:01:22 -0500 Subject: [PATCH 36/62] Update test compilation. --- .../ccip/ocrimpls/contract_transmitter.go | 11 ++++++++++- .../ccip/ocrimpls/contract_transmitter_test.go | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter.go b/core/capabilities/ccip/ocrimpls/contract_transmitter.go index 09561c3817a..66546aee96d 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter.go @@ -123,14 +123,23 @@ type commitTransmitter struct { func XXXNewContractTransmitterTestsOnly( cw commontypes.ContractWriter, fromAccount ocrtypes.Account, + contractName string, + method string, offrampAddress string, toCalldataFn ToCalldataFunc, ) ocr3types.ContractTransmitter[[]byte] { + wrappedToCalldataFunc := func(rawReportCtx [2][32]byte, + report ocr3types.ReportWithInfo[[]byte], + rs, ss [][32]byte, + vs [32]byte) (string, string, any, error) { + _, _, args, err := toCalldataFn(rawReportCtx, report, rs, ss, vs) + return contractName, method, args, err + } return &commitTransmitter{ cw: cw, fromAccount: fromAccount, offrampAddress: offrampAddress, - toCalldataFn: toCalldataFn, + toCalldataFn: wrappedToCalldataFunc, } } diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 53042e475e4..89c11120de1 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -15,6 +15,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" @@ -316,7 +317,7 @@ func newTestUniverse(t *testing.T, ks *keyringsAndSigners[[]byte]) *testUniverse contractName, methodTransmitWithSignatures, ocr3HelperAddr.Hex(), - ocrimpls.ToCommitCalldata, + ocrimpls.NewToCommitCalldata(consts.MethodCommit, ""), ) transmitterWithoutSigs := ocrimpls.XXXNewContractTransmitterTestsOnly( chainWriter, From 41e7db24ef89aa216eaf70f11c7e06390e2cfb6d Mon Sep 17 00:00:00 2001 From: Will Winder Date: Sat, 15 Feb 2025 18:05:15 -0500 Subject: [PATCH 37/62] Lint --- core/capabilities/ccip/ocrimpls/contract_transmitter_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 89c11120de1..d09b4aa28cc 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -15,21 +15,21 @@ import ( "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - "github.com/smartcontractkit/chainlink-integrations/evm/heads" + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/chainlink-integrations/evm/assets" "github.com/smartcontractkit/chainlink-integrations/evm/client" evmconfig "github.com/smartcontractkit/chainlink-integrations/evm/config" "github.com/smartcontractkit/chainlink-integrations/evm/config/chaintype" "github.com/smartcontractkit/chainlink-integrations/evm/config/toml" "github.com/smartcontractkit/chainlink-integrations/evm/gas" + "github.com/smartcontractkit/chainlink-integrations/evm/heads" "github.com/smartcontractkit/chainlink-integrations/evm/keystore" "github.com/smartcontractkit/chainlink-integrations/evm/logpoller" evmtestutils "github.com/smartcontractkit/chainlink-integrations/evm/testutils" From c06fc88638df939f8cb56010295ec1658f391662 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Tue, 18 Feb 2025 23:46:22 -0600 Subject: [PATCH 38/62] Updated commit remaining accounts to be writable --- core/capabilities/ccip/configs/solana/chain_writer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/capabilities/ccip/configs/solana/chain_writer.go b/core/capabilities/ccip/configs/solana/chain_writer.go index e8deee0ede7..684a652ee48 100644 --- a/core/capabilities/ccip/configs/solana/chain_writer.go +++ b/core/capabilities/ccip/configs/solana/chain_writer.go @@ -484,7 +484,7 @@ func getGlobalStateConfig(offrampProgramAddress string) chainwriter.Lookup { {Static: []byte("state")}, }, IsSigner: false, - IsWritable: false, + IsWritable: true, }, Optional: true, } @@ -500,7 +500,7 @@ func getBillingTokenConfig(offrampProgramAddress string) chainwriter.Lookup { {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Location: "Info.TokenPrices.TokenID"}}}, }, IsSigner: false, - IsWritable: false, + IsWritable: true, }, Optional: true, } @@ -516,7 +516,7 @@ func getChainConfigGasPriceConfig(offrampProgramAddress string, destChainSelecto {Static: destChainSelector}, }, IsSigner: false, - IsWritable: false, + IsWritable: true, }, Optional: true, } From 30b06a0527f350fd7f05f19385d4e1a817e3bbb4 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Tue, 18 Feb 2025 23:53:51 -0600 Subject: [PATCH 39/62] Updated ChainConfigGasPrice account config --- .../ccip/configs/solana/chain_writer.go | 20 +++++++++---------- .../ccip/configs/solana/chain_writer_test.go | 2 +- .../capabilities/ccip/oraclecreator/plugin.go | 4 +--- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/core/capabilities/ccip/configs/solana/chain_writer.go b/core/capabilities/ccip/configs/solana/chain_writer.go index 684a652ee48..b336c774c4c 100644 --- a/core/capabilities/ccip/configs/solana/chain_writer.go +++ b/core/capabilities/ccip/configs/solana/chain_writer.go @@ -1,7 +1,6 @@ package solana import ( - "encoding/binary" "encoding/json" "errors" "fmt" @@ -28,8 +27,7 @@ const ( merkleRoot = "Info.MerkleRoots.MerkleRoot" ) -func getCommitMethodConfig(fromAddress string, offrampProgramAddress string, destChainSelector uint64, priceOnly bool) chainwriter.MethodConfig { - destChainSelectorBytes := binary.LittleEndian.AppendUint64([]byte{}, destChainSelector) +func getCommitMethodConfig(fromAddress string, offrampProgramAddress string, priceOnly bool) chainwriter.MethodConfig { chainSpecificName := "commit" if priceOnly { chainSpecificName = "commitPriceOnly" @@ -50,12 +48,12 @@ func getCommitMethodConfig(fromAddress string, offrampProgramAddress string, des getCommonAddressLookupTableConfig(offrampProgramAddress), }, }, - Accounts: buildCommitAccountsList(fromAddress, offrampProgramAddress, destChainSelectorBytes, priceOnly), + Accounts: buildCommitAccountsList(fromAddress, offrampProgramAddress, priceOnly), DebugIDLocation: "", } } -func buildCommitAccountsList(fromAddress, offrampProgramAddress string, destChainSelectorBytes []byte, priceOnly bool) []chainwriter.Lookup { +func buildCommitAccountsList(fromAddress, offrampProgramAddress string, priceOnly bool) []chainwriter.Lookup { accounts := []chainwriter.Lookup{} accounts = append(accounts, getOfframpAccountConfig(offrampProgramAddress), @@ -100,7 +98,7 @@ func buildCommitAccountsList(fromAddress, offrampProgramAddress string, destChai getFeeQuoterConfigLookup(offrampProgramAddress), getGlobalStateConfig(offrampProgramAddress), getBillingTokenConfig(offrampProgramAddress), - getChainConfigGasPriceConfig(offrampProgramAddress, destChainSelectorBytes), + getChainConfigGasPriceConfig(offrampProgramAddress), ) return accounts } @@ -307,7 +305,7 @@ func getExecuteMethodConfig(fromAddress string, offrampProgramAddress string) ch } } -func GetSolanaChainWriterConfig(offrampProgramAddress string, fromAddress string, destChainSelector uint64) (chainwriter.ChainWriterConfig, error) { +func GetSolanaChainWriterConfig(offrampProgramAddress string, fromAddress string) (chainwriter.ChainWriterConfig, error) { // check fromAddress pk, err := solana.PublicKeyFromBase58(fromAddress) if err != nil { @@ -333,8 +331,8 @@ func GetSolanaChainWriterConfig(offrampProgramAddress string, fromAddress string ccipconsts.ContractNameOffRamp: { Methods: map[string]chainwriter.MethodConfig{ ccipconsts.MethodExecute: getExecuteMethodConfig(fromAddress, offrampProgramAddress), - ccipconsts.MethodCommit: getCommitMethodConfig(fromAddress, offrampProgramAddress, destChainSelector, false), - ccipconsts.MethodCommitPriceOnly: getCommitMethodConfig(fromAddress, offrampProgramAddress, destChainSelector, true), + ccipconsts.MethodCommit: getCommitMethodConfig(fromAddress, offrampProgramAddress, false), + ccipconsts.MethodCommitPriceOnly: getCommitMethodConfig(fromAddress, offrampProgramAddress, true), }, IDL: ccipOfframpIDL, }, @@ -506,14 +504,14 @@ func getBillingTokenConfig(offrampProgramAddress string) chainwriter.Lookup { } } -func getChainConfigGasPriceConfig(offrampProgramAddress string, destChainSelector []byte) chainwriter.Lookup { +func getChainConfigGasPriceConfig(offrampProgramAddress string) chainwriter.Lookup { return chainwriter.Lookup{ PDALookups: &chainwriter.PDALookups{ Name: "ChainConfigGasPrice", PublicKey: getFeeQuoterProgramAccount(offrampProgramAddress), Seeds: []chainwriter.Seed{ {Static: []byte("dest_chain")}, - {Static: destChainSelector}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Location: "Info.GasPriceUpdates.ChainSel"}}}, }, IsSigner: false, IsWritable: true, diff --git a/core/capabilities/ccip/configs/solana/chain_writer_test.go b/core/capabilities/ccip/configs/solana/chain_writer_test.go index 34b53f9b186..1ad55ef82fa 100644 --- a/core/capabilities/ccip/configs/solana/chain_writer_test.go +++ b/core/capabilities/ccip/configs/solana/chain_writer_test.go @@ -30,7 +30,7 @@ func TestChainWriterConfigRaw(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - config, err := GetSolanaChainWriterConfig("4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6H", tt.fromAddress, 0) + config, err := GetSolanaChainWriterConfig("4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6H", tt.fromAddress) if tt.expectedError != "" { assert.EqualError(t, err, tt.expectedError) } else { diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index b734f3cd05c..05696895627 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -472,7 +472,6 @@ func (i *pluginOracleCreator) createReadersAndWriters( execBatchGasLimit, relayChainFamily, config.Config.OfframpAddress, - chainDetails.ChainSelector, ) if err1 != nil { return nil, nil, err1 @@ -600,7 +599,6 @@ func createChainWriter( execBatchGasLimit uint64, chainFamily string, offrampAddress []byte, - destChainSelector uint64, ) (types.ContractWriter, error) { var err error var chainWriterConfig []byte @@ -614,7 +612,7 @@ func createChainWriter( if len(offrampAddress) == solana.PublicKeyLength { offrampProgramAddress = solana.PublicKeyFromBytes(offrampAddress) } - if solConfig, err = solanaconfig.GetSolanaChainWriterConfig(offrampProgramAddress.String(), transmitter[0], destChainSelector); err != nil { + if solConfig, err = solanaconfig.GetSolanaChainWriterConfig(offrampProgramAddress.String(), transmitter[0]); err != nil { return nil, fmt.Errorf("failed to get Solana chain writer config: %w", err) } if chainWriterConfig, err = json.Marshal(solConfig); err != nil { From 5fb204bcb401009fd6b8c06974b8a1b57f11b453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 00:48:47 +0900 Subject: [PATCH 40/62] Fix commit price only report encoding --- .../ccip/ccipsolana/commitcodec.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/core/capabilities/ccip/ccipsolana/commitcodec.go b/core/capabilities/ccip/ccipsolana/commitcodec.go index d080505dceb..506cdae3665 100644 --- a/core/capabilities/ccip/ccipsolana/commitcodec.go +++ b/core/capabilities/ccip/ccipsolana/commitcodec.go @@ -28,25 +28,25 @@ func (c *CommitPluginCodecV1) Encode(ctx context.Context, report cciptypes.Commi encoder := agbinary.NewBorshEncoder(&buf) combinedRoots := report.BlessedMerkleRoots combinedRoots = append(combinedRoots, report.UnblessedMerkleRoots...) - var merkleRoot cciptypes.MerkleRootChain + var mr *ccip_offramp.MerkleRoot switch len(combinedRoots) { case 0: // price updates only, zero the root case 1: // valid - merkleRoot = combinedRoots[0] + merkleRoot := combinedRoots[0] + mr = &ccip_offramp.MerkleRoot{ + SourceChainSelector: uint64(merkleRoot.ChainSel), + OnRampAddress: merkleRoot.OnRampAddress, + MinSeqNr: uint64(merkleRoot.SeqNumsRange.Start()), + MaxSeqNr: uint64(merkleRoot.SeqNumsRange.End()), + MerkleRoot: merkleRoot.MerkleRoot, + } + default: return nil, fmt.Errorf("unexpected merkle root length in report: %d", len(combinedRoots)) } - mr := &ccip_offramp.MerkleRoot{ - SourceChainSelector: uint64(merkleRoot.ChainSel), - OnRampAddress: merkleRoot.OnRampAddress, - MinSeqNr: uint64(merkleRoot.SeqNumsRange.Start()), - MaxSeqNr: uint64(merkleRoot.SeqNumsRange.End()), - MerkleRoot: merkleRoot.MerkleRoot, - } - tpu := make([]ccip_offramp.TokenPriceUpdate, 0, len(report.PriceUpdates.TokenPriceUpdates)) for _, update := range report.PriceUpdates.TokenPriceUpdates { token, err := solana.PublicKeyFromBase58(string(update.TokenID)) @@ -117,8 +117,8 @@ func (c *CommitPluginCodecV1) Decode(ctx context.Context, bytes []byte) (cciptyp } merkleRoots := make([]cciptypes.MerkleRootChain, 0, 1) - // if the merkle root is zeroed, ignore it - if commitReport.MerkleRoot.MerkleRoot != [32]byte{} { + // if the merkle root is nil, ignore it + if commitReport.MerkleRoot != nil { merkleRoots = append(merkleRoots, cciptypes.MerkleRootChain{ ChainSel: cciptypes.ChainSelector(commitReport.MerkleRoot.SourceChainSelector), OnRampAddress: commitReport.MerkleRoot.OnRampAddress, From a817135391344f731f4cb4adf026c71f3126edfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 00:49:09 +0900 Subject: [PATCH 41/62] solana: Fix OCR3 report hashing --- .../keystore/keys/ocr2key/solana_keyring.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/services/keystore/keys/ocr2key/solana_keyring.go b/core/services/keystore/keys/ocr2key/solana_keyring.go index e9a017c3ce5..175bd52fabf 100644 --- a/core/services/keystore/keys/ocr2key/solana_keyring.go +++ b/core/services/keystore/keys/ocr2key/solana_keyring.go @@ -4,12 +4,14 @@ import ( "bytes" "crypto/ecdsa" "crypto/sha256" + "encoding/binary" "io" "github.com/ethereum/go-ethereum/crypto" "github.com/smartcontractkit/libocr/offchainreporting2/types" "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "golang.org/x/crypto/sha3" ) var _ ocrtypes.OnchainKeyring = &solanaKeyring{} @@ -53,10 +55,13 @@ func (skr *solanaKeyring) Sign3(digest types.ConfigDigest, seqNr uint64, r ocrty func (skr *solanaKeyring) reportToSigData3(digest types.ConfigDigest, seqNr uint64, r ocrtypes.Report) []byte { rawReportContext := RawReportContext3(digest, seqNr) - sigData := crypto.Keccak256(r) - sigData = append(sigData, rawReportContext[0][:]...) - sigData = append(sigData, rawReportContext[1][:]...) - return crypto.Keccak256(sigData) + h := sha3.NewLegacyKeccak256() + reportLen := uint16(len(r)) //nolint:gosec // max U16 larger than solana transaction size + binary.Write(h, binary.LittleEndian, reportLen) + h.Write(r) + h.Write(rawReportContext[0][:]) + h.Write(rawReportContext[1][:]) + return h.Sum(nil) } func (skr *solanaKeyring) signBlob(b []byte) (sig []byte, err error) { From f4ad99894be6e8d4c550f5d7d94000ce75b34797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 00:49:59 +0900 Subject: [PATCH 42/62] environment/memory: Fund the solana transmitter keys --- deployment/environment/memory/node.go | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/deployment/environment/memory/node.go b/deployment/environment/memory/node.go index d4a950f431f..37eb7b29810 100644 --- a/deployment/environment/memory/node.go +++ b/deployment/environment/memory/node.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/gagliardetto/solana-go" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" @@ -27,6 +28,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" mnCfg "github.com/smartcontractkit/chainlink-framework/multinode/config" + solrpc "github.com/gagliardetto/solana-go/rpc" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" "github.com/smartcontractkit/chainlink/deployment" @@ -479,10 +481,11 @@ func CreateKeys(t *testing.T, // need to look more into it, but it seems like with sim chains nodes are sending txs with 0x from address fundAddress(t, chain.DeployerKey, common.Address{}, assets.Ether(1000).ToInt(), backend) case chainsel.FamilyAptos: - err = app.GetKeyStore().Aptos().EnsureKey(ctx) + keystore := app.GetKeyStore().Aptos() + err = keystore.EnsureKey(ctx) require.NoError(t, err, "failed to create key for aptos") - keys, err := app.GetKeyStore().Aptos().GetAll() + keys, err := keystore.GetAll() require.NoError(t, err) require.Len(t, keys, 1) @@ -491,23 +494,22 @@ func CreateKeys(t *testing.T, // TODO: funding case chainsel.FamilyStarknet: - err = app.GetKeyStore().StarkNet().EnsureKey(ctx) + keystore := app.GetKeyStore().StarkNet() + err = keystore.EnsureKey(ctx) require.NoError(t, err, "failed to create key for starknet") - keys, err := app.GetKeyStore().StarkNet().GetAll() + keys, err := keystore.GetAll() require.NoError(t, err) require.Len(t, keys, 1) transmitter := keys[0] transmitters[chain.Selector] = transmitter.ID() - - // TODO: funding default: // TODO: other transmission keys unsupported for now } } - for chain := range solchains { + for chainSelector, chain := range solchains { ctype := chaintype.Solana err = app.GetKeyStore().OCR2().EnsureKeys(ctx, ctype) require.NoError(t, err) @@ -526,9 +528,9 @@ func CreateKeys(t *testing.T, require.Len(t, solkeys, 1) transmitter := solkeys[0] - transmitters[chain] = transmitter.ID() + transmitters[chainSelector] = transmitter.ID() - // TODO: funding + FundSolAccounts(ctx, []solana.PublicKey{transmitter.PublicKey()}, chain.Client, t) } return Keys{ @@ -539,6 +541,16 @@ func CreateKeys(t *testing.T, } } +func FundSolAccounts(ctx context.Context, accounts []solana.PublicKey, solanaGoClient *solrpc.Client, t *testing.T) { + sigs := []solana.Signature{} + for _, v := range accounts { + sig, err := solanaGoClient.RequestAirdrop(ctx, v, 1000*solana.LAMPORTS_PER_SOL, solrpc.CommitmentConfirmed) + require.NoError(t, err) + sigs = append(sigs, sig) + } + // we don't wait for confirmation so we don't block the tests, it'll take a while before nodes start transmitting +} + func createConfigV2Chain(chainID uint64) *v2toml.EVMConfig { chainIDBig := evmutils.NewI(int64(chainID)) chain := v2toml.Defaults(chainIDBig) From 27b11e30e3eb1f0fb731ebf67feebaeafb159727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 00:52:16 +0900 Subject: [PATCH 43/62] fix ChainConfigGasPriceConfig lookup --- core/capabilities/ccip/configs/solana/chain_writer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/capabilities/ccip/configs/solana/chain_writer.go b/core/capabilities/ccip/configs/solana/chain_writer.go index b336c774c4c..a83b9e91ab1 100644 --- a/core/capabilities/ccip/configs/solana/chain_writer.go +++ b/core/capabilities/ccip/configs/solana/chain_writer.go @@ -511,7 +511,7 @@ func getChainConfigGasPriceConfig(offrampProgramAddress string) chainwriter.Look PublicKey: getFeeQuoterProgramAccount(offrampProgramAddress), Seeds: []chainwriter.Seed{ {Static: []byte("dest_chain")}, - {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Location: "Info.GasPriceUpdates.ChainSel"}}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Location: "Info.GasPrices.ChainSel"}}}, }, IsSigner: false, IsWritable: true, From 0c58118525f955b0a80bcef3369c4dc4669985c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 14:24:24 +0900 Subject: [PATCH 44/62] Backport Ilija's change --- .../ccip/configs/solana/contract_reader.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index ea3def6fb31..cc666fe21f0 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -53,6 +53,17 @@ func DestContractReaderConfig() (config.ContractReader, error) { consts.ContractNameOffRamp: { IDL: offRampIDL, Reads: map[string]config.ReadDefinition{ + consts.EventNameCommitReportAccepted: { + ChainSpecificName: "CommitReportAccepted", + ReadType: config.Event, + EventDefinitions: &config.EventDefinitions{ + PollingFilter: &config.PollingFilter{}, + }, + OutputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"MerkleRoot": "UnblessedMerkleRoots"}}, + &codec.ElementExtractorModifierConfig{Extractions: map[string]*codec.ElementExtractorLocation{"UnblessedMerkleRoots": &locationFirst}}, + }, + }, consts.MethodNameOffRampLatestConfigDetails: { ChainSpecificName: "Config", ReadType: config.Account, From a0e8aeb5d5070da38b2d05f730ce55ac221616ae Mon Sep 17 00:00:00 2001 From: ilija Date: Thu, 20 Feb 2025 13:25:01 +0100 Subject: [PATCH 45/62] Add EventNameExecutionStateChanged to Solana dest chain cfg --- .../ccip/configs/solana/contract_reader.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index cc666fe21f0..d2405673841 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -53,6 +53,25 @@ func DestContractReaderConfig() (config.ContractReader, error) { consts.ContractNameOffRamp: { IDL: offRampIDL, Reads: map[string]config.ReadDefinition{ + consts.EventNameExecutionStateChanged: { + ChainSpecificName: consts.EventNameExecutionStateChanged, + ReadType: config.Event, + EventDefinitions: &config.EventDefinitions{ + PollingFilter: &config.PollingFilter{}, + IndexedField0: &config.IndexedField{ + OffChainPath: consts.EventAttributeSourceChain, + OnChainPath: consts.EventAttributeSourceChain, + }, + IndexedField1: &config.IndexedField{ + OffChainPath: consts.EventAttributeSequenceNumber, + OnChainPath: consts.EventAttributeSequenceNumber, + }, + IndexedField2: &config.IndexedField{ + OffChainPath: consts.EventAttributeState, + OnChainPath: consts.EventAttributeState, + }, + }, + }, consts.EventNameCommitReportAccepted: { ChainSpecificName: "CommitReportAccepted", ReadType: config.Event, From a34a084fb253bdb1d55528959fbc92625889e69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 22:27:24 +0900 Subject: [PATCH 46/62] Use correct address transforms --- core/capabilities/ccip/configs/solana/chain_writer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/capabilities/ccip/configs/solana/chain_writer.go b/core/capabilities/ccip/configs/solana/chain_writer.go index a83b9e91ab1..6bccfd61228 100644 --- a/core/capabilities/ccip/configs/solana/chain_writer.go +++ b/core/capabilities/ccip/configs/solana/chain_writer.go @@ -43,6 +43,7 @@ func getCommitMethodConfig(fromAddress string, offrampProgramAddress string, pri }, }, ChainSpecificName: chainSpecificName, + ArgsTransform: "CCIPCommit", LookupTables: chainwriter.LookupTables{ DerivedLookupTables: []chainwriter.DerivedLookupTable{ getCommonAddressLookupTableConfig(offrampProgramAddress), @@ -115,7 +116,7 @@ func getExecuteMethodConfig(fromAddress string, offrampProgramAddress string) ch }, }, ChainSpecificName: "execute", - ArgsTransform: "CCIP", + ArgsTransform: "CCIPExecute", LookupTables: chainwriter.LookupTables{ DerivedLookupTables: []chainwriter.DerivedLookupTable{ { From d2ee0b55aa35f5eac09f6d511724fa0b05c203a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 22:27:33 +0900 Subject: [PATCH 47/62] correct IndexedField0 --- core/capabilities/ccip/configs/solana/contract_reader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index d2405673841..a9a251c9233 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -60,7 +60,7 @@ func DestContractReaderConfig() (config.ContractReader, error) { PollingFilter: &config.PollingFilter{}, IndexedField0: &config.IndexedField{ OffChainPath: consts.EventAttributeSourceChain, - OnChainPath: consts.EventAttributeSourceChain, + OnChainPath: "SourceChainSelector", }, IndexedField1: &config.IndexedField{ OffChainPath: consts.EventAttributeSequenceNumber, From 2b3dd2140e7ef71329c4744f5108872f22dd6dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 22:28:23 +0900 Subject: [PATCH 48/62] Read events from offramp, not router --- .../changeset/testhelpers/test_assertions.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index d6494e49c4d..983cdadc9ea 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -222,7 +222,7 @@ func ConfirmCommitForAllWithExpectedSeqNums( t, srcChain, e.SolChains[dstChain], - state.SolChains[dstChain].Router, + state.SolChains[dstChain].OffRamp, startSlot, ccipocr3.SeqNumRange{ ccipocr3.SeqNum(expectedSeqNum), @@ -332,7 +332,7 @@ func ConfirmMultipleCommits( t, srcChain, env.SolChains[destChain], - state.SolChains[destChain].Router, + state.SolChains[destChain].OffRamp, startSlot, seqRange, enforceSingleCommit, @@ -522,7 +522,7 @@ func ConfirmCommitWithExpectedSeqNumRangeSol( t *testing.T, srcSelector uint64, dest deployment.SolChain, - routerAddress solana.PublicKey, + offrampAddress solana.PublicKey, startSlot uint64, expectedSeqNumRange ccipocr3.SeqNumRange, enforceSingleCommit bool, @@ -531,7 +531,7 @@ func ConfirmCommitWithExpectedSeqNumRangeSol( done := make(chan any) defer close(done) - sink := SolEventEmitter[solccip.EventCommitReportAccepted](t, dest.Client, routerAddress, "CommitReportAccepted", startSlot, done) + sink := SolEventEmitter[solccip.EventCommitReportAccepted](t, dest.Client, offrampAddress, "CommitReportAccepted", startSlot, done) timeout := time.NewTimer(tests.WaitTimeout(t)) defer timeout.Stop() @@ -621,7 +621,7 @@ func ConfirmExecWithSeqNrsForAll( t, srcChain, e.SolChains[dstChain], - state.SolChains[dstChain].Router, + state.SolChains[dstChain].OffRamp, startSlot, seqRange, ) @@ -727,7 +727,7 @@ func ConfirmExecWithSeqNrsSol( t *testing.T, srcSelector uint64, dest deployment.SolChain, - routerAddress solana.PublicKey, + offrampAddress solana.PublicKey, startSlot uint64, expectedSeqNrs []uint64, ) (executionStates map[uint64]int, err error) { @@ -742,7 +742,7 @@ func ConfirmExecWithSeqNrsSol( done := make(chan any) defer close(done) - sink := SolEventEmitter[solccip.EventExecutionStateChanged](t, dest.Client, routerAddress, "ExecutionStateChanged", startSlot, done) + sink := SolEventEmitter[solccip.EventExecutionStateChanged](t, dest.Client, offrampAddress, "ExecutionStateChanged", startSlot, done) timeout := time.NewTimer(tests.WaitTimeout(t)) defer timeout.Stop() @@ -754,7 +754,7 @@ func ConfirmExecWithSeqNrsSol( _, found := seqNrsToWatch[execEvent.SequenceNumber] if found && execEvent.SourceChainSelector == srcSelector { t.Logf("Received ExecutionStateChanged (state %s) on chain %d (offramp %s) from chain %d with expected sequence number %d", - execEvent.State.String(), dest.Selector, routerAddress.String(), srcSelector, execEvent.SequenceNumber) + execEvent.State.String(), dest.Selector, offrampAddress.String(), srcSelector, execEvent.SequenceNumber) executionStates[execEvent.SequenceNumber] = int(execEvent.State) delete(seqNrsToWatch, execEvent.SequenceNumber) if len(seqNrsToWatch) == 0 { @@ -763,7 +763,7 @@ func ConfirmExecWithSeqNrsSol( } case <-timeout.C: return nil, fmt.Errorf("timed out waiting for ExecutionStateChanged on chain %d (offramp %s) from chain %d with expected sequence numbers %+v", - dest.Selector, routerAddress.String(), srcSelector, expectedSeqNrs) + dest.Selector, offrampAddress.String(), srcSelector, expectedSeqNrs) } } } From 1eedb7cd969ed8cfe6fa2e6329cc5725255a8efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 22:28:41 +0900 Subject: [PATCH 49/62] allow v0 transactions --- deployment/ccip/changeset/testhelpers/test_assertions.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index 983cdadc9ea..66add80ee3a 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -485,12 +485,14 @@ func SolEventEmitter[T any]( // Skip any signatures that are before the starting slot continue } + v := uint64(0) // v0 = latest, supports address table lookups tx, err := client.GetTransaction( ctx, txSig.Signature, &solrpc.GetTransactionOpts{ - Commitment: solrpc.CommitmentConfirmed, - Encoding: solana.EncodingBase64, + Commitment: solrpc.CommitmentConfirmed, + Encoding: solana.EncodingBase64, + MaxSupportedTransactionVersion: &v, }, ) require.NoError(t, err) From 2bf5e1aae6b8a1ae1b5701c41655da9e067d48fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 20 Feb 2025 22:28:59 +0900 Subject: [PATCH 50/62] skip price-only events --- deployment/ccip/changeset/testhelpers/test_assertions.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index 66add80ee3a..9e6c78bb1b4 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -541,10 +541,13 @@ func ConfirmCommitWithExpectedSeqNumRangeSol( for { select { case commitEvent := <-sink: + // if merkle root is zero, it only contains price updates + if commitEvent.Report.MerkleRoot == [32]uint8{} { + t.Logf("Skipping CommitReportAccepted with only price updates") + continue + } require.Equal(t, srcSelector, commitEvent.Report.SourceChainSelector) - // TODO: commitEvent.Report.MerkleRoot ? do we need to recursive call? - // TODO: this logic is duplicated with verifyCommitReport, share mr := commitEvent.Report seenMessages.visitCommitReport(mr.SourceChainSelector, mr.MinSeqNr, mr.MaxSeqNr) From 680806366a7e1a9dae71a6bb7bf2e9dcf285ce17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 21 Feb 2025 00:29:26 +0900 Subject: [PATCH 51/62] Fix config for GetTokenPrice --- core/capabilities/ccip/configs/solana/contract_reader.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index a9a251c9233..9b883e33a0f 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -236,12 +236,8 @@ func DestContractReaderConfig() (config.ContractReader, error) { PDADefinition: solanacodec.PDATypeDef{ Prefix: []byte("fee_billing_token_config"), Seeds: []solanacodec.PDASeed{{ - Name: "Tokens", - Type: solanacodec.IdlType{ - AsIdlTypeVec: &solanacodec.IdlTypeVec{ - Vec: solanacodec.IdlType{AsString: solanacodec.IdlTypePublicKey}, - }, - }, + Name: "Token", + Type: solanacodec.IdlType{AsString: solanacodec.IdlTypePublicKey}, }}}, }, consts.MethodNameGetFeePriceUpdate: { From d19b85ba98ed358c0846bd66b8cf203adfecbb79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 21 Feb 2025 00:46:10 +0900 Subject: [PATCH 52/62] Extract the token field --- core/capabilities/ccip/configs/solana/contract_reader.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 9b883e33a0f..4bded832de4 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -239,6 +239,9 @@ func DestContractReaderConfig() (config.ContractReader, error) { Name: "Token", Type: solanacodec.IdlType{AsString: solanacodec.IdlTypePublicKey}, }}}, + OutputModifications: codec.ModifiersConfig{ + &codec.PropertyExtractorConfig{FieldName: "Config.UsdPerToken"}, + }, }, consts.MethodNameGetFeePriceUpdate: { ChainSpecificName: "DestChain", From 610fb1a782fb143a4684f82c079a57877cda95c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 22 Feb 2025 01:45:11 +0900 Subject: [PATCH 53/62] solana: Set gas helpers to non-zero to appease the CCIP plugin --- core/capabilities/ccip/ccipsolana/gas_helpers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/capabilities/ccip/ccipsolana/gas_helpers.go b/core/capabilities/ccip/ccipsolana/gas_helpers.go index 59ab4ff9dcb..2ac8a000d64 100644 --- a/core/capabilities/ccip/ccipsolana/gas_helpers.go +++ b/core/capabilities/ccip/ccipsolana/gas_helpers.go @@ -13,10 +13,10 @@ type EstimateProvider struct { // CalculateMerkleTreeGas is not implemented func (gp EstimateProvider) CalculateMerkleTreeGas(numRequests int) uint64 { - return 0 + return 1 } // CalculateMessageMaxGas is not implemented. func (gp EstimateProvider) CalculateMessageMaxGas(msg cciptypes.Message) uint64 { - return 0 + return 1 } From 86050f237cac407774c0ec9c9315e19a78fe7d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 22 Feb 2025 01:45:45 +0900 Subject: [PATCH 54/62] Fix the LinkTokenMint property --- .../ccip/configs/solana/contract_reader.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 4bded832de4..9ba38d43821 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -297,11 +297,7 @@ func DestContractReaderConfig() (config.ContractReader, error) { Prefix: []byte("config"), }, OutputModifications: codec.ModifiersConfig{ - &codec.RenameModifierConfig{ - Fields: map[string]string{ - "LinkTokenMint": "LinkToken", - }, - }, + &codec.PropertyExtractorConfig{FieldName: "LinkTokenMint"}, }, }, }, @@ -513,11 +509,7 @@ func SourceContractReaderConfig() (config.ContractReader, error) { Prefix: []byte("config"), }, OutputModifications: codec.ModifiersConfig{ - &codec.RenameModifierConfig{ - Fields: map[string]string{ - "LinkTokenMint": "LinkToken", - }, - }, + &codec.PropertyExtractorConfig{FieldName: "LinkTokenMint"}, }, }, }, From 849376ae98cd3ad01d7d255de74909ff9a2638ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 22 Feb 2025 01:46:16 +0900 Subject: [PATCH 55/62] Return router as the nonce manager --- .../ccip/configs/solana/contract_reader.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 9ba38d43821..014f77c0e40 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -120,6 +120,21 @@ func DestContractReaderConfig() (config.ContractReader, error) { }, }, }, + MultiReader: &config.MultiReader{ + Reads: []config.ReadDefinition{ + // CCIP expects a NonceManager address, in our case that's the Router + { + ChainSpecificName: "ReferenceAddresses", + ReadType: config.Account, + PDADefinition: solanacodec.PDATypeDef{ + Prefix: []byte("reference_addresses"), + }, + OutputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"Router": "NonceManager"}}, + }, + }, + }, + }, }, consts.MethodNameOffRampGetDynamicConfig: { ChainSpecificName: "Config", From 598aafbf429d75c82242f3982741072bff7b4904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 22 Feb 2025 01:46:30 +0900 Subject: [PATCH 56/62] Use a custom workaround in chainlink-solana to return the right config based on pluginType --- .../ccip/configs/solana/contract_reader.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 014f77c0e40..53c87ffa10e 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -84,17 +84,17 @@ func DestContractReaderConfig() (config.ContractReader, error) { }, }, consts.MethodNameOffRampLatestConfigDetails: { - ChainSpecificName: "Config", - ReadType: config.Account, - PDADefinition: solanacodec.PDATypeDef{Prefix: []byte("config")}, + ChainSpecificName: "Config", + ReadType: config.Account, + PDADefinition: solanacodec.PDATypeDef{Prefix: []byte("config")}, OutputModifications: codec.ModifiersConfig{ // TODO why does Solana have two of these in an array, but EVM has one - &codec.WrapperModifierConfig{ - Fields: map[string]string{"Ocr3": "OcrConfig"}, - }, - &codec.PropertyExtractorConfig{FieldName: "Ocr3"}, - &codec.ElementExtractorFromOnchainModifierConfig{Extractions: map[string]*codec.ElementExtractorLocation{"OcrConfig": &locationFirst}}, - &codec.ByteToBooleanModifierConfig{Fields: []string{"OcrConfig.ConfigInfo.IsSignatureVerificationEnabled"}}, + // &codec.WrapperModifierConfig{ + // Fields: map[string]string{"Ocr3": "OcrConfig"}, + // }, + // &codec.PropertyExtractorConfig{FieldName: "Ocr3"}, + // &codec.ElementExtractorFromOnchainModifierConfig{Extractions: map[string]*codec.ElementExtractorLocation{"OcrConfig": &locationFirst}}, + // &codec.ByteToBooleanModifierConfig{Fields: []string{"OcrConfig.ConfigInfo.IsSignatureVerificationEnabled"}}, }, }, consts.MethodNameGetLatestPriceSequenceNumber: { From 70eb82d7d951dab97fe6a91ad69df631cf4b2dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 22 Feb 2025 01:47:05 +0900 Subject: [PATCH 57/62] Set a non-zero execution fee estimate --- deployment/environment/memory/node.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deployment/environment/memory/node.go b/deployment/environment/memory/node.go index 37eb7b29810..951ff2d25a3 100644 --- a/deployment/environment/memory/node.go +++ b/deployment/environment/memory/node.go @@ -570,6 +570,12 @@ func createSolanaChainConfig(chainID string, chain deployment.SolChain) *solcfg. chainConfig := solcfg.Chain{} chainConfig.SetDefaults() + // CCIP requires a non-zero execution fee estimate + computeUnitPriceDefault := uint64(100) + txRetentionTimeout := config.MustNewDuration(10 * time.Minute) + chainConfig.ComputeUnitPriceDefault = &computeUnitPriceDefault + chainConfig.TxRetentionTimeout = txRetentionTimeout + url, err := config.ParseURL(chain.URL) if err != nil { panic(err) From 276b9b029db3dc3c3bcecadfd706c4d56d079c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 22 Feb 2025 11:30:30 +0900 Subject: [PATCH 58/62] CW: mark as optional --- core/capabilities/ccip/configs/solana/chain_writer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/capabilities/ccip/configs/solana/chain_writer.go b/core/capabilities/ccip/configs/solana/chain_writer.go index 6bccfd61228..29844e2baa4 100644 --- a/core/capabilities/ccip/configs/solana/chain_writer.go +++ b/core/capabilities/ccip/configs/solana/chain_writer.go @@ -139,6 +139,7 @@ func getExecuteMethodConfig(fromAddress string, offrampProgramAddress string) ch }, }, }, + Optional: true, // Lookup table is optional if DestTokenAddress is not present in report }, getCommonAddressLookupTableConfig(offrampProgramAddress), }, @@ -154,6 +155,7 @@ func getExecuteMethodConfig(fromAddress string, offrampProgramAddress string) ch }, }, MintAddress: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Location: destTokenAddress}}, + Optional: true, // ATA lookup is optional if DestTokenAddress is not present in report }, }, Accounts: []chainwriter.Lookup{ From a8bdb8e5eb02983055b40e3565ddbc610cf94353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 24 Feb 2025 17:34:16 +0900 Subject: [PATCH 59/62] ccipsolana: Add OnRampAddress and MerkleRoot to report Requires changes in chainlink-ccip --- core/capabilities/ccip/ccipsolana/executecodec.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/capabilities/ccip/ccipsolana/executecodec.go b/core/capabilities/ccip/ccipsolana/executecodec.go index dd38197fe00..8c468769f4e 100644 --- a/core/capabilities/ccip/ccipsolana/executecodec.go +++ b/core/capabilities/ccip/ccipsolana/executecodec.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + ethcommon "github.com/ethereum/go-ethereum/common" agbinary "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common" @@ -108,6 +109,7 @@ func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.Exec TokenReceiver: solana.PublicKeyFromBytes(msg.Receiver), TokenAmounts: tokenAmounts, ExtraArgs: extraArgs, + OnRampAddress: ethcommon.LeftPadBytes(msg.Header.OnRamp, 64), } // should only have an offchain token data if there are tokens as part of the message @@ -126,6 +128,7 @@ func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.Exec Message: message, OffchainTokenData: offChainTokenData, Proofs: solanaProofs, + Root: chainReport.Root, } var buf bytes.Buffer From 3ee00d40d1996b8e12777f601bae2ed461640435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 25 Feb 2025 15:15:17 +0900 Subject: [PATCH 60/62] wip rm --- core/capabilities/ccip/ccipsolana/executecodec.go | 3 +++ core/capabilities/ccip/ocrimpls/contract_transmitter.go | 4 ++++ .../ccip/changeset/testhelpers/messagingtest/helpers.go | 3 +++ 3 files changed, 10 insertions(+) diff --git a/core/capabilities/ccip/ccipsolana/executecodec.go b/core/capabilities/ccip/ccipsolana/executecodec.go index 8c468769f4e..76a5598fe19 100644 --- a/core/capabilities/ccip/ccipsolana/executecodec.go +++ b/core/capabilities/ccip/ccipsolana/executecodec.go @@ -96,6 +96,8 @@ func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.Exec return nil, fmt.Errorf("invalid receiver address: %v", msg.Receiver) } + fmt.Printf("ONRAMP %+v\n", msg.Header.OnRamp) + message = ccip_offramp.Any2SVMRampMessage{ Header: ccip_offramp.RampMessageHeader{ MessageId: msg.Header.MessageID, @@ -109,6 +111,7 @@ func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.Exec TokenReceiver: solana.PublicKeyFromBytes(msg.Receiver), TokenAmounts: tokenAmounts, ExtraArgs: extraArgs, + // TODO: is this available? OnRampAddress: ethcommon.LeftPadBytes(msg.Header.OnRamp, 64), } diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter.go b/core/capabilities/ccip/ocrimpls/contract_transmitter.go index 66546aee96d..aaf17dae240 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter.go @@ -46,6 +46,8 @@ func NewToCommitCalldata(defaultMethod, priceOnlyMethod string) ToCalldataFunc { } } + // fmt.Printf("transmitter: decoded report info %+v\n", info) + method := defaultMethod if len(priceOnlyMethod) > 0 && len(info.MerkleRoots) == 0 && len(info.TokenPrices) > 0 { method = priceOnlyMethod @@ -98,6 +100,8 @@ func ToExecCalldata( } } + fmt.Printf("transmitter: decoded exec report info %+v\n", info) + return consts.ContractNameOffRamp, consts.MethodExecute, struct { diff --git a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go index f1ced004140..d193b6bf59a 100644 --- a/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go +++ b/deployment/ccip/changeset/testhelpers/messagingtest/helpers.go @@ -200,6 +200,9 @@ func Run(tc TestCase) (out TestCaseOutput) { execStates[sourceDest][msgSentEvent.SequenceNumber], ) + // TODO: check this again, nonce not incremented + // is this based on source chain? + // check the sender latestNonce on the dest, should be incremented latestNonce := getLatestNonce(tc) require.Equal(tc.T, tc.Nonce+1, latestNonce) From 72919fb23cd2983e27cf6ff571da5c64b7b6a49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 26 Feb 2025 15:27:59 +0900 Subject: [PATCH 61/62] solana: fix GetTokenPrice for source chain reader too --- .../ccip/configs/solana/contract_reader.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 53c87ffa10e..1f0521aa6be 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -479,13 +479,12 @@ func SourceContractReaderConfig() (config.ContractReader, error) { PDADefinition: solanacodec.PDATypeDef{ Prefix: []byte("fee_billing_token_config"), Seeds: []solanacodec.PDASeed{{ - Name: "Tokens", - Type: solanacodec.IdlType{ - AsIdlTypeVec: &solanacodec.IdlTypeVec{ - Vec: solanacodec.IdlType{AsString: solanacodec.IdlTypePublicKey}, - }, - }, + Name: "Token", + Type: solanacodec.IdlType{AsString: solanacodec.IdlTypePublicKey}, }}}, + OutputModifications: codec.ModifiersConfig{ + &codec.PropertyExtractorConfig{FieldName: "Config.UsdPerToken"}, + }, }, consts.MethodNameGetFeePriceUpdate: { ChainSpecificName: "DestChain", From eea6fa4f1fe3c7dea7b70b9f0c4b5a223808e381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 26 Feb 2025 19:52:03 +0900 Subject: [PATCH 62/62] Update OnRampAddress and Root handling to match upstream contract changes --- core/capabilities/ccip/ccipsolana/executecodec.go | 4 +--- .../ccip/configs/solana/contract_reader.go | 9 +++++++++ .../ccip/changeset/solana/cs_add_remote_chain.go | 10 +++++----- .../ccip/changeset/testhelpers/test_assertions.go | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/core/capabilities/ccip/ccipsolana/executecodec.go b/core/capabilities/ccip/ccipsolana/executecodec.go index 76a5598fe19..fb13a4eaa3d 100644 --- a/core/capabilities/ccip/ccipsolana/executecodec.go +++ b/core/capabilities/ccip/ccipsolana/executecodec.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" - ethcommon "github.com/ethereum/go-ethereum/common" agbinary "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common" @@ -112,7 +111,7 @@ func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.Exec TokenAmounts: tokenAmounts, ExtraArgs: extraArgs, // TODO: is this available? - OnRampAddress: ethcommon.LeftPadBytes(msg.Header.OnRamp, 64), + OnRampAddress: msg.Header.OnRamp, } // should only have an offchain token data if there are tokens as part of the message @@ -131,7 +130,6 @@ func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.Exec Message: message, OffchainTokenData: offChainTokenData, Proofs: solanaProofs, - Root: chainReport.Root, } var buf bytes.Buffer diff --git a/core/capabilities/ccip/configs/solana/contract_reader.go b/core/capabilities/ccip/configs/solana/contract_reader.go index 1f0521aa6be..4507d9d11c7 100644 --- a/core/capabilities/ccip/configs/solana/contract_reader.go +++ b/core/capabilities/ccip/configs/solana/contract_reader.go @@ -41,6 +41,15 @@ func DestContractReaderConfig() (config.ContractReader, error) { }, }) + // Prepend custom type so it takes priority over the IDL + offRampIDL.Types = append([]solanacodec.IdlTypeDef{{ + Name: "OnRampAddress", + Type: solanacodec.IdlTypeDefTy{ + Kind: solanacodec.IdlTypeDefTyKindCustom, + Codec: "onramp_address", + }, + }}, offRampIDL.Types...) + var routerIDL solanacodec.IDL if err := json.Unmarshal([]byte(ccipRouterIDL), &routerIDL); err != nil { return config.ContractReader{}, fmt.Errorf("unexpected error: invalid CCIP Router IDL, error: %w", err) diff --git a/deployment/ccip/changeset/solana/cs_add_remote_chain.go b/deployment/ccip/changeset/solana/cs_add_remote_chain.go index 93278ac0560..d2895b4b3e0 100644 --- a/deployment/ccip/changeset/solana/cs_add_remote_chain.go +++ b/deployment/ccip/changeset/solana/cs_add_remote_chain.go @@ -6,7 +6,6 @@ import ( "fmt" "strconv" - "github.com/ethereum/go-ethereum/common" "github.com/gagliardetto/solana-go" "github.com/smartcontractkit/mcms" @@ -162,11 +161,12 @@ func doAddRemoteChainToSolana( } for remoteChainSel, update := range updates { - var onRampBytes [64]byte + var onRampAddress solOffRamp.OnRampAddress + // var onRampBytes [64]byte // already verified, skipping errcheck addressBytes, _ := s.GetOnRampAddressBytes(remoteChainSel) - addressBytes = common.LeftPadBytes(addressBytes, 64) - copy(onRampBytes[:], addressBytes) + copy(onRampAddress.Bytes[:], addressBytes) + onRampAddress.Len = uint32(len(addressBytes)) // verified while loading state fqRemoteChainPDA, _, _ := solState.FindFqDestChainPDA(remoteChainSel, feeQuoterID) @@ -286,7 +286,7 @@ func doAddRemoteChainToSolana( solOffRamp.SetProgramID(offRampID) validSourceChainConfig := solOffRamp.SourceChainConfig{ - OnRamp: [2][64]byte{onRampBytes, [64]byte{}}, + OnRamp: [2]solOffRamp.OnRampAddress{onRampAddress, {}}, IsEnabled: update.EnabledAsSource, } if offRampUsingMCMS { diff --git a/deployment/ccip/changeset/testhelpers/test_assertions.go b/deployment/ccip/changeset/testhelpers/test_assertions.go index 9e6c78bb1b4..abe1f949b57 100644 --- a/deployment/ccip/changeset/testhelpers/test_assertions.go +++ b/deployment/ccip/changeset/testhelpers/test_assertions.go @@ -542,7 +542,7 @@ func ConfirmCommitWithExpectedSeqNumRangeSol( select { case commitEvent := <-sink: // if merkle root is zero, it only contains price updates - if commitEvent.Report.MerkleRoot == [32]uint8{} { + if commitEvent.Report == nil { t.Logf("Skipping CommitReportAccepted with only price updates") continue }