Skip to content

Commit

Permalink
multicall ccip-send (#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
AnieeG authored Nov 9, 2023
1 parent 0f3634f commit 6d69c82
Show file tree
Hide file tree
Showing 11 changed files with 601 additions and 104 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ jobs:
os: ubuntu20.04-16cores-64GB
file: ccip
run: -run ^TestSmokeCCIPRateLimit$
- name: ccip-smoke-multicall
nodes: 1
dir: ccip-tests/smoke
os: ubuntu20.04-16cores-64GB
file: ccip
run: -run ^TestSmokeCCIPMulticall$
- name: cron
nodes: 1
os: ubuntu-latest
Expand Down
322 changes: 242 additions & 80 deletions integration-tests/ccip-tests/actions/ccip_helpers.go

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions integration-tests/ccip-tests/contracts/contract_deployer.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package contracts

import (
"context"
"crypto/ed25519"
"encoding/hex"
"fmt"
"math/big"
"strings"
"time"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -55,6 +57,34 @@ func (e *CCIPContractsDeployer) Client() blockchain.EVMClient {
return e.evmClient
}

func (e *CCIPContractsDeployer) DeployMultiCallContract() (common.Address, error) {
multiCallABI, err := abi.JSON(strings.NewReader(MultiCallABI))
if err != nil {
return common.Address{}, err
}
address, tx, _, err := e.evmClient.DeployContract("MultiCall Contract", func(
auth *bind.TransactOpts,
backend bind.ContractBackend,
) (common.Address, *types.Transaction, interface{}, error) {
address, tx, contract, err := bind.DeployContract(auth, multiCallABI, common.FromHex(MultiCallBIN), backend)
if err != nil {
return common.Address{}, nil, nil, err
}
return address, tx, contract, err
})
if err != nil {
return common.Address{}, err
}
r, err := bind.WaitMined(context.Background(), e.evmClient.DeployBackend(), tx)
if err != nil {
return common.Address{}, err
}
if r.Status != types.ReceiptStatusSuccessful {
return common.Address{}, fmt.Errorf("deploy multicall failed")
}
return *address, nil
}

func (e *CCIPContractsDeployer) DeployLinkTokenContract() (*LinkToken, error) {
address, _, instance, err := e.evmClient.DeployContract("Link Token", func(
auth *bind.TransactOpts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type CommonContracts struct {

type SourceContracts struct {
OnRamp string `json:"on_ramp"`
Multicall string `json:"multicall,omitempty"`
DepolyedAt uint64 `json:"deployed_at"`
}

Expand Down
215 changes: 215 additions & 0 deletions integration-tests/ccip-tests/contracts/multicall.go

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions integration-tests/ccip-tests/load/ccip_loadgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,15 @@ func (c *CCIPE2ELoad) Call(_ *wasp.Generator) *wasp.CallResult {
})
// wait for
// - CCIPSendRequested Event log to be generated,
msgLog, sourceLogTime, err := c.Lane.Source.AssertEventCCIPSendRequested(lggr, sendTx.Hash().Hex(), c.CallTimeOut, txConfirmationTime, stats)
if err != nil || msgLog == nil {
msgLogs, sourceLogTime, err := c.Lane.Source.AssertEventCCIPSendRequested(lggr, sendTx.Hash().Hex(), c.CallTimeOut, txConfirmationTime, []*testreporters.RequestStat{stats})

if err != nil || msgLogs == nil || len(msgLogs) == 0 {
res.Error = err.Error()
res.Data = stats.StatusByPhase
res.Failed = true
return res
}
msgLog := msgLogs[0]
sentMsg := msgLog.Message
seqNum := sentMsg.SequenceNumber
lggr = lggr.With().Str("msgId ", fmt.Sprintf("0x%x", sentMsg.MessageId[:])).Logger()
Expand Down Expand Up @@ -275,7 +277,7 @@ func (c *CCIPE2ELoad) Call(_ *wasp.Generator) *wasp.CallResult {
} else {
var finalizingBlock uint64
sourceLogFinalizedAt, finalizingBlock, err = c.Lane.Source.AssertSendRequestedLogFinalized(
lggr, seqNum, msgLog, sourceLogTime, stats)
lggr, sendTx.Hash(), sourceLogTime, []*testreporters.RequestStat{stats})
if err != nil {
res.Error = err.Error()
res.Data = stats.StatusByPhase
Expand Down
65 changes: 60 additions & 5 deletions integration-tests/ccip-tests/smoke/ccip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"time"

"github.com/AlekSi/pointer"
"github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/chainlink-testing-framework/logging"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/ccip/integration-tests/ccip-tests/testconfig"
"github.com/smartcontractkit/ccip/integration-tests/utils"
"github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/actions"
"github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp"
Expand Down Expand Up @@ -226,7 +226,7 @@ func TestSmokeCCIPRateLimit(t *testing.T) {
failedTx, _, _, err := tc.lane.Source.SendRequest(
tc.lane.Dest.ReceiverDapp.EthAddress,
actions.TokenTransfer, "msg with token more than aggregated capacity",
common.HexToAddress(tc.lane.Source.Common.FeeToken.Address()))
)
require.NoError(t, err)
require.Error(t, tc.lane.Source.Common.ChainClient.WaitForEvents())
errReason, v, err := tc.lane.Source.Common.ChainClient.RevertReasonFromTx(failedTx, evm_2_evm_onramp.EVM2EVMOnRampABI)
Expand Down Expand Up @@ -256,7 +256,7 @@ func TestSmokeCCIPRateLimit(t *testing.T) {
failedTx, _, _, err = tc.lane.Source.SendRequest(
tc.lane.Dest.ReceiverDapp.EthAddress,
actions.TokenTransfer, "msg with token more than aggregated rate",
common.HexToAddress(tc.lane.Source.Common.FeeToken.Address()))
)
require.NoError(t, err)
require.Error(t, tc.lane.Source.Common.ChainClient.WaitForEvents())
errReason, v, err = tc.lane.Source.Common.ChainClient.RevertReasonFromTx(failedTx, evm_2_evm_onramp.EVM2EVMOnRampABI)
Expand Down Expand Up @@ -309,7 +309,7 @@ func TestSmokeCCIPRateLimit(t *testing.T) {
failedTx, _, _, err = tc.lane.Source.SendRequest(
tc.lane.Dest.ReceiverDapp.EthAddress,
actions.TokenTransfer, "msg with token more than token pool capacity",
common.HexToAddress(tc.lane.Source.Common.FeeToken.Address()))
)
require.NoError(t, err)
require.Error(t, tc.lane.Source.Common.ChainClient.WaitForEvents())
errReason, v, err = tc.lane.Source.Common.ChainClient.RevertReasonFromTx(failedTx, lock_release_token_pool.LockReleaseTokenPoolABI)
Expand Down Expand Up @@ -339,7 +339,7 @@ func TestSmokeCCIPRateLimit(t *testing.T) {
failedTx, _, _, err = tc.lane.Source.SendRequest(
tc.lane.Dest.ReceiverDapp.EthAddress,
actions.TokenTransfer, "msg with token more than token pool rate",
common.HexToAddress(tc.lane.Source.Common.FeeToken.Address()))
)
require.NoError(t, err)
require.Error(t, tc.lane.Source.Common.ChainClient.WaitForEvents())
errReason, v, err = tc.lane.Source.Common.ChainClient.RevertReasonFromTx(failedTx, lock_release_token_pool.LockReleaseTokenPoolABI)
Expand All @@ -358,3 +358,58 @@ func TestSmokeCCIPRateLimit(t *testing.T) {
})
}
}

func TestSmokeCCIPMulticall(t *testing.T) {
t.Parallel()
type subtestInput struct {
testName string
lane *actions.CCIPLane
}
l := logging.GetTestLogger(t)
TestCfg := testsetups.NewCCIPTestConfig(t, l, testconfig.Smoke)
// enable multicall in one tx for this test
TestCfg.TestGroupInput.MulticallInOneTx = utils.Ptr(true)
setUpOutput := testsetups.CCIPDefaultTestSetUp(t, l, "smoke-ccip", nil, TestCfg)
var tcs []subtestInput
if len(setUpOutput.Lanes) == 0 {
return
}

t.Cleanup(func() {
if TestCfg.TestGroupInput.MsgType == actions.TokenTransfer {
setUpOutput.Balance.Verify(t)
}
require.NoError(t, setUpOutput.TearDown())
})
for i := range setUpOutput.Lanes {
tcs = append(tcs, subtestInput{
testName: fmt.Sprintf("CCIP message transfer from network %s to network %s",
setUpOutput.Lanes[i].ForwardLane.SourceNetworkName, setUpOutput.Lanes[i].ForwardLane.DestNetworkName),
lane: setUpOutput.Lanes[i].ForwardLane,
})
if setUpOutput.Lanes[i].ReverseLane != nil {
tcs = append(tcs, subtestInput{
testName: fmt.Sprintf("CCIP message transfer from network %s to network %s",
setUpOutput.Lanes[i].ReverseLane.SourceNetworkName, setUpOutput.Lanes[i].ReverseLane.DestNetworkName),
lane: setUpOutput.Lanes[i].ReverseLane,
})
}
}
l.Info().Int("Total Lanes", len(tcs)).Msg("Starting CCIP test")
for _, testcase := range tcs {
tc := testcase
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
tc.lane.Test = t
l.Info().
Str("Source", tc.lane.SourceNetworkName).
Str("Destination", tc.lane.DestNetworkName).
Msgf("Starting lane %s -> %s", tc.lane.SourceNetworkName, tc.lane.DestNetworkName)

tc.lane.RecordStateBeforeTransfer()
err := tc.lane.Multicall(TestCfg.TestGroupInput.NoOfSendsInMulticall, TestCfg.TestGroupInput.MsgType, tc.lane.Source.MulticallContract)
require.NoError(t, err)
tc.lane.ValidateRequests()
})
}
}
13 changes: 13 additions & 0 deletions integration-tests/ccip-tests/testconfig/ccip.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type CCIPTestConfig struct {
CommitAndExecuteOnSameDON *bool `toml:",omitempty"`
NumberOfCommitNodes int `toml:",omitempty"`
MsgType string `toml:",omitempty"`
MulticallInOneTx *bool `toml:",omitempty"`
NoOfSendsInMulticall int `toml:",omitempty"`
PhaseTimeout *models.Duration `toml:",omitempty"`
TestDuration *models.Duration `toml:",omitempty"`
LocalCluster *bool `toml:",omitempty"`
Expand Down Expand Up @@ -111,6 +113,12 @@ func (c *CCIPTestConfig) ApplyOverrides(fromCfg *CCIPTestConfig) error {
if fromCfg.AmountPerToken != 0 {
c.AmountPerToken = fromCfg.AmountPerToken
}
if fromCfg.MulticallInOneTx != nil {
c.MulticallInOneTx = fromCfg.MulticallInOneTx
}
if fromCfg.NoOfSendsInMulticall != 0 {
c.NoOfSendsInMulticall = fromCfg.NoOfSendsInMulticall
}

return nil
}
Expand Down Expand Up @@ -143,6 +151,11 @@ func (c *CCIPTestConfig) Validate() error {
}
}

if c.MulticallInOneTx != nil {
if c.NoOfSendsInMulticall == 0 {
return errors.Errorf("number of sends in multisend should be greater than 0 if multisend is true")
}
}
return nil
}

Expand Down
6 changes: 6 additions & 0 deletions integration-tests/ccip-tests/testconfig/tomls/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ NoOfRoutersPerPair = 1
NoOfTokensPerChain = 2
NoOfTokensInMsg = 2
AmountPerToken = 1
MulticallInOneTx = false
NoOfSendsInMulticall = 5

[CCIP.Groups.load]
KeepEnvAlive = false
Expand All @@ -116,6 +118,8 @@ NoOfRoutersPerPair = 1
NoOfTokensPerChain = 2
NoOfTokensInMsg = 2
AmountPerToken = 1
MulticallInOneTx = false
NoOfSendsInMulticall = 5

[CCIP.Groups.chaos]
KeepEnvAlive = false
Expand All @@ -138,3 +142,5 @@ NoOfTokensInMsg = 2
AmountPerToken = 1
WaitBetweenChaosDuringLoad = '2m'
ChaosDuration = '10m'
MulticallInOneTx = false
NoOfSendsInMulticall = 5
16 changes: 9 additions & 7 deletions integration-tests/ccip-tests/testreporters/ccip.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ type PhaseStat struct {
}

type RequestStat struct {
reqNo int64
ReqNo int64
SeqNum uint64
StatusByPhase map[Phase]PhaseStat `json:"status_by_phase,omitempty"`
}

func (stat *RequestStat) UpdateState(lggr zerolog.Logger, seqNum uint64, step Phase, duration time.Duration, state Status, sendTransactionStats ...TransactionStats) {
durationInSec := duration.Seconds()
stat.SeqNum = seqNum
phaseDetails := PhaseStat{
SeqNum: seqNum,
Duration: durationInSec,
Expand All @@ -85,10 +87,10 @@ func (stat *RequestStat) UpdateState(lggr zerolog.Logger, seqNum uint64, step Ph
}
lggr.Info().
Str(fmt.Sprint(E2E), fmt.Sprintf("%s", Failure)).
Msgf("reqNo %d", stat.reqNo)
event.Str(fmt.Sprint(step), fmt.Sprintf("%s", Failure)).Msgf("reqNo %d", stat.reqNo)
Msgf("reqNo %d", stat.ReqNo)
event.Str(fmt.Sprint(step), fmt.Sprintf("%s", Failure)).Msgf("reqNo %d", stat.ReqNo)
} else {
event.Str(fmt.Sprint(step), fmt.Sprintf("%s", Success)).Msgf("reqNo %d", stat.reqNo)
event.Str(fmt.Sprint(step), fmt.Sprintf("%s", Success)).Msgf("reqNo %d", stat.ReqNo)
if step == Commit || step == ReportBlessed || step == ExecStateChanged {
stat.StatusByPhase[E2E] = PhaseStat{
SeqNum: seqNum,
Expand All @@ -98,15 +100,15 @@ func (stat *RequestStat) UpdateState(lggr zerolog.Logger, seqNum uint64, step Ph
if step == ExecStateChanged {
lggr.Info().
Str(fmt.Sprint(E2E), fmt.Sprintf("%s", Success)).
Msgf("reqNo %d", stat.reqNo)
Msgf("reqNo %d", stat.ReqNo)
}
}
}
}

func NewCCIPRequestStats(reqNo int64) *RequestStat {
return &RequestStat{
reqNo: reqNo,
ReqNo: reqNo,
StatusByPhase: make(map[Phase]PhaseStat),
}
}
Expand All @@ -122,7 +124,7 @@ type CCIPLaneStats struct {
}

func (testStats *CCIPLaneStats) UpdatePhaseStatsForReq(stat *RequestStat) {
testStats.statusByPhaseByRequests.Store(stat.reqNo, stat.StatusByPhase)
testStats.statusByPhaseByRequests.Store(stat.ReqNo, stat.StatusByPhase)
}

func (testStats *CCIPLaneStats) Aggregate(phase Phase, durationInSec float64) {
Expand Down
23 changes: 14 additions & 9 deletions integration-tests/ccip-tests/testsetups/ccip.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"github.com/AlekSi/pointer"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/rs/zerolog"
chainselectors "github.com/smartcontractkit/chain-selectors"
Expand Down Expand Up @@ -368,7 +369,7 @@ func (o *CCIPTestSetUpOutputs) AddLanesForNetworkPair(
SourceNetworkName: actions.NetworkName(networkA.Name),
DestNetworkName: actions.NetworkName(networkB.Name),
ValidationTimeout: o.Cfg.TestGroupInput.PhaseTimeout.Duration(),
SentReqs: make(map[int64]actions.CCIPRequest),
SentReqs: make(map[common.Hash][]actions.CCIPRequest),
TotalFee: big.NewInt(0),
Balance: o.Balance,
Context: ctx,
Expand Down Expand Up @@ -422,7 +423,7 @@ func (o *CCIPTestSetUpOutputs) AddLanesForNetworkPair(
DestChain: destChainClientB2A,
ValidationTimeout: o.Cfg.TestGroupInput.PhaseTimeout.Duration(),
Balance: o.Balance,
SentReqs: make(map[int64]actions.CCIPRequest),
SentReqs: make(map[common.Hash][]actions.CCIPRequest),
TotalFee: big.NewInt(0),
Context: ctx,
SrcNetworkLaneCfg: ccipLaneA2B.DstNetworkLaneCfg,
Expand Down Expand Up @@ -456,7 +457,7 @@ func (o *CCIPTestSetUpOutputs) AddLanesForNetworkPair(
setUpFuncs.Go(func() error {
lggr.Info().Msgf("Setting up lane %s to %s", networkA.Name, networkB.Name)
srcConfig, destConfig, err := ccipLaneA2B.DeployNewCCIPLane(numOfCommitNodes, commitAndExecOnSameDON, networkACmn, networkBCmn,
transferAmounts, o.BootstrapAdded, configureCLNode, o.JobAddGrp)
transferAmounts, pointer.GetBool(o.Cfg.TestGroupInput.MulticallInOneTx), o.BootstrapAdded, configureCLNode, o.JobAddGrp)
if err != nil {
allErrors.Store(multierr.Append(allErrors.Load(), fmt.Errorf("deploying lane %s to %s; err - %+v", networkA.Name, networkB.Name, err)))
return err
Expand All @@ -480,7 +481,7 @@ func (o *CCIPTestSetUpOutputs) AddLanesForNetworkPair(
if bidirectional {
lggr.Info().Msgf("Setting up lane %s to %s", networkB.Name, networkA.Name)
srcConfig, destConfig, err := ccipLaneB2A.DeployNewCCIPLane(numOfCommitNodes, commitAndExecOnSameDON, networkBCmn, networkACmn,
transferAmounts, o.BootstrapAdded, configureCLNode, o.JobAddGrp)
transferAmounts, pointer.GetBool(o.Cfg.TestGroupInput.MulticallInOneTx), o.BootstrapAdded, configureCLNode, o.JobAddGrp)
if err != nil {
lggr.Error().Err(err).Msgf("error deploying lane %s to %s", networkB.Name, networkA.Name)
allErrors.Store(multierr.Append(allErrors.Load(), fmt.Errorf("deploying lane %s to %s; err - %+v", networkB.Name, networkA.Name, err)))
Expand Down Expand Up @@ -526,8 +527,10 @@ func (o *CCIPTestSetUpOutputs) StartEventWatchers() {
for _, lane := range o.ReadLanes() {
err := lane.ForwardLane.StartEventWatchers()
require.NoError(o.Cfg.Test, err)
err = lane.ReverseLane.StartEventWatchers()
require.NoError(o.Cfg.Test, err)
if lane.ReverseLane != nil {
err = lane.ReverseLane.StartEventWatchers()
require.NoError(o.Cfg.Test, err)
}
}
}

Expand Down Expand Up @@ -574,9 +577,11 @@ func (o *CCIPTestSetUpOutputs) WaitForPriceUpdates(ctx context.Context) {
priceUpdateGrp.Go(func() error {
return waitForUpdate(*lanes.ForwardLane)
})
priceUpdateGrp.Go(func() error {
return waitForUpdate(*lanes.ReverseLane)
})
if lanes.ReverseLane != nil {
priceUpdateGrp.Go(func() error {
return waitForUpdate(*lanes.ReverseLane)
})
}
}

require.NoError(t, priceUpdateGrp.Wait())
Expand Down

0 comments on commit 6d69c82

Please sign in to comment.