From 2a4e6598c902c30debb336fb5fb1bacc5a3ab0bc Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Thu, 28 Nov 2024 22:42:52 -0600 Subject: [PATCH 01/12] add unit test for using evm codec to encode and decode plugin report, need to handle big int pointer --- core/services/relay/evm/codec/codec_test.go | 59 +++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/core/services/relay/evm/codec/codec_test.go b/core/services/relay/evm/codec/codec_test.go index 2da88abaac1..ab6eeb13769 100644 --- a/core/services/relay/evm/codec/codec_test.go +++ b/core/services/relay/evm/codec/codec_test.go @@ -3,12 +3,15 @@ package codec_test import ( "encoding/json" "math/big" + "math/rand" "testing" "github.com/cometbft/cometbft/libs/strings" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -172,6 +175,62 @@ func TestCodec_EncodeTupleWithLists(t *testing.T) { require.Equal(t, expected, hexutil.Encode(result)[2:]) } +func TestCodec_CommitReport(t *testing.T) { + input := cciptypes.CommitPluginReport{ + MerkleRoots: []cciptypes.MerkleRootChain{ + { + ChainSel: cciptypes.ChainSelector(rand.Uint64()), + OnRampAddress: common.LeftPadBytes(utils.RandomAddress().Bytes(), 32), + SeqNumsRange: cciptypes.NewSeqNumRange( + cciptypes.SeqNum(rand.Uint64()), + cciptypes.SeqNum(rand.Uint64()), + ), + MerkleRoot: utils.RandomBytes32(), + }, + { + OnRampAddress: common.LeftPadBytes(utils.RandomAddress().Bytes(), 32), + ChainSel: cciptypes.ChainSelector(rand.Uint64()), + SeqNumsRange: cciptypes.NewSeqNumRange( + cciptypes.SeqNum(rand.Uint64()), + cciptypes.SeqNum(rand.Uint64()), + ), + MerkleRoot: utils.RandomBytes32(), + }, + }, + PriceUpdates: cciptypes.PriceUpdates{ + TokenPriceUpdates: []cciptypes.TokenPrice{ + { + TokenID: cciptypes.UnknownEncodedAddress(utils.RandomAddress().String()), + Price: cciptypes.NewBigInt(utils.RandUint256()), + }, + }, + GasPriceUpdates: []cciptypes.GasPriceChain{ + {GasPrice: cciptypes.NewBigInt(utils.RandUint256()), ChainSel: cciptypes.ChainSelector(rand.Uint64())}, + {GasPrice: cciptypes.NewBigInt(utils.RandUint256()), ChainSel: cciptypes.ChainSelector(rand.Uint64())}, + {GasPrice: cciptypes.NewBigInt(utils.RandUint256()), ChainSel: cciptypes.ChainSelector(rand.Uint64())}, + }, + }, + RMNSignatures: []cciptypes.RMNECDSASignature{ + {R: utils.RandomBytes32(), S: utils.RandomBytes32()}, + {R: utils.RandomBytes32(), S: utils.RandomBytes32()}, + }, + } + config := `[{"components":[{"name":"chainSel","type":"uint64","internalType":"uint64"},{"name":"onRampAddress","type":"bytes","internalType":"bytes"},{"name":"seqNumsRange","type":"uint64[2]","internalType":"uint64[2]"},{"name":"merkleRoot","type":"bytes32","internalType":"bytes32"}],"name":"merkleRoots","type":"tuple[]","internalType":"struct MerkleRootChain[]"},{"components":[{"components":[{"name":"tokenID","type":"string","internalType":"string"},{"name":"price","type":"uint256","internalType":"uint256"}],"name":"tokenPriceUpdates","type":"tuple[]","internalType":"struct TokenPrice[]"},{"components":[{"name":"chainSel","type":"uint64","internalType":"uint64"},{"name":"gasPrice","type":"uint256","internalType":"uint256"}],"name":"gasPriceUpdates","type":"tuple[]","internalType":"struct GasPriceChain[]"}],"name":"priceUpdates","type":"tuple","internalType":"struct PriceUpdates"},{"components":[{"name":"r","type":"bytes32","internalType":"bytes32"},{"name":"s","type":"bytes32","internalType":"bytes32"}],"name":"rmnSignatures","type":"tuple[]"}]` + codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ + "CommitPluginReport": {TypeABI: config}, + }} + c, err := codec.NewCodec(codecConfig) + require.NoError(t, err) + + result, err := c.Encode(testutils.Context(t), input, "CommitPluginReport") + require.NoError(t, err) + require.NotNil(t, result) + output := cciptypes.CommitPluginReport{} + err = c.Decode(testutils.Context(t), result, &output, "CommitPluginReport") + require.NoError(t, err) + //require.Equal(t, input, output) +} + type codecInterfaceTester struct { TestSelectionSupport } From 9804484883440778fa7f552b4ecbe7586ff72219 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Fri, 29 Nov 2024 11:16:01 -0600 Subject: [PATCH 02/12] add modifier config, unit test passes --- core/services/relay/evm/codec/codec_test.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/core/services/relay/evm/codec/codec_test.go b/core/services/relay/evm/codec/codec_test.go index ab6eeb13769..3136dc1c34e 100644 --- a/core/services/relay/evm/codec/codec_test.go +++ b/core/services/relay/evm/codec/codec_test.go @@ -216,9 +216,19 @@ func TestCodec_CommitReport(t *testing.T) { }, } config := `[{"components":[{"name":"chainSel","type":"uint64","internalType":"uint64"},{"name":"onRampAddress","type":"bytes","internalType":"bytes"},{"name":"seqNumsRange","type":"uint64[2]","internalType":"uint64[2]"},{"name":"merkleRoot","type":"bytes32","internalType":"bytes32"}],"name":"merkleRoots","type":"tuple[]","internalType":"struct MerkleRootChain[]"},{"components":[{"components":[{"name":"tokenID","type":"string","internalType":"string"},{"name":"price","type":"uint256","internalType":"uint256"}],"name":"tokenPriceUpdates","type":"tuple[]","internalType":"struct TokenPrice[]"},{"components":[{"name":"chainSel","type":"uint64","internalType":"uint64"},{"name":"gasPrice","type":"uint256","internalType":"uint256"}],"name":"gasPriceUpdates","type":"tuple[]","internalType":"struct GasPriceChain[]"}],"name":"priceUpdates","type":"tuple","internalType":"struct PriceUpdates"},{"components":[{"name":"r","type":"bytes32","internalType":"bytes32"},{"name":"s","type":"bytes32","internalType":"bytes32"}],"name":"rmnSignatures","type":"tuple[]"}]` - codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ - "CommitPluginReport": {TypeABI: config}, - }} + codecConfig := types.CodecConfig{ + Configs: map[string]types.ChainCodecConfig{ + "CommitPluginReport": { + TypeABI: config, + ModifierConfigs: commoncodec.ModifiersConfig{ + &commoncodec.WrapperModifierConfig{Fields: map[string]string{ + "PriceUpdates.TokenPriceUpdates.Price": "Int", + "PriceUpdates.GasPriceUpdates.GasPrice": "Int", + }}, + }, + }, + }, + } c, err := codec.NewCodec(codecConfig) require.NoError(t, err) @@ -228,7 +238,7 @@ func TestCodec_CommitReport(t *testing.T) { output := cciptypes.CommitPluginReport{} err = c.Decode(testutils.Context(t), result, &output, "CommitPluginReport") require.NoError(t, err) - //require.Equal(t, input, output) + require.Equal(t, input, output) } type codecInterfaceTester struct { From aef5f264d5e9aed6873a2566bccf847804476481 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Sat, 30 Nov 2024 23:38:43 -0600 Subject: [PATCH 03/12] added exec report, but need to debug on modifier --- .../ccip/ccipevm/executecodec_test.go | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/core/capabilities/ccip/ccipevm/executecodec_test.go b/core/capabilities/ccip/ccipevm/executecodec_test.go index 53b215d8ed9..0d9c339a655 100644 --- a/core/capabilities/ccip/ccipevm/executecodec_test.go +++ b/core/capabilities/ccip/ccipevm/executecodec_test.go @@ -8,6 +8,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" @@ -89,6 +92,39 @@ var randomExecuteReport = func(t *testing.T, d *testSetupData) cciptypes.Execute return cciptypes.ExecutePluginReport{ChainReports: chainReports} } +func TestCodec_ExecReport(t *testing.T) { + d := testSetup(t) + input := randomExecuteReport(t, d) + config := `[{"name":"chainReports","type":"tuple[]","components":[{"name":"sourceChainSelector","type":"uint64","internalType":"uint64"},{"name":"messages","type":"tuple[]","components":[{"name":"header","type":"tuple","components":[{"name":"messageID","type":"bytes32","internalType":"bytes32"},{"name":"sourceChainSelector","type":"uint64","internalType":"uint64"},{"name":"destChainSelector","type":"uint64","internalType":"uint64"},{"name":"sequenceNumber","type":"uint64","internalType":"uint64"},{"name":"nonce","type":"uint64","internalType":"uint64"},{"name":"msgHash","type":"bytes32","internalType":"bytes32"},{"name":"onRamp","type":"bytes","internalType":"bytes"}]},{"name":"data","type":"bytes","internalType":"bytes"},{"name":"sender","type":"bytes","internalType":"bytes"},{"name":"receiver","type":"bytes","internalType":"bytes"},{"name":"extraArgs","type":"bytes","internalType":"bytes"},{"name":"feeToken","type":"bytes","internalType":"bytes"},{"name":"feeTokenAmount","type":"uint256","internalType":"uint256"},{"name":"feeValueJuels","type":"uint256","internalType":"uint256"},{"name":"tokenAmounts","type":"tuple[]","components":[{"name":"sourcePoolAddress","type":"bytes","internalType":"bytes"},{"name":"destTokenAddress","type":"bytes","internalType":"bytes"},{"name":"extraData","type":"bytes","internalType":"bytes"},{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"destExecData","type":"bytes","internalType":"bytes"}]}]},{"name":"offchainTokenData","type":"bytes[][]","internalType":"bytes[][]"},{"name":"proofs","type":"bytes32[]","internalType":"bytes32[]"},{"name":"proofFlagBits","type":"uint256","internalType":"uint256"}]}]` + codecConfig := types.CodecConfig{ + Configs: map[string]types.ChainCodecConfig{ + "ExecPluginReport": { + TypeABI: config, + ModifierConfigs: commoncodec.ModifiersConfig{ + &commoncodec.WrapperModifierConfig{ + Fields: map[string]string{ + "ChainReports.Messages.FeeTokenAmount": "Int", + //"ChainReports.Messages.FeeValueJuels": "Int", + "ChainReports.Messages.TokenAmounts.Amount": "Int", + "ChainReports.ProofFlagBits": "Int", + }, + }, + }, + }, + }, + } + c, err := codec.NewCodec(codecConfig) + require.NoError(t, err) + + result, err := c.Encode(testutils.Context(t), input, "ExecPluginReport") + require.NoError(t, err) + require.NotNil(t, result) + output := cciptypes.ExecutePluginReport{} + err = c.Decode(testutils.Context(t), result, &output, "ExecPluginReport") + require.NoError(t, err) + require.Equal(t, input, output) +} + func TestExecutePluginCodecV1(t *testing.T) { d := testSetup(t) From d9f0c07d7927f758d5356bb6030578518f7afc04 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Sat, 30 Nov 2024 23:46:14 -0600 Subject: [PATCH 04/12] fixed --- core/capabilities/ccip/ccipevm/executecodec_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/capabilities/ccip/ccipevm/executecodec_test.go b/core/capabilities/ccip/ccipevm/executecodec_test.go index 0d9c339a655..e7678963201 100644 --- a/core/capabilities/ccip/ccipevm/executecodec_test.go +++ b/core/capabilities/ccip/ccipevm/executecodec_test.go @@ -71,6 +71,7 @@ var randomExecuteReport = func(t *testing.T, d *testSetupData) cciptypes.Execute ExtraArgs: extraArgs, FeeToken: utils.RandomAddress().Bytes(), FeeTokenAmount: cciptypes.NewBigInt(utils.RandUint256()), + FeeValueJuels: cciptypes.NewBigInt(utils.RandUint256()), TokenAmounts: tokenAmounts, } } @@ -103,8 +104,8 @@ func TestCodec_ExecReport(t *testing.T) { ModifierConfigs: commoncodec.ModifiersConfig{ &commoncodec.WrapperModifierConfig{ Fields: map[string]string{ - "ChainReports.Messages.FeeTokenAmount": "Int", - //"ChainReports.Messages.FeeValueJuels": "Int", + "ChainReports.Messages.FeeTokenAmount": "Int", + "ChainReports.Messages.FeeValueJuels": "Int", "ChainReports.Messages.TokenAmounts.Amount": "Int", "ChainReports.ProofFlagBits": "Int", }, From 6a3c1d43f7e0eebda69b90f5d737e49f99a2df9e Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Sun, 1 Dec 2024 14:19:32 -0600 Subject: [PATCH 05/12] need to fix unit tests --- .../ccip/ccipevm/commitcodecv2.go | 85 ++++++++++++ .../ccip/ccipevm/commitcodecv2_test.go | 95 ++++++++++++++ core/capabilities/ccip/ccipevm/execcodecv2.go | 99 ++++++++++++++ .../ccip/ccipevm/execcodecv2_test.go | 123 ++++++++++++++++++ .../ccip/ccipevm/executecodec_test.go | 1 + 5 files changed, 403 insertions(+) create mode 100644 core/capabilities/ccip/ccipevm/commitcodecv2.go create mode 100644 core/capabilities/ccip/ccipevm/commitcodecv2_test.go create mode 100644 core/capabilities/ccip/ccipevm/execcodecv2.go create mode 100644 core/capabilities/ccip/ccipevm/execcodecv2_test.go diff --git a/core/capabilities/ccip/ccipevm/commitcodecv2.go b/core/capabilities/ccip/ccipevm/commitcodecv2.go new file mode 100644 index 00000000000..28bbe496a0d --- /dev/null +++ b/core/capabilities/ccip/ccipevm/commitcodecv2.go @@ -0,0 +1,85 @@ +package ccipevm + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +const commitReportABI = `[{"components":[{"name":"chainSel","type":"uint64","internalType":"uint64"},{"name":"onRampAddress","type":"bytes","internalType":"bytes"},{"name":"seqNumsRange","type":"uint64[2]","internalType":"uint64[2]"},{"name":"merkleRoot","type":"bytes32","internalType":"bytes32"}],"name":"merkleRoots","type":"tuple[]","internalType":"struct MerkleRootChain[]"},{"components":[{"components":[{"name":"tokenID","type":"string","internalType":"string"},{"name":"price","type":"uint256","internalType":"uint256"}],"name":"tokenPriceUpdates","type":"tuple[]","internalType":"struct TokenPrice[]"},{"components":[{"name":"chainSel","type":"uint64","internalType":"uint64"},{"name":"gasPrice","type":"uint256","internalType":"uint256"}],"name":"gasPriceUpdates","type":"tuple[]","internalType":"struct GasPriceChain[]"}],"name":"priceUpdates","type":"tuple","internalType":"struct PriceUpdates"},{"components":[{"name":"r","type":"bytes32","internalType":"bytes32"},{"name":"s","type":"bytes32","internalType":"bytes32"}],"name":"rmnSignatures","type":"tuple[]"}]` + +var commitCodecConfig = types.CodecConfig{ + Configs: map[string]types.ChainCodecConfig{ + "CommitPluginReport": { + TypeABI: commitReportABI, + ModifierConfigs: commoncodec.ModifiersConfig{ + &commoncodec.WrapperModifierConfig{Fields: map[string]string{ + "PriceUpdates.TokenPriceUpdates.Price": "Int", + "PriceUpdates.GasPriceUpdates.GasPrice": "Int", + }}, + }, + }, + }, +} + +// CommitPluginCodecV2 is a codec for encoding and decoding commit plugin reports using generic evm codec +type CommitPluginCodecV2 struct{} + +func NewCommitPluginCodecV2() *CommitPluginCodecV2 { + return &CommitPluginCodecV2{} +} + +func validateReport(report cciptypes.CommitPluginReport) error { + for _, update := range report.PriceUpdates.TokenPriceUpdates { + if !common.IsHexAddress(string(update.TokenID)) { + return fmt.Errorf("invalid token address: %s", update.TokenID) + } + if update.Price.IsEmpty() { + return fmt.Errorf("empty price for token: %s", update.TokenID) + } + } + + for _, update := range report.PriceUpdates.GasPriceUpdates { + if update.GasPrice.IsEmpty() { + return fmt.Errorf("empty gas price for chain: %d", update.ChainSel) + } + } + + return nil +} + +func (c *CommitPluginCodecV2) Encode(ctx context.Context, report cciptypes.CommitPluginReport) ([]byte, error) { + if err := validateReport(report); err != nil { + return nil, err + } + + cd, err := codec.NewCodec(commitCodecConfig) + if err != nil { + return nil, err + } + + return cd.Encode(ctx, report, "CommitPluginReport") +} + +func (c *CommitPluginCodecV2) Decode(ctx context.Context, bytes []byte) (cciptypes.CommitPluginReport, error) { + report := cciptypes.CommitPluginReport{} + cd, err := codec.NewCodec(commitCodecConfig) + if err != nil { + return report, err + } + + err = cd.Decode(ctx, bytes, &report, "CommitPluginReport") + if err != nil { + return report, err + } + + return report, nil +} + +// Ensure CommitPluginCodec implements the CommitPluginCodec interface +var _ cciptypes.CommitPluginCodec = (*CommitPluginCodecV2)(nil) diff --git a/core/capabilities/ccip/ccipevm/commitcodecv2_test.go b/core/capabilities/ccip/ccipevm/commitcodecv2_test.go new file mode 100644 index 00000000000..93360f1cd6d --- /dev/null +++ b/core/capabilities/ccip/ccipevm/commitcodecv2_test.go @@ -0,0 +1,95 @@ +package ccipevm + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" +) + +func TestCommitPluginCodecV2(t *testing.T) { + testCases := []struct { + name string + report func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport + expErr bool + }{ + { + name: "base report", + report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { + return report + }, + }, + { + name: "empty token address", + report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { + report.PriceUpdates.TokenPriceUpdates[0].TokenID = "" + return report + }, + expErr: true, + }, + { + name: "empty merkle root", + report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { + report.MerkleRoots[0].MerkleRoot = cciptypes.Bytes32{} + return report + }, + }, + //{ + // name: "zero token price", + // report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { + // report.PriceUpdates.TokenPriceUpdates[0].Price = cciptypes.NewBigInt(big.NewInt(0)) + // return report + // }, + //}, + //{ + // name: "zero gas price", + // report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { + // report.PriceUpdates.GasPriceUpdates[0].GasPrice = cciptypes.NewBigInt(big.NewInt(0)) + // return report + // }, + //}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + report := tc.report(randomCommitReport()) + commitCodec := NewCommitPluginCodecV2() + ctx := testutils.Context(t) + encodedReport, err := commitCodec.Encode(ctx, report) + if tc.expErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + decodedReport, err := commitCodec.Decode(ctx, encodedReport) + require.NoError(t, err) + require.Equal(t, report, decodedReport) + }) + } +} + +func BenchmarkCommitPluginCodecV2_Encode(b *testing.B) { + commitCodec := NewCommitPluginCodecV2() + ctx := testutils.Context(b) + + rep := randomCommitReport() + for i := 0; i < b.N; i++ { + _, err := commitCodec.Encode(ctx, rep) + require.NoError(b, err) + } +} + +func BenchmarkCommitPluginCodecV2_Decode(b *testing.B) { + commitCodec := NewCommitPluginCodecV2() + ctx := testutils.Context(b) + encodedReport, err := commitCodec.Encode(ctx, randomCommitReport()) + require.NoError(b, err) + + for i := 0; i < b.N; i++ { + _, err := commitCodec.Decode(ctx, encodedReport) + require.NoError(b, err) + } +} diff --git a/core/capabilities/ccip/ccipevm/execcodecv2.go b/core/capabilities/ccip/ccipevm/execcodecv2.go new file mode 100644 index 00000000000..fde3e46d8a1 --- /dev/null +++ b/core/capabilities/ccip/ccipevm/execcodecv2.go @@ -0,0 +1,99 @@ +package ccipevm + +import ( + "context" + "fmt" + + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" +) + +const execReportABI = `[{"name":"chainReports","type":"tuple[]","components":[{"name":"sourceChainSelector","type":"uint64","internalType":"uint64"},{"name":"messages","type":"tuple[]","components":[{"name":"header","type":"tuple","components":[{"name":"messageID","type":"bytes32","internalType":"bytes32"},{"name":"sourceChainSelector","type":"uint64","internalType":"uint64"},{"name":"destChainSelector","type":"uint64","internalType":"uint64"},{"name":"sequenceNumber","type":"uint64","internalType":"uint64"},{"name":"nonce","type":"uint64","internalType":"uint64"},{"name":"msgHash","type":"bytes32","internalType":"bytes32"},{"name":"onRamp","type":"bytes","internalType":"bytes"}]},{"name":"data","type":"bytes","internalType":"bytes"},{"name":"sender","type":"bytes","internalType":"bytes"},{"name":"receiver","type":"bytes","internalType":"bytes"},{"name":"extraArgs","type":"bytes","internalType":"bytes"},{"name":"feeToken","type":"bytes","internalType":"bytes"},{"name":"feeTokenAmount","type":"uint256","internalType":"uint256"},{"name":"feeValueJuels","type":"uint256","internalType":"uint256"},{"name":"tokenAmounts","type":"tuple[]","components":[{"name":"sourcePoolAddress","type":"bytes","internalType":"bytes"},{"name":"destTokenAddress","type":"bytes","internalType":"bytes"},{"name":"extraData","type":"bytes","internalType":"bytes"},{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"destExecData","type":"bytes","internalType":"bytes"}]}]},{"name":"offchainTokenData","type":"bytes[][]","internalType":"bytes[][]"},{"name":"proofs","type":"bytes32[]","internalType":"bytes32[]"},{"name":"proofFlagBits","type":"uint256","internalType":"uint256"}]}]` + +var execCodecConfig = types.CodecConfig{ + Configs: map[string]types.ChainCodecConfig{ + "ExecPluginReport": { + TypeABI: execReportABI, + ModifierConfigs: commoncodec.ModifiersConfig{ + &commoncodec.WrapperModifierConfig{ + Fields: map[string]string{ + "ChainReports.Messages.FeeTokenAmount": "Int", + "ChainReports.Messages.FeeValueJuels": "Int", + "ChainReports.Messages.TokenAmounts.Amount": "Int", + "ChainReports.ProofFlagBits": "Int", + }, + }, + }, + }, + }, +} + +// ExecutePluginCodecV2 is a codec for encoding and decoding execute plugin reports with generic codec +type ExecutePluginCodecV2 struct{} + +func NewExecutePluginCodecV2() *ExecutePluginCodecV2 { + return &ExecutePluginCodecV2{} +} + +func validate(report cciptypes.ExecutePluginReport) error { + for _, chainReport := range report.ChainReports { + if chainReport.ProofFlagBits.IsEmpty() { + return fmt.Errorf("proof flag bits are empty") + } + + evmProofs := make([][32]byte, 0, len(chainReport.Proofs)) + for _, proof := range chainReport.Proofs { + evmProofs = append(evmProofs, proof) + } + + for _, message := range chainReport.Messages { + for _, tokenAmount := range message.TokenAmounts { + if tokenAmount.Amount.IsEmpty() { + return fmt.Errorf("empty amount for token: %s", tokenAmount.DestTokenAddress) + } + + _, err := abiDecodeUint32(tokenAmount.DestExecData) + if err != nil { + return fmt.Errorf("decode dest gas amount: %w", err) + } + } + + _, err := decodeExtraArgsV1V2(message.ExtraArgs) + if err != nil { + return fmt.Errorf("decode extra args to get gas limit: %w", err) + } + } + } + + return nil +} + +func (e *ExecutePluginCodecV2) Encode(ctx context.Context, report cciptypes.ExecutePluginReport) ([]byte, error) { + if err := validate(report); err != nil { + return nil, err + } + + cd, err := codec.NewCodec(execCodecConfig) + if err != nil { + return nil, err + } + + return cd.Encode(ctx, report, "ExecPluginReport") +} + +func (e *ExecutePluginCodecV2) Decode(ctx context.Context, encodedReport []byte) (cciptypes.ExecutePluginReport, error) { + report := cciptypes.ExecutePluginReport{} + cd, err := codec.NewCodec(execCodecConfig) + if err != nil { + return report, err + } + + err = cd.Decode(ctx, encodedReport, &report, "ExecPluginReport") + return report, err +} + +// Ensure ExecutePluginCodec implements the ExecutePluginCodec interface +var _ cciptypes.ExecutePluginCodec = (*ExecutePluginCodecV2)(nil) diff --git a/core/capabilities/ccip/ccipevm/execcodecv2_test.go b/core/capabilities/ccip/ccipevm/execcodecv2_test.go new file mode 100644 index 00000000000..2d7080f0ef4 --- /dev/null +++ b/core/capabilities/ccip/ccipevm/execcodecv2_test.go @@ -0,0 +1,123 @@ +package ccipevm + +import ( + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/report_codec" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCodec_ExecReportV2(t *testing.T) { + d := testSetup(t) + input := randomExecuteReport(t, d) + c, err := codec.NewCodec(execCodecConfig) + require.NoError(t, err) + + result, err := c.Encode(testutils.Context(t), input, "ExecPluginReport") + require.NoError(t, err) + require.NotNil(t, result) + output := cciptypes.ExecutePluginReport{} + err = c.Decode(testutils.Context(t), result, &output, "ExecPluginReport") + require.NoError(t, err) + require.Equal(t, input, output) +} + +func TestExecutePluginCodecV2(t *testing.T) { + d := testSetup(t) + + testCases := []struct { + name string + report func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport + expErr bool + }{ + { + name: "base report", + report: func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport { return report }, + expErr: false, + }, + { + name: "reports have empty msgs", + report: func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport { + report.ChainReports[0].Messages = []cciptypes.Message{} + report.ChainReports[4].Messages = []cciptypes.Message{} + return report + }, + expErr: false, + }, + { + name: "reports have empty offchain token data", + report: func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport { + report.ChainReports[0].OffchainTokenData = [][][]byte{} + report.ChainReports[4].OffchainTokenData[1] = [][]byte{} + return report + }, + expErr: false, + }, + } + + ctx := testutils.Context(t) + + // Deploy the contract + transactor := testutils.MustNewSimTransactor(t) + simulatedBackend := backends.NewSimulatedBackend(core.GenesisAlloc{ + transactor.From: {Balance: assets.Ether(1000).ToInt()}, + }, 30e6) + address, _, _, err := report_codec.DeployReportCodec(transactor, simulatedBackend) + require.NoError(t, err) + simulatedBackend.Commit() + contract, err := report_codec.NewReportCodec(address, simulatedBackend) + require.NoError(t, err) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cd := NewExecutePluginCodecV2() + report := tc.report(randomExecuteReport(t, d)) + bytes, err := cd.Encode(ctx, report) + if tc.expErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + testSetup(t) + + // ignore msg hash in comparison + for i := range report.ChainReports { + for j := range report.ChainReports[i].Messages { + report.ChainReports[i].Messages[j].Header.MsgHash = cciptypes.Bytes32{} + report.ChainReports[i].Messages[j].Header.OnRamp = cciptypes.UnknownAddress{} + report.ChainReports[i].Messages[j].FeeToken = cciptypes.UnknownAddress{} + report.ChainReports[i].Messages[j].ExtraArgs = cciptypes.Bytes{} + report.ChainReports[i].Messages[j].FeeTokenAmount = cciptypes.BigInt{} + report.ChainReports[i].Messages[j].FeeValueJuels = cciptypes.BigInt{} + } + } + + // decode using the contract + contractDecodedReport, err := contract.DecodeExecuteReport(&bind.CallOpts{Context: ctx}, bytes) + assert.NoError(t, err) + assert.Equal(t, len(report.ChainReports), len(contractDecodedReport)) + for i, expReport := range report.ChainReports { + actReport := contractDecodedReport[i] + assert.Equal(t, expReport.OffchainTokenData, actReport.OffchainTokenData) + assert.Equal(t, len(expReport.Messages), len(actReport.Messages)) + assert.Equal(t, uint64(expReport.SourceChainSelector), actReport.SourceChainSelector) + } + + // decode using the codec + codecDecoded, err := cd.Decode(ctx, bytes) + assert.NoError(t, err) + assert.Equal(t, report, codecDecoded) + }) + } +} diff --git a/core/capabilities/ccip/ccipevm/executecodec_test.go b/core/capabilities/ccip/ccipevm/executecodec_test.go index e7678963201..eabf1a7d623 100644 --- a/core/capabilities/ccip/ccipevm/executecodec_test.go +++ b/core/capabilities/ccip/ccipevm/executecodec_test.go @@ -193,6 +193,7 @@ func TestExecutePluginCodecV1(t *testing.T) { report.ChainReports[i].Messages[j].FeeToken = cciptypes.UnknownAddress{} report.ChainReports[i].Messages[j].ExtraArgs = cciptypes.Bytes{} report.ChainReports[i].Messages[j].FeeTokenAmount = cciptypes.BigInt{} + report.ChainReports[i].Messages[j].FeeValueJuels = cciptypes.BigInt{} } } From 3ea3a4f94770d4ba7593736f7abbb7b7b801aa69 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 2 Dec 2024 14:46:26 -0600 Subject: [PATCH 06/12] fix commit test --- .../ccip/ccipevm/commitcodecv2.go | 24 ++++++++++++ .../ccip/ccipevm/commitcodecv2_test.go | 29 ++++++++------- .../ccip/ccipevm/executecodec_test.go | 37 ------------------- 3 files changed, 39 insertions(+), 51 deletions(-) diff --git a/core/capabilities/ccip/ccipevm/commitcodecv2.go b/core/capabilities/ccip/ccipevm/commitcodecv2.go index 28bbe496a0d..18941743759 100644 --- a/core/capabilities/ccip/ccipevm/commitcodecv2.go +++ b/core/capabilities/ccip/ccipevm/commitcodecv2.go @@ -3,6 +3,7 @@ package ccipevm import ( "context" "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" @@ -66,6 +67,25 @@ func (c *CommitPluginCodecV2) Encode(ctx context.Context, report cciptypes.Commi return cd.Encode(ctx, report, "CommitPluginReport") } +func postProcess(report *cciptypes.CommitPluginReport) error { + for index, update := range report.PriceUpdates.TokenPriceUpdates { + if !common.IsHexAddress(string(update.TokenID)) { + return fmt.Errorf("invalid token address: %s", update.TokenID) + } + if update.Price.IsEmpty() || update.Price.Int64() == 0 { + report.PriceUpdates.TokenPriceUpdates[index].Price = cciptypes.NewBigInt(big.NewInt(0)) + } + } + + for idx, update := range report.PriceUpdates.GasPriceUpdates { + if update.GasPrice.IsEmpty() || update.GasPrice.Int64() == 0 { + report.PriceUpdates.GasPriceUpdates[idx].GasPrice = cciptypes.NewBigInt(big.NewInt(0)) + } + } + + return nil +} + func (c *CommitPluginCodecV2) Decode(ctx context.Context, bytes []byte) (cciptypes.CommitPluginReport, error) { report := cciptypes.CommitPluginReport{} cd, err := codec.NewCodec(commitCodecConfig) @@ -78,6 +98,10 @@ func (c *CommitPluginCodecV2) Decode(ctx context.Context, bytes []byte) (cciptyp return report, err } + if err = postProcess(&report); err != nil { + return report, err + } + return report, nil } diff --git a/core/capabilities/ccip/ccipevm/commitcodecv2_test.go b/core/capabilities/ccip/ccipevm/commitcodecv2_test.go index 93360f1cd6d..6754980349c 100644 --- a/core/capabilities/ccip/ccipevm/commitcodecv2_test.go +++ b/core/capabilities/ccip/ccipevm/commitcodecv2_test.go @@ -1,6 +1,7 @@ package ccipevm import ( + "math/big" "testing" "github.com/stretchr/testify/assert" @@ -37,20 +38,20 @@ func TestCommitPluginCodecV2(t *testing.T) { return report }, }, - //{ - // name: "zero token price", - // report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { - // report.PriceUpdates.TokenPriceUpdates[0].Price = cciptypes.NewBigInt(big.NewInt(0)) - // return report - // }, - //}, - //{ - // name: "zero gas price", - // report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { - // report.PriceUpdates.GasPriceUpdates[0].GasPrice = cciptypes.NewBigInt(big.NewInt(0)) - // return report - // }, - //}, + { + name: "zero token price", + report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { + report.PriceUpdates.TokenPriceUpdates[0].Price = cciptypes.NewBigInt(big.NewInt(0)) + return report + }, + }, + { + name: "zero gas price", + report: func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport { + report.PriceUpdates.GasPriceUpdates[0].GasPrice = cciptypes.NewBigInt(big.NewInt(0)) + return report + }, + }, } for _, tc := range testCases { diff --git a/core/capabilities/ccip/ccipevm/executecodec_test.go b/core/capabilities/ccip/ccipevm/executecodec_test.go index eabf1a7d623..de926a028b3 100644 --- a/core/capabilities/ccip/ccipevm/executecodec_test.go +++ b/core/capabilities/ccip/ccipevm/executecodec_test.go @@ -8,10 +8,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" - commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -93,39 +89,6 @@ var randomExecuteReport = func(t *testing.T, d *testSetupData) cciptypes.Execute return cciptypes.ExecutePluginReport{ChainReports: chainReports} } -func TestCodec_ExecReport(t *testing.T) { - d := testSetup(t) - input := randomExecuteReport(t, d) - config := `[{"name":"chainReports","type":"tuple[]","components":[{"name":"sourceChainSelector","type":"uint64","internalType":"uint64"},{"name":"messages","type":"tuple[]","components":[{"name":"header","type":"tuple","components":[{"name":"messageID","type":"bytes32","internalType":"bytes32"},{"name":"sourceChainSelector","type":"uint64","internalType":"uint64"},{"name":"destChainSelector","type":"uint64","internalType":"uint64"},{"name":"sequenceNumber","type":"uint64","internalType":"uint64"},{"name":"nonce","type":"uint64","internalType":"uint64"},{"name":"msgHash","type":"bytes32","internalType":"bytes32"},{"name":"onRamp","type":"bytes","internalType":"bytes"}]},{"name":"data","type":"bytes","internalType":"bytes"},{"name":"sender","type":"bytes","internalType":"bytes"},{"name":"receiver","type":"bytes","internalType":"bytes"},{"name":"extraArgs","type":"bytes","internalType":"bytes"},{"name":"feeToken","type":"bytes","internalType":"bytes"},{"name":"feeTokenAmount","type":"uint256","internalType":"uint256"},{"name":"feeValueJuels","type":"uint256","internalType":"uint256"},{"name":"tokenAmounts","type":"tuple[]","components":[{"name":"sourcePoolAddress","type":"bytes","internalType":"bytes"},{"name":"destTokenAddress","type":"bytes","internalType":"bytes"},{"name":"extraData","type":"bytes","internalType":"bytes"},{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"destExecData","type":"bytes","internalType":"bytes"}]}]},{"name":"offchainTokenData","type":"bytes[][]","internalType":"bytes[][]"},{"name":"proofs","type":"bytes32[]","internalType":"bytes32[]"},{"name":"proofFlagBits","type":"uint256","internalType":"uint256"}]}]` - codecConfig := types.CodecConfig{ - Configs: map[string]types.ChainCodecConfig{ - "ExecPluginReport": { - TypeABI: config, - ModifierConfigs: commoncodec.ModifiersConfig{ - &commoncodec.WrapperModifierConfig{ - Fields: map[string]string{ - "ChainReports.Messages.FeeTokenAmount": "Int", - "ChainReports.Messages.FeeValueJuels": "Int", - "ChainReports.Messages.TokenAmounts.Amount": "Int", - "ChainReports.ProofFlagBits": "Int", - }, - }, - }, - }, - }, - } - c, err := codec.NewCodec(codecConfig) - require.NoError(t, err) - - result, err := c.Encode(testutils.Context(t), input, "ExecPluginReport") - require.NoError(t, err) - require.NotNil(t, result) - output := cciptypes.ExecutePluginReport{} - err = c.Decode(testutils.Context(t), result, &output, "ExecPluginReport") - require.NoError(t, err) - require.Equal(t, input, output) -} - func TestExecutePluginCodecV1(t *testing.T) { d := testSetup(t) From 8bae2c97cd5d909ec3593beb5e47704385ef03cf Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 2 Dec 2024 14:51:21 -0600 Subject: [PATCH 07/12] remove contract decode --- .../ccip/ccipevm/execcodecv2_test.go | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/core/capabilities/ccip/ccipevm/execcodecv2_test.go b/core/capabilities/ccip/ccipevm/execcodecv2_test.go index 2d7080f0ef4..62553331396 100644 --- a/core/capabilities/ccip/ccipevm/execcodecv2_test.go +++ b/core/capabilities/ccip/ccipevm/execcodecv2_test.go @@ -3,14 +3,9 @@ package ccipevm import ( "testing" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" - "github.com/ethereum/go-ethereum/core" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/report_codec" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/stretchr/testify/assert" @@ -68,15 +63,15 @@ func TestExecutePluginCodecV2(t *testing.T) { ctx := testutils.Context(t) // Deploy the contract - transactor := testutils.MustNewSimTransactor(t) - simulatedBackend := backends.NewSimulatedBackend(core.GenesisAlloc{ - transactor.From: {Balance: assets.Ether(1000).ToInt()}, - }, 30e6) - address, _, _, err := report_codec.DeployReportCodec(transactor, simulatedBackend) - require.NoError(t, err) - simulatedBackend.Commit() - contract, err := report_codec.NewReportCodec(address, simulatedBackend) - require.NoError(t, err) + //transactor := testutils.MustNewSimTransactor(t) + //simulatedBackend := backends.NewSimulatedBackend(core.GenesisAlloc{ + // transactor.From: {Balance: assets.Ether(1000).ToInt()}, + //}, 30e6) + //address, _, _, err := report_codec.DeployReportCodec(transactor, simulatedBackend) + //require.NoError(t, err) + //simulatedBackend.Commit() + //contract, err := report_codec.NewReportCodec(address, simulatedBackend) + //require.NoError(t, err) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -92,27 +87,27 @@ func TestExecutePluginCodecV2(t *testing.T) { testSetup(t) // ignore msg hash in comparison - for i := range report.ChainReports { - for j := range report.ChainReports[i].Messages { - report.ChainReports[i].Messages[j].Header.MsgHash = cciptypes.Bytes32{} - report.ChainReports[i].Messages[j].Header.OnRamp = cciptypes.UnknownAddress{} - report.ChainReports[i].Messages[j].FeeToken = cciptypes.UnknownAddress{} - report.ChainReports[i].Messages[j].ExtraArgs = cciptypes.Bytes{} - report.ChainReports[i].Messages[j].FeeTokenAmount = cciptypes.BigInt{} - report.ChainReports[i].Messages[j].FeeValueJuels = cciptypes.BigInt{} - } - } + //for i := range report.ChainReports { + // for j := range report.ChainReports[i].Messages { + // report.ChainReports[i].Messages[j].Header.MsgHash = cciptypes.Bytes32{} + // report.ChainReports[i].Messages[j].Header.OnRamp = cciptypes.UnknownAddress{} + // report.ChainReports[i].Messages[j].FeeToken = cciptypes.UnknownAddress{} + // report.ChainReports[i].Messages[j].ExtraArgs = cciptypes.Bytes{} + // report.ChainReports[i].Messages[j].FeeTokenAmount = cciptypes.BigInt{} + // report.ChainReports[i].Messages[j].FeeValueJuels = cciptypes.BigInt{} + // } + //} - // decode using the contract - contractDecodedReport, err := contract.DecodeExecuteReport(&bind.CallOpts{Context: ctx}, bytes) - assert.NoError(t, err) - assert.Equal(t, len(report.ChainReports), len(contractDecodedReport)) - for i, expReport := range report.ChainReports { - actReport := contractDecodedReport[i] - assert.Equal(t, expReport.OffchainTokenData, actReport.OffchainTokenData) - assert.Equal(t, len(expReport.Messages), len(actReport.Messages)) - assert.Equal(t, uint64(expReport.SourceChainSelector), actReport.SourceChainSelector) - } + //// decode using the contract + //contractDecodedReport, err := contract.DecodeExecuteReport(&bind.CallOpts{Context: ctx}, bytes) + //assert.NoError(t, err) + //assert.Equal(t, len(report.ChainReports), len(contractDecodedReport)) + //for i, expReport := range report.ChainReports { + // actReport := contractDecodedReport[i] + // assert.Equal(t, expReport.OffchainTokenData, actReport.OffchainTokenData) + // assert.Equal(t, len(expReport.Messages), len(actReport.Messages)) + // assert.Equal(t, uint64(expReport.SourceChainSelector), actReport.SourceChainSelector) + //} // decode using the codec codecDecoded, err := cd.Decode(ctx, bytes) From 090d18e173a5a1fc3ff96b6f40b7d2ed8f65be0d Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 2 Dec 2024 15:22:01 -0600 Subject: [PATCH 08/12] update exec to remove few values --- .../ccip/ccipevm/commitcodecv2.go | 4 +- core/capabilities/ccip/ccipevm/execcodecv2.go | 27 ++++++++++++ .../ccip/ccipevm/execcodecv2_test.go | 41 ++++--------------- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/core/capabilities/ccip/ccipevm/commitcodecv2.go b/core/capabilities/ccip/ccipevm/commitcodecv2.go index 18941743759..01ea501eb9f 100644 --- a/core/capabilities/ccip/ccipevm/commitcodecv2.go +++ b/core/capabilities/ccip/ccipevm/commitcodecv2.go @@ -67,7 +67,7 @@ func (c *CommitPluginCodecV2) Encode(ctx context.Context, report cciptypes.Commi return cd.Encode(ctx, report, "CommitPluginReport") } -func postProcess(report *cciptypes.CommitPluginReport) error { +func commitPostProcess(report *cciptypes.CommitPluginReport) error { for index, update := range report.PriceUpdates.TokenPriceUpdates { if !common.IsHexAddress(string(update.TokenID)) { return fmt.Errorf("invalid token address: %s", update.TokenID) @@ -98,7 +98,7 @@ func (c *CommitPluginCodecV2) Decode(ctx context.Context, bytes []byte) (cciptyp return report, err } - if err = postProcess(&report); err != nil { + if err = commitPostProcess(&report); err != nil { return report, err } diff --git a/core/capabilities/ccip/ccipevm/execcodecv2.go b/core/capabilities/ccip/ccipevm/execcodecv2.go index fde3e46d8a1..8e3f4a8e065 100644 --- a/core/capabilities/ccip/ccipevm/execcodecv2.go +++ b/core/capabilities/ccip/ccipevm/execcodecv2.go @@ -2,6 +2,7 @@ package ccipevm import ( "context" + "errors" "fmt" commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" @@ -84,6 +85,24 @@ func (e *ExecutePluginCodecV2) Encode(ctx context.Context, report cciptypes.Exec return cd.Encode(ctx, report, "ExecPluginReport") } +func execPostProcess(report *cciptypes.ExecutePluginReport) error { + if len(report.ChainReports) == 0 { + return errors.New("chain reports is empty") + } + + for i, evmChainReport := range report.ChainReports { + for j := range evmChainReport.Messages { + report.ChainReports[i].Messages[j].Header.MsgHash = cciptypes.Bytes32{} + report.ChainReports[i].Messages[j].Header.OnRamp = cciptypes.UnknownAddress{} + report.ChainReports[i].Messages[j].ExtraArgs = cciptypes.Bytes{} + report.ChainReports[i].Messages[j].FeeToken = cciptypes.UnknownAddress{} + report.ChainReports[i].Messages[j].FeeTokenAmount = cciptypes.BigInt{} + } + } + + return nil +} + func (e *ExecutePluginCodecV2) Decode(ctx context.Context, encodedReport []byte) (cciptypes.ExecutePluginReport, error) { report := cciptypes.ExecutePluginReport{} cd, err := codec.NewCodec(execCodecConfig) @@ -92,6 +111,14 @@ func (e *ExecutePluginCodecV2) Decode(ctx context.Context, encodedReport []byte) } err = cd.Decode(ctx, encodedReport, &report, "ExecPluginReport") + if err != nil { + return report, err + } + + if err = execPostProcess(&report); err != nil { + return report, err + } + return report, err } diff --git a/core/capabilities/ccip/ccipevm/execcodecv2_test.go b/core/capabilities/ccip/ccipevm/execcodecv2_test.go index 62553331396..bfb150ea2af 100644 --- a/core/capabilities/ccip/ccipevm/execcodecv2_test.go +++ b/core/capabilities/ccip/ccipevm/execcodecv2_test.go @@ -62,17 +62,6 @@ func TestExecutePluginCodecV2(t *testing.T) { ctx := testutils.Context(t) - // Deploy the contract - //transactor := testutils.MustNewSimTransactor(t) - //simulatedBackend := backends.NewSimulatedBackend(core.GenesisAlloc{ - // transactor.From: {Balance: assets.Ether(1000).ToInt()}, - //}, 30e6) - //address, _, _, err := report_codec.DeployReportCodec(transactor, simulatedBackend) - //require.NoError(t, err) - //simulatedBackend.Commit() - //contract, err := report_codec.NewReportCodec(address, simulatedBackend) - //require.NoError(t, err) - for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cd := NewExecutePluginCodecV2() @@ -87,27 +76,15 @@ func TestExecutePluginCodecV2(t *testing.T) { testSetup(t) // ignore msg hash in comparison - //for i := range report.ChainReports { - // for j := range report.ChainReports[i].Messages { - // report.ChainReports[i].Messages[j].Header.MsgHash = cciptypes.Bytes32{} - // report.ChainReports[i].Messages[j].Header.OnRamp = cciptypes.UnknownAddress{} - // report.ChainReports[i].Messages[j].FeeToken = cciptypes.UnknownAddress{} - // report.ChainReports[i].Messages[j].ExtraArgs = cciptypes.Bytes{} - // report.ChainReports[i].Messages[j].FeeTokenAmount = cciptypes.BigInt{} - // report.ChainReports[i].Messages[j].FeeValueJuels = cciptypes.BigInt{} - // } - //} - - //// decode using the contract - //contractDecodedReport, err := contract.DecodeExecuteReport(&bind.CallOpts{Context: ctx}, bytes) - //assert.NoError(t, err) - //assert.Equal(t, len(report.ChainReports), len(contractDecodedReport)) - //for i, expReport := range report.ChainReports { - // actReport := contractDecodedReport[i] - // assert.Equal(t, expReport.OffchainTokenData, actReport.OffchainTokenData) - // assert.Equal(t, len(expReport.Messages), len(actReport.Messages)) - // assert.Equal(t, uint64(expReport.SourceChainSelector), actReport.SourceChainSelector) - //} + for i := range report.ChainReports { + for j := range report.ChainReports[i].Messages { + report.ChainReports[i].Messages[j].Header.MsgHash = cciptypes.Bytes32{} + report.ChainReports[i].Messages[j].Header.OnRamp = cciptypes.UnknownAddress{} + report.ChainReports[i].Messages[j].FeeToken = cciptypes.UnknownAddress{} + report.ChainReports[i].Messages[j].ExtraArgs = cciptypes.Bytes{} + report.ChainReports[i].Messages[j].FeeTokenAmount = cciptypes.BigInt{} + } + } // decode using the codec codecDecoded, err := cd.Decode(ctx, bytes) From 7a9173b1a7cb0926eb360752110b62fa81ccabb0 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 2 Dec 2024 15:24:48 -0600 Subject: [PATCH 09/12] changeset --- .changeset/serious-cups-yawn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/serious-cups-yawn.md diff --git a/.changeset/serious-cups-yawn.md b/.changeset/serious-cups-yawn.md new file mode 100644 index 00000000000..3ba442ec639 --- /dev/null +++ b/.changeset/serious-cups-yawn.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +add new commit and exec codec using evm codec #added From a41ccbb921e30feecf1a0d95dfa7e268a12f5544 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 2 Dec 2024 15:55:06 -0600 Subject: [PATCH 10/12] rename file --- .../ccip/ccipevm/{execcodecv2.go => executecodecv2.go} | 0 .../ccip/ccipevm/{execcodecv2_test.go => executecodecv2_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename core/capabilities/ccip/ccipevm/{execcodecv2.go => executecodecv2.go} (100%) rename core/capabilities/ccip/ccipevm/{execcodecv2_test.go => executecodecv2_test.go} (100%) diff --git a/core/capabilities/ccip/ccipevm/execcodecv2.go b/core/capabilities/ccip/ccipevm/executecodecv2.go similarity index 100% rename from core/capabilities/ccip/ccipevm/execcodecv2.go rename to core/capabilities/ccip/ccipevm/executecodecv2.go diff --git a/core/capabilities/ccip/ccipevm/execcodecv2_test.go b/core/capabilities/ccip/ccipevm/executecodecv2_test.go similarity index 100% rename from core/capabilities/ccip/ccipevm/execcodecv2_test.go rename to core/capabilities/ccip/ccipevm/executecodecv2_test.go From a1fd4dd94ebf1dd8535d7578d97de395272dbb1c Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 2 Dec 2024 17:31:30 -0600 Subject: [PATCH 11/12] add more validation checks --- .../ccip/ccipevm/executecodecv2.go | 28 ++++++++++++++----- .../ccip/ccipevm/executecodecv2_test.go | 6 ++-- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/core/capabilities/ccip/ccipevm/executecodecv2.go b/core/capabilities/ccip/ccipevm/executecodecv2.go index 8e3f4a8e065..9f52bed604d 100644 --- a/core/capabilities/ccip/ccipevm/executecodecv2.go +++ b/core/capabilities/ccip/ccipevm/executecodecv2.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/big" commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" @@ -40,17 +41,30 @@ func NewExecutePluginCodecV2() *ExecutePluginCodecV2 { } func validate(report cciptypes.ExecutePluginReport) error { - for _, chainReport := range report.ChainReports { + for i, chainReport := range report.ChainReports { if chainReport.ProofFlagBits.IsEmpty() { - return fmt.Errorf("proof flag bits are empty") + return errors.New("proof flag bits are empty") } - evmProofs := make([][32]byte, 0, len(chainReport.Proofs)) - for _, proof := range chainReport.Proofs { - evmProofs = append(evmProofs, proof) - } + for j, message := range chainReport.Messages { + // optional fields + if message.FeeToken == nil { + report.ChainReports[i].Messages[j].FeeToken = []byte{} + } + + if message.FeeValueJuels.IsEmpty() { + report.ChainReports[i].Messages[j].FeeValueJuels = cciptypes.NewBigInt(big.NewInt(0)) + } + + if message.FeeTokenAmount.IsEmpty() { + report.ChainReports[i].Messages[j].FeeTokenAmount = cciptypes.NewBigInt(big.NewInt(0)) + } + + // required fields + if message.Sender == nil { + return errors.New("message sender is nil") + } - for _, message := range chainReport.Messages { for _, tokenAmount := range message.TokenAmounts { if tokenAmount.Amount.IsEmpty() { return fmt.Errorf("empty amount for token: %s", tokenAmount.DestTokenAddress) diff --git a/core/capabilities/ccip/ccipevm/executecodecv2_test.go b/core/capabilities/ccip/ccipevm/executecodecv2_test.go index bfb150ea2af..7f22ac9a842 100644 --- a/core/capabilities/ccip/ccipevm/executecodecv2_test.go +++ b/core/capabilities/ccip/ccipevm/executecodecv2_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestCodec_ExecReportV2(t *testing.T) { +func TestExecuteCodec(t *testing.T) { d := testSetup(t) input := randomExecuteReport(t, d) c, err := codec.NewCodec(execCodecConfig) @@ -71,7 +71,7 @@ func TestExecutePluginCodecV2(t *testing.T) { assert.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) testSetup(t) @@ -88,7 +88,7 @@ func TestExecutePluginCodecV2(t *testing.T) { // decode using the codec codecDecoded, err := cd.Decode(ctx, bytes) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, report, codecDecoded) }) } From e03ec6bb21161bba654daf88078b7ff346edf739 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 2 Dec 2024 17:36:42 -0600 Subject: [PATCH 12/12] update constructor --- .../ccip/ccipevm/commitcodecv2.go | 26 +++++++++--------- .../ccip/ccipevm/commitcodecv2_test.go | 9 ++++--- .../ccip/ccipevm/executecodecv2.go | 27 +++++++++---------- .../ccip/ccipevm/executecodecv2_test.go | 3 ++- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/core/capabilities/ccip/ccipevm/commitcodecv2.go b/core/capabilities/ccip/ccipevm/commitcodecv2.go index 01ea501eb9f..16e98765910 100644 --- a/core/capabilities/ccip/ccipevm/commitcodecv2.go +++ b/core/capabilities/ccip/ccipevm/commitcodecv2.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -29,10 +30,16 @@ var commitCodecConfig = types.CodecConfig{ } // CommitPluginCodecV2 is a codec for encoding and decoding commit plugin reports using generic evm codec -type CommitPluginCodecV2 struct{} +type CommitPluginCodecV2 struct { + codec commontypes.RemoteCodec +} -func NewCommitPluginCodecV2() *CommitPluginCodecV2 { - return &CommitPluginCodecV2{} +func NewCommitPluginCodecV2() (*CommitPluginCodecV2, error) { + cd, err := codec.NewCodec(commitCodecConfig) + if err != nil { + return nil, err + } + return &CommitPluginCodecV2{codec: cd}, nil } func validateReport(report cciptypes.CommitPluginReport) error { @@ -59,12 +66,7 @@ func (c *CommitPluginCodecV2) Encode(ctx context.Context, report cciptypes.Commi return nil, err } - cd, err := codec.NewCodec(commitCodecConfig) - if err != nil { - return nil, err - } - - return cd.Encode(ctx, report, "CommitPluginReport") + return c.codec.Encode(ctx, report, "CommitPluginReport") } func commitPostProcess(report *cciptypes.CommitPluginReport) error { @@ -88,12 +90,8 @@ func commitPostProcess(report *cciptypes.CommitPluginReport) error { func (c *CommitPluginCodecV2) Decode(ctx context.Context, bytes []byte) (cciptypes.CommitPluginReport, error) { report := cciptypes.CommitPluginReport{} - cd, err := codec.NewCodec(commitCodecConfig) - if err != nil { - return report, err - } - err = cd.Decode(ctx, bytes, &report, "CommitPluginReport") + err := c.codec.Decode(ctx, bytes, &report, "CommitPluginReport") if err != nil { return report, err } diff --git a/core/capabilities/ccip/ccipevm/commitcodecv2_test.go b/core/capabilities/ccip/ccipevm/commitcodecv2_test.go index 6754980349c..3a4c3da528c 100644 --- a/core/capabilities/ccip/ccipevm/commitcodecv2_test.go +++ b/core/capabilities/ccip/ccipevm/commitcodecv2_test.go @@ -57,7 +57,8 @@ func TestCommitPluginCodecV2(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { report := tc.report(randomCommitReport()) - commitCodec := NewCommitPluginCodecV2() + commitCodec, err := NewCommitPluginCodecV2() + require.NoError(t, err) ctx := testutils.Context(t) encodedReport, err := commitCodec.Encode(ctx, report) if tc.expErr { @@ -73,7 +74,8 @@ func TestCommitPluginCodecV2(t *testing.T) { } func BenchmarkCommitPluginCodecV2_Encode(b *testing.B) { - commitCodec := NewCommitPluginCodecV2() + commitCodec, err := NewCommitPluginCodecV2() + require.NoError(b, err) ctx := testutils.Context(b) rep := randomCommitReport() @@ -84,7 +86,8 @@ func BenchmarkCommitPluginCodecV2_Encode(b *testing.B) { } func BenchmarkCommitPluginCodecV2_Decode(b *testing.B) { - commitCodec := NewCommitPluginCodecV2() + commitCodec, err := NewCommitPluginCodecV2() + require.NoError(b, err) ctx := testutils.Context(b) encodedReport, err := commitCodec.Encode(ctx, randomCommitReport()) require.NoError(b, err) diff --git a/core/capabilities/ccip/ccipevm/executecodecv2.go b/core/capabilities/ccip/ccipevm/executecodecv2.go index 9f52bed604d..0742be5b451 100644 --- a/core/capabilities/ccip/ccipevm/executecodecv2.go +++ b/core/capabilities/ccip/ccipevm/executecodecv2.go @@ -7,6 +7,7 @@ import ( "math/big" commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" @@ -34,10 +35,16 @@ var execCodecConfig = types.CodecConfig{ } // ExecutePluginCodecV2 is a codec for encoding and decoding execute plugin reports with generic codec -type ExecutePluginCodecV2 struct{} +type ExecutePluginCodecV2 struct { + codec commontypes.RemoteCodec +} -func NewExecutePluginCodecV2() *ExecutePluginCodecV2 { - return &ExecutePluginCodecV2{} +func NewExecutePluginCodecV2() (*ExecutePluginCodecV2, error) { + cd, err := codec.NewCodec(execCodecConfig) + if err != nil { + return nil, err + } + return &ExecutePluginCodecV2{codec: cd}, nil } func validate(report cciptypes.ExecutePluginReport) error { @@ -91,12 +98,7 @@ func (e *ExecutePluginCodecV2) Encode(ctx context.Context, report cciptypes.Exec return nil, err } - cd, err := codec.NewCodec(execCodecConfig) - if err != nil { - return nil, err - } - - return cd.Encode(ctx, report, "ExecPluginReport") + return e.codec.Encode(ctx, report, "ExecPluginReport") } func execPostProcess(report *cciptypes.ExecutePluginReport) error { @@ -119,12 +121,7 @@ func execPostProcess(report *cciptypes.ExecutePluginReport) error { func (e *ExecutePluginCodecV2) Decode(ctx context.Context, encodedReport []byte) (cciptypes.ExecutePluginReport, error) { report := cciptypes.ExecutePluginReport{} - cd, err := codec.NewCodec(execCodecConfig) - if err != nil { - return report, err - } - - err = cd.Decode(ctx, encodedReport, &report, "ExecPluginReport") + err := e.codec.Decode(ctx, encodedReport, &report, "ExecPluginReport") if err != nil { return report, err } diff --git a/core/capabilities/ccip/ccipevm/executecodecv2_test.go b/core/capabilities/ccip/ccipevm/executecodecv2_test.go index 7f22ac9a842..7722f01f0f3 100644 --- a/core/capabilities/ccip/ccipevm/executecodecv2_test.go +++ b/core/capabilities/ccip/ccipevm/executecodecv2_test.go @@ -64,7 +64,8 @@ func TestExecutePluginCodecV2(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - cd := NewExecutePluginCodecV2() + cd, err := NewExecutePluginCodecV2() + require.NoError(t, err) report := tc.report(randomExecuteReport(t, d)) bytes, err := cd.Encode(ctx, report) if tc.expErr {