Skip to content

smoke/ccip/canonical: poc of canonical test scenarios #17748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions deployment/ccip/changeset/testhelpers/test_assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,9 @@ func ConfirmCommitWithExpectedSeqNumRangeSol(

for {
select {
case <-time.After(10 * time.Second):
t.Logf("Waiting for CommitReportAccepted event on chain selector %d from source selector %d expected seq nr range %s",
dest.Selector, srcSelector, expectedSeqNumRange.String())
case commitEvent := <-sink:
// if merkle root is zero, it only contains price updates
if commitEvent.Report == nil {
Expand Down Expand Up @@ -774,6 +777,9 @@ func ConfirmExecWithSeqNrsSol(

for {
select {
case <-time.After(10 * time.Second):
t.Logf("Waiting for ExecutionStateChanged event on chain %d (offramp %s) from chain %d with expected sequence numbers %+v",
dest.Selector, offrampAddress.String(), srcSelector, expectedSeqNrs)
case execEvent := <-sink:
// TODO: share with EVM
_, found := seqNrsToWatch[execEvent.SequenceNumber]
Expand Down
129 changes: 129 additions & 0 deletions integration-tests/smoke/ccip/canonical/adapters/evm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package adapters

import (
"context"
crand "crypto/rand"
"fmt"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi/bind/v2"
"github.com/ethereum/go-ethereum/common"
chain_selectors "github.com/smartcontractkit/chain-selectors"

Check failure on line 11 in integration-tests/smoke/ccip/canonical/adapters/evm.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (integration-tests)

File is not properly formatted (goimports)
"github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_2_0/router"
"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
"github.com/smartcontractkit/chainlink/deployment/ccip/shared/stateview/evm"
"github.com/smartcontractkit/chainlink/integration-tests/smoke/ccip/canonical/types"
)

var _ types.Adapter = &evmAdapter{}

type evmAdapter struct {
chain cldf.Chain
chainState evm.CCIPChainState
}

// ChainSelector implements types.Adapter.
func (a *evmAdapter) ChainSelector() uint64 {
return a.chain.Selector
}

// GetExtraArgs implements types.Adapter.
// TODO: apply opts.
func (a *evmAdapter) GetExtraArgs(_ []byte, sourceFamily string, opts ...types.ExtraArgOpt) ([]byte, error) {
switch sourceFamily {
case chain_selectors.FamilyEVM:
return []byte{}, nil // default extra args are empty for EVM
case chain_selectors.FamilySolana:
return []byte{}, nil // default extra args are empty for Solana
default:
return nil, fmt.Errorf("unsupported source family: %s", sourceFamily)
}
}

// CCIPReceiver implements types.Adapter.
func (a *evmAdapter) CCIPReceiver() []byte {
return common.LeftPadBytes(a.chainState.Receiver.Address().Bytes(), 32)
}

// ValidateCommit implements types.Adapter.
func (a *evmAdapter) ValidateCommit(
t *testing.T,
sourceSelector uint64,
seqNumRange ccipocr3.SeqNumRange) {
testhelpers.ConfirmCommitWithExpectedSeqNumRange(

Check failure on line 55 in integration-tests/smoke/ccip/canonical/adapters/evm.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (integration-tests)

Error return value of `testhelpers.ConfirmCommitWithExpectedSeqNumRange` is not checked (errcheck)
t,
sourceSelector,
a.chain,
a.chainState.OffRamp,
nil,
seqNumRange,
false,
)
}

// ValidateExec implements types.Adapter.
func (a *evmAdapter) ValidateExec(
t *testing.T,
sourceSelector uint64,
seqNrs []uint64) {
testhelpers.ConfirmExecWithSeqNrs(

Check failure on line 71 in integration-tests/smoke/ccip/canonical/adapters/evm.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (integration-tests)

Error return value of `testhelpers.ConfirmExecWithSeqNrs` is not checked (errcheck)
t,
sourceSelector,
a.chain,
a.chainState.OffRamp,
nil,
seqNrs,
)
}

// BuildMessage implements types.Adapter.
func (a *evmAdapter) BuildMessage(components types.MessageComponents) (any, error) {
var tokenAmounts []router.ClientEVMTokenAmount

Check failure on line 83 in integration-tests/smoke/ccip/canonical/adapters/evm.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (integration-tests)

Consider pre-allocating `tokenAmounts` (prealloc)
for _, tokenAmount := range components.TokenAmounts {
tokenAmounts = append(tokenAmounts, router.ClientEVMTokenAmount{
Token: common.HexToAddress(tokenAmount.Token),
Amount: tokenAmount.Amount,
})
}

msg := router.ClientEVM2AnyMessage{
Receiver: components.Receiver,
Data: components.Data,
TokenAmounts: tokenAmounts,
FeeToken: common.HexToAddress(components.FeeToken),
ExtraArgs: components.ExtraArgs,
}

return msg, nil
}

// NativeFeeToken implements types.Adapter.
func (a *evmAdapter) NativeFeeToken() string {
return common.HexToAddress("0x0").Hex()
}

// RandomReceiver implements types.Adapter.
func (a *evmAdapter) RandomReceiver() []byte {
b := make([]byte, 20)
_, _ = crand.Read(b) // Assignment for errcheck. Only used in tests so we can ignore.
// return a random address as a left-padded 32 byte array
addr := common.LeftPadBytes(b, 32)

return addr
}

func NewEVMAdapter(chain cldf.Chain, chainState evm.CCIPChainState) types.Adapter {
return &evmAdapter{chain: chain, chainState: chainState}
}

func (a *evmAdapter) ChainFamily() string {
return chain_selectors.FamilyEVM
}

func (a *evmAdapter) GetInboundNonce(ctx context.Context, sender []byte, srcSel uint64) (uint64, error) {
return a.chainState.NonceManager.GetInboundNonce(&bind.CallOpts{
Context: ctx,
}, srcSel, sender)
}
176 changes: 176 additions & 0 deletions integration-tests/smoke/ccip/canonical/adapters/svm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package adapters

import (
"context"
crand "crypto/rand"
"fmt"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/gagliardetto/solana-go"

chain_selectors "github.com/smartcontractkit/chain-selectors"

Check failure on line 12 in integration-tests/smoke/ccip/canonical/adapters/svm.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (integration-tests)

File is not properly formatted (goimports)
"github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_6_0/message_hasher"
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-ccip/pkg/types/ccipocr3"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
solanastate "github.com/smartcontractkit/chainlink/deployment/ccip/shared/stateview/solana"
"github.com/smartcontractkit/chainlink/integration-tests/smoke/ccip/canonical/types"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm"
)

var _ types.Adapter = &svmAdapter{}

type svmAdapter struct {
chain cldf.SolChain
chainState solanastate.CCIPChainState
}

// ChainSelector implements types.Adapter.
func (s *svmAdapter) ChainSelector() uint64 {
return s.chain.Selector
}

// GetExtraArgs implements types.Adapter.
// TODO: apply opts.
func (s *svmAdapter) GetExtraArgs(receiver []byte, sourceFamily string, opts ...types.ExtraArgOpt) ([]byte, error) {
receiverProgram := solana.PublicKeyFromBytes(receiver)
receiverTargetAccountPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("counter")}, receiverProgram)
receiverExternalExecutionConfigPDA, _, _ := solana.FindProgramAddress([][]byte{[]byte("external_execution_config")}, receiverProgram)

accounts := [][32]byte{
receiverExternalExecutionConfigPDA,
receiverTargetAccountPDA,
solana.SystemProgramID,
}

switch sourceFamily {
case chain_selectors.FamilyEVM:
extraArgs, err := ccipevm.SerializeClientSVMExtraArgsV1(message_hasher.ClientSVMExtraArgsV1{
AccountIsWritableBitmap: solccip.GenerateBitMapForIndexes([]int{0, 1}),
Accounts: accounts,
ComputeUnits: 80_000,
})
if err != nil {
return nil, fmt.Errorf("failed to serialize extra args: %w", err)
}
return extraArgs, nil
default:
// TODO: add support for other families
return nil, fmt.Errorf("unsupported source family: %s", sourceFamily)
}
}

// CCIPReceiver implements types.Adapter.
func (s *svmAdapter) CCIPReceiver() []byte {
return s.chainState.Receiver.Bytes()
}

func NewSVMAdapter(chain cldf.SolChain, chainState solanastate.CCIPChainState) types.Adapter {
return &svmAdapter{
chain: chain,
chainState: chainState,
}
}

// BuildMessage implements types.Adapter.
func (s *svmAdapter) BuildMessage(components types.MessageComponents) (any, error) {
feeToken := solana.PublicKey{}
if len(components.FeeToken) > 0 {
var err error
feeToken, err = solana.PublicKeyFromBase58(components.FeeToken)
if err != nil {
return nil, fmt.Errorf("invalid format for fee token: %w", err)
}
}

var tokenAmounts []ccip_router.SVMTokenAmount
if len(components.TokenAmounts) > 0 {
tokenAmounts = make([]ccip_router.SVMTokenAmount, len(components.TokenAmounts))
for i, amount := range components.TokenAmounts {
token, err := solana.PublicKeyFromBase58(amount.Token)
if err != nil {
return nil, fmt.Errorf("invalid format for token: %w", err)
}
tokenAmounts[i] = ccip_router.SVMTokenAmount{
Token: token,
Amount: amount.Amount.Uint64(),
}
}
}

msg := ccip_router.SVM2AnyMessage{
Receiver: components.Receiver,
TokenAmounts: tokenAmounts,
Data: components.Data,
FeeToken: feeToken,
ExtraArgs: components.ExtraArgs,
}

return msg, nil
}

// ChainFamily implements types.Adapter.
func (s *svmAdapter) ChainFamily() string {
return chain_selectors.FamilySolana
}

// GetInboundNonce implements types.Adapter.
func (s *svmAdapter) GetInboundNonce(ctx context.Context, sender []byte, srcSel uint64) (uint64, error) {
client := s.chain.Client
// TODO: solcommon.FindNoncePDA expected the sender to be a solana pubkey
chainSelectorLE := solcommon.Uint64ToLE(s.chain.Selector)
noncePDA, _, err := solana.FindProgramAddress([][]byte{[]byte("nonce"), chainSelectorLE, sender}, s.chainState.Router)
if err != nil {
return 0, fmt.Errorf("failed to find nonce PDA: %w", err)
}
var nonceCounterAccount ccip_router.Nonce
// we ignore the error because the account might not exist yet
_ = solcommon.GetAccountDataBorshInto(ctx, client, noncePDA, solconfig.DefaultCommitment, &nonceCounterAccount)
latestNonce := nonceCounterAccount.Counter
return latestNonce, nil
}

// NativeFeeToken implements types.Adapter.
func (s *svmAdapter) NativeFeeToken() string {
return solana.PublicKey{}.String()
}

// RandomReceiver implements types.Adapter.
func (s *svmAdapter) RandomReceiver() []byte {
b := make([]byte, 20)
_, _ = crand.Read(b) // Assignment for errcheck. Only used in tests so we can ignore.
// return a random address as a left-padded 32 byte array
addr := common.LeftPadBytes(b, 32)

return addr
}

// ValidateCommit implements types.Adapter.
func (s *svmAdapter) ValidateCommit(t *testing.T, sourceSelector uint64, seqNumRange ccipocr3.SeqNumRange) {
testhelpers.ConfirmCommitWithExpectedSeqNumRangeSol(

Check failure on line 155 in integration-tests/smoke/ccip/canonical/adapters/svm.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (integration-tests)

Error return value of `testhelpers.ConfirmCommitWithExpectedSeqNumRangeSol` is not checked (errcheck)
t,
sourceSelector,
s.chain,
s.chainState.OffRamp,
0, // startSlot
seqNumRange,
true,
)
}

// ValidateExec implements types.Adapter.
func (s *svmAdapter) ValidateExec(t *testing.T, sourceSelector uint64, seqNrs []uint64) {
testhelpers.ConfirmExecWithSeqNrsSol(

Check failure on line 168 in integration-tests/smoke/ccip/canonical/adapters/svm.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (integration-tests)

Error return value of `testhelpers.ConfirmExecWithSeqNrsSol` is not checked (errcheck)
t,
sourceSelector,
s.chain,
s.chainState.OffRamp,
0, // startSlot
seqNrs,
)
}
Loading
Loading