Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ccip-load-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ jobs:
CANTON_LOAD_MESSAGE_RATE: ${{ steps.prod_defaults.outputs.message_rate }}
CANTON_LOAD_DURATION: ${{ steps.prod_defaults.outputs.load_duration }}
CCIP_ENV: ${{ inputs.ccip_env == 'prod-testnet' && 'prod-testnet' || '' }}
CCIP_CONFIG_FILE: ${{ inputs.config_file }}
CCIP_CONFIG_FILE: ${{ inputs.ccip_env == 'prod-testnet' && inputs.config_file || '' }}
CANTON_AUTH_TYPE: ${{ inputs.ccip_env == 'prod-testnet' && 'clientCredentials' || '' }}
CANTON_AUTH_URL: ${{ inputs.ccip_env == 'prod-testnet' && secrets.CANTON_OKTA_AUTHORIZER_TESTNET || '' }}
CANTON_CLIENT_ID: ${{ inputs.ccip_env == 'prod-testnet' && secrets.CANTON_OKTA_CLIENT_ID_TESTNET || '' }}
Expand Down
165 changes: 54 additions & 111 deletions ccip/devenv/tests/e2e/canton2evm_e2e_test.go

Large diffs are not rendered by default.

107 changes: 42 additions & 65 deletions ccip/devenv/tests/e2e/evm2canton_e2e_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
package canton

import (
"fmt"
"math/big"
"testing"
"time"

"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/operations/proxy"
"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/sequences"
"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/versioned_verifier_resolver"
ccv "github.com/smartcontractkit/chainlink-ccv/build/devenv"
"github.com/smartcontractkit/chainlink-ccv/build/devenv/cciptestinterfaces"
ccldf "github.com/smartcontractkit/chainlink-ccv/build/devenv/cldf"
"github.com/smartcontractkit/chainlink-ccv/build/devenv/common"
_ "github.com/smartcontractkit/chainlink-ccv/build/devenv/evm" // register EVM ImplFactory
"github.com/smartcontractkit/chainlink-ccv/protocol"
utilstests "github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/canton"
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
"github.com/smartcontractkit/chainlink-testing-framework/framework"
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
"github.com/stretchr/testify/require"

_ "github.com/smartcontractkit/chainlink-canton/ccip/devenv" // register Canton ImplFactory
cantondevenv "github.com/smartcontractkit/chainlink-canton/ccip/devenv"
devenvtests "github.com/smartcontractkit/chainlink-canton/ccip/devenv/tests"
"github.com/smartcontractkit/chainlink-canton/testhelpers"
)
Expand All @@ -32,38 +28,24 @@ func TestEVM2Canton_Basic(t *testing.T) {
t.Skip("skipping EVM2Canton_Basic test in short mode")
}

configPath := "../../env-canton-evm-out.toml"
in, err := ccv.LoadOutput[ccv.Cfg](configPath)
require.NoError(t, err)

lib, err := ccv.NewLibFromCCVEnv(&ccv.Plog, configPath)
require.NoError(t, err)
boot := devenvtests.BootstrapE2E(t, devenvtests.ParseEnvFromFlag(t))
ctx := ccv.Plog.WithContext(t.Context())
boot.SetupCantonReceive(t, ctx)

chainMap, err := lib.ChainsMap(t.Context())
require.NoError(t, err)
require.NoError(t, devenvtests.WireVerifierObservationFromLib(lib, chainMap))

srcChain := devenvtests.GetChainFromMap(t, blockchain.TypeAnvil, in, chainMap)
dstChain := devenvtests.GetChainFromMap(t, blockchain.TypeCanton, in, chainMap)
cantonDest, ok := dstChain.(*cantondevenv.Chain)
require.True(t, ok, "Canton dest chain must be *devenv.Chain")
require.NoError(t, cantonDest.SetupReceive(ccv.Plog.WithContext(t.Context())))

t.Cleanup(func() {
_, err := framework.SaveContainerLogs(fmt.Sprintf("%s-%s", framework.DefaultCTFLogsDir, t.Name()))
require.NoError(t, err)
})

srcSelector := srcChain.ChainSelector()
dstSelector := dstChain.ChainSelector()
receiverParticipant, _, err := cantonDest.ClientParticipant()
srcSelector := boot.EVM.ChainSelector()
dstSelector := boot.Canton.ChainSelector()
_, opsEnv, err := ccldf.NewCLDFOperationsEnvironment(boot.Cfg.Blockchains, boot.Cfg.CLDF.DataStore)
require.NoError(t, err)
var receiverParticipant canton.Participant
if chains := opsEnv.BlockChains.CantonChains(); len(chains[dstSelector].Participants) > 0 {
receiverParticipant = chains[dstSelector].Participants[0]
}
require.NotEmpty(t, receiverParticipant.PartyID)

receiver, err := cantonDest.GetEOAReceiverAddress()
receiver, err := boot.Canton.GetEOAReceiverAddress()
require.NoError(t, err)

ds, err := lib.DataStore()
ds, err := boot.Lib.DataStore()
require.NoError(t, err)
ccvAddr := devenvtests.GetContractAddress(
t, ds, srcSelector,
Expand All @@ -83,39 +65,26 @@ func TestEVM2Canton_Basic(t *testing.T) {
t.Run("message transfer", func(t *testing.T) {
subtestCtx := ccv.Plog.WithContext(t.Context())

seqNo, err := srcChain.GetExpectedNextSequenceNumber(subtestCtx, dstSelector)
seqNo, err := boot.EVM.GetExpectedNextSequenceNumber(subtestCtx, dstSelector)
require.NoError(t, err)
sendMessageResult, err := srcChain.SendMessage(subtestCtx, dstSelector, cciptestinterfaces.MessageFields{
sendMessageResult, err := boot.EVM.SendMessage(subtestCtx, dstSelector, cciptestinterfaces.MessageFields{
Receiver: receiver,
Data: []byte("Hello message transfer from EVM!"),
}, cciptestinterfaces.MessageOptions{
ExecutionGasLimit: 200_000,
FinalityConfig: 0,
Executor: executorAddress,
CCVs: []protocol.CCV{
{
CCVAddress: ccvAddr,
Args: []byte{},
ArgsLen: 0,
},
},
}, 3)
}, devenvtests.EVMToCantonMessageOptions(200_000, executorAddress, ccvAddr), 3)
require.NoError(t, err)
require.NotNil(t, sendMessageResult.Message)

sentEvent, err := srcChain.ConfirmSendOnSource(subtestCtx, dstSelector, cciptestinterfaces.MessageEventKey{SeqNum: seqNo}, 15*time.Second)
sentEvent, err := boot.ConfirmEVMSendOnSource(t, subtestCtx, dstSelector, seqNo, sendMessageResult)
require.NoError(t, err)
require.NotNil(t, sentEvent.Message)
require.Nil(t, sentEvent.Message.TokenTransfer)

execKey := cciptestinterfaces.MessageEventKey{SeqNum: seqNo, MessageID: sentEvent.MessageID}
executionStateChangedEvent, err := cantonDest.ConfirmExecOnDest(subtestCtx, srcSelector, execKey, utilstests.WaitTimeout(t))
executionStateChangedEvent, err := boot.Canton.ConfirmExecOnDest(subtestCtx, srcSelector, execKey, devenvtests.ConfirmExecTimeout(t))
require.NoError(t, err)
require.Equal(t, cciptestinterfaces.ExecutionStateSuccess, executionStateChangedEvent.State)

// testing idempotency of ConfirmExecOnDest: a second call
// must return the same event without re-executing.
idempotentEvent, err := cantonDest.ConfirmExecOnDest(subtestCtx, srcSelector, execKey, utilstests.WaitTimeout(t))
idempotentEvent, err := boot.Canton.ConfirmExecOnDest(subtestCtx, srcSelector, execKey, devenvtests.ConfirmExecTimeout(t))
require.NoError(t, err)
require.Equal(t, executionStateChangedEvent.State, idempotentEvent.State)
require.Equal(t, executionStateChangedEvent.MessageNumber, idempotentEvent.MessageNumber)
Expand All @@ -125,14 +94,25 @@ func TestEVM2Canton_Basic(t *testing.T) {
t.Run("token transfer", func(t *testing.T) {
subtestCtx := ccv.Plog.WithContext(t.Context())

// Send params (transfer amount, gas limit, finality) come from token_transfer_config.toml.
lane := devenvtests.ResolveTokenLane(t, devenvtests.EnvDevenv, in, lib, chainMap, srcSelector, []uint64{dstSelector})
lane := devenvtests.ResolveTokenLane(t, boot.Env, boot.Cfg, boot.Lib, boot.ChainMap, srcSelector, []uint64{dstSelector})
t.Logf("Token lane: pool=%s srcToken=%x transfer=%s",
lane.PoolRef.Qualifier, lane.SrcToken, lane.TransferAmount.String())

srcToken := lane.SrcToken
srcSender, err := srcChain.GetEOAReceiverAddress()
require.NoError(t, err)
seqNo, err := srcChain.GetExpectedNextSequenceNumber(subtestCtx, dstSelector)
srcSender := boot.ResolveEVMReceiver(t)

if boot.Env.IsRemote() {
senderBalance, err := boot.EVM.GetTokenBalance(subtestCtx, srcSender, srcToken)
require.NoError(t, err)
require.NotNil(t, senderBalance)
require.GreaterOrEqual(t, senderBalance.Cmp(lane.TransferAmount), 0,
"EVM sender token balance %s is below transfer amount %s; fund PRIVATE_KEY wallet with TEST tokens on Sepolia",
senderBalance.String(), lane.TransferAmount.String())
t.Log("prod-testnet: ensure PRIVATE_KEY wallet has Sepolia ETH for send and possible router approve tx")
}
seqNo, err := boot.EVM.GetExpectedNextSequenceNumber(subtestCtx, dstSelector)
require.NoError(t, err)
sendMessageResult, err := srcChain.SendMessage(subtestCtx, dstSelector, cciptestinterfaces.MessageFields{
sendMessageResult, err := boot.EVM.SendMessage(subtestCtx, dstSelector, cciptestinterfaces.MessageFields{
Receiver: receiver,
Data: []byte("Hello token transfer from EVM!"),
TokenAmount: cciptestinterfaces.TokenAmount{
Expand All @@ -155,23 +135,20 @@ func TestEVM2Canton_Basic(t *testing.T) {
require.NotNil(t, sendMessageResult.Message)
require.NotNil(t, sendMessageResult.Message.TokenTransfer)

sentEvent, err := srcChain.ConfirmSendOnSource(subtestCtx, dstSelector, cciptestinterfaces.MessageEventKey{SeqNum: seqNo}, 15*time.Second)
sentEvent, err := boot.ConfirmEVMSendOnSource(t, subtestCtx, dstSelector, seqNo, sendMessageResult)
require.NoError(t, err)
require.NotNil(t, sentEvent.Message)
require.NotNil(t, sentEvent.Message.TokenTransfer)

// Pre-exec assertions on the verifier result are kept here (cheap aggregator/indexer
// re-read) so the token transfer assertions stay co-located with the test body.
// ConfirmExecOnDest below performs its own fetch internally for execution.
result := devenvtests.AssertSingleVerifierResult(t, subtestCtx, lib, sentEvent.MessageID)
result := devenvtests.AssertSingleVerifierResult(t, subtestCtx, boot.Lib, sentEvent.MessageID)
vr := result.IndexedVerifications.Results[0].VerifierResult
require.NotNil(t, vr.Message.TokenTransfer)
require.NotNil(t, vr.Message.TokenTransfer.Amount)
t.Logf("Canton token transfer amount from verifier result: %s", vr.Message.TokenTransfer.Amount.String())
require.Positive(t, vr.Message.TokenTransfer.Amount.Cmp(big.NewInt(0)), "token transfer amount must be positive")

execKey := cciptestinterfaces.MessageEventKey{SeqNum: seqNo, MessageID: sentEvent.MessageID}
executionStateChangedEvent, err := cantonDest.ConfirmExecOnDest(subtestCtx, srcSelector, execKey, utilstests.WaitTimeout(t))
executionStateChangedEvent, err := boot.Canton.ConfirmExecOnDest(subtestCtx, srcSelector, execKey, devenvtests.ConfirmExecTimeout(t))
require.NoError(t, err)
require.Equal(t, cciptestinterfaces.ExecutionStateSuccess, executionStateChangedEvent.State)

Expand All @@ -180,10 +157,10 @@ func TestEVM2Canton_Basic(t *testing.T) {
totalHoldingsFloat, _ := new(big.Float).SetRat(totalHoldingsRat).Float64()
t.Logf("Canton receiver total holdings after execute: %.10f", totalHoldingsFloat)

srcBalanceAfter, err := srcChain.GetTokenBalance(subtestCtx, srcSender, srcToken)
srcBalanceAfter, err := boot.EVM.GetTokenBalance(subtestCtx, srcSender, srcToken)
require.NoError(t, err)
require.NotNil(t, srcBalanceAfter)
dstBalanceAfter, err := cantonDest.GetTokenBalance(subtestCtx, receiver, nil)
dstBalanceAfter, err := boot.Canton.GetTokenBalance(subtestCtx, receiver, nil)
require.NoError(t, err)
require.NotNil(t, dstBalanceAfter)
t.Logf("Token balances after execute: evm_sender=%s canton_receiver=%s", srcBalanceAfter.String(), dstBalanceAfter.String())
Expand Down
62 changes: 62 additions & 0 deletions ccip/devenv/tests/env.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package tests

import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

const envConfigFile = "CCIP_CONFIG_FILE"

// CCIPEnv names a CCIP e2e target environment.
type CCIPEnv string

Expand All @@ -13,6 +21,20 @@ const (
EnvProdTestnet CCIPEnv = "prod-testnet"
)

var ccipEnvFlag = flag.String(
"ccip-env",
defaultFromEnv("CCIP_ENV", string(EnvDevenv)),
"CCIP e2e environment: devenv (default) or prod-testnet",
)

func defaultFromEnv(key, fallback string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}

return fallback
}

// ParseCCIPEnv validates and returns a CCIPEnv from its string form.
func ParseCCIPEnv(s string) (CCIPEnv, error) {
switch CCIPEnv(strings.TrimSpace(s)) {
Expand All @@ -27,7 +49,47 @@ func ParseCCIPEnv(s string) (CCIPEnv, error) {
}
}

// ConfigPath returns the CCV env output TOML filename under ccip/devenv.
func (e CCIPEnv) ConfigPath() string {
switch e {
case EnvDevenv:
return "env-canton-evm-out.toml"
case EnvProdTestnet:
return "env-prod-testnet-out.toml"
default:
return ""
}
}

// ResolveConfigPath returns the CCV env output TOML filename under ccip/devenv.
// On prod-testnet, CCIP_CONFIG_FILE overrides the default when set.
func ResolveConfigPath(env CCIPEnv) string {
if !env.IsRemote() {
return env.ConfigPath()
}
if override := strings.TrimSpace(os.Getenv(envConfigFile)); override != "" {
return filepath.Base(override)
}

return env.ConfigPath()
}

// IsRemote reports whether the environment targets live testnet infrastructure.
func (e CCIPEnv) IsRemote() bool {
return e == EnvProdTestnet
}

// ParseEnvFromFlag reads the -ccip-env flag (defaulting from CCIP_ENV).
func ParseEnvFromFlag(t *testing.T) CCIPEnv {
t.Helper()

t.Logf("CCIP_ENV env=%q ccip-env flag=%q",
os.Getenv("CCIP_ENV"),
*ccipEnvFlag,
)

env, err := ParseCCIPEnv(*ccipEnvFlag)
require.NoError(t, err)

return env
}
36 changes: 36 additions & 0 deletions ccip/devenv/tests/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package tests

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestResolveConfigPath(t *testing.T) {
t.Setenv(envConfigFile, "")

require.Equal(t, "env-canton-evm-out.toml", ResolveConfigPath(EnvDevenv))
require.Equal(t, "env-prod-testnet-out.toml", ResolveConfigPath(EnvProdTestnet))

t.Setenv(envConfigFile, "env-prod-testnet.ci.toml")
require.Equal(t, "env-prod-testnet.ci.toml", ResolveConfigPath(EnvProdTestnet))

t.Setenv(envConfigFile, "ccip/devenv/custom.toml")
require.Equal(t, "env-canton-evm-out.toml", ResolveConfigPath(EnvDevenv))
require.Equal(t, "custom.toml", ResolveConfigPath(EnvProdTestnet))
}

func TestParseCCIPEnv(t *testing.T) {
t.Parallel()

env, err := ParseCCIPEnv("devenv")
require.NoError(t, err)
require.Equal(t, EnvDevenv, env)

env, err = ParseCCIPEnv("prod-testnet")
require.NoError(t, err)
require.Equal(t, EnvProdTestnet, env)

_, err = ParseCCIPEnv("unknown")
require.Error(t, err)
}
Loading
Loading