Skip to content

Commit

Permalink
refactor extradata codec to unblock ccip ocr message optimization usi…
Browse files Browse the repository at this point in the history
…ng protobuf (#16402)

* refactor extradata codec to unblock ccip ocr message optimization using protobuf

* goimport

* revert

* minor

* minor

* refactor

* fix import

* fix import

* add comments

* fix test

* support Solana->EVM

* fix

* update

* fix make
  • Loading branch information
huangzhen1997 authored Feb 14, 2025
1 parent 224ee68 commit 13b7d5a
Show file tree
Hide file tree
Showing 19 changed files with 512 additions and 124 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-hats-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

refactor extradata codec logic and unblock ccip msg optimization using protobuf #added
3 changes: 3 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ packages:
interfaces:
CCIPOracle:
OracleCreator:
github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common:
interfaces:
ExtraDataCodec:
github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types:
interfaces:
Dispatcher:
Expand Down
19 changes: 16 additions & 3 deletions core/capabilities/ccip/ccipevm/executecodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
ccipcommon "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common"

cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"

Expand All @@ -19,9 +20,10 @@ import (
// - "OffRamp 1.6.0"
type ExecutePluginCodecV1 struct {
executeReportMethodInputs abi.Arguments
extraDataCodec ccipcommon.ExtraDataCodec
}

func NewExecutePluginCodecV1() *ExecutePluginCodecV1 {
func NewExecutePluginCodecV1(extraDataCodec ccipcommon.ExtraDataCodec) *ExecutePluginCodecV1 {
abiParsed, err := abi.JSON(strings.NewReader(offramp.OffRampABI))
if err != nil {
panic(fmt.Errorf("parse multi offramp abi: %s", err))
Expand All @@ -33,6 +35,7 @@ func NewExecutePluginCodecV1() *ExecutePluginCodecV1 {

return &ExecutePluginCodecV1{
executeReportMethodInputs: methodInputs[:1],
extraDataCodec: extraDataCodec,
}
}

Expand All @@ -59,7 +62,12 @@ func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.Exec
return nil, fmt.Errorf("empty amount for token: %s", tokenAmount.DestTokenAddress)
}

destGasAmount, err := abiDecodeUint32(tokenAmount.DestExecData)
destExecDataDecodedMap, err := e.extraDataCodec.DecodeTokenAmountDestExecData(tokenAmount.DestExecData, chainReport.SourceChainSelector)
if err != nil {
return nil, fmt.Errorf("failed to decode dest exec data: %w", err)
}

destGasAmount, err := extractDestGasAmountFromMap(destExecDataDecodedMap)
if err != nil {
return nil, fmt.Errorf("decode dest gas amount: %w", err)
}
Expand All @@ -82,7 +90,12 @@ func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.Exec
})
}

gasLimit, err := decodeExtraArgsV1V2(message.ExtraArgs)
decodedExtraArgsMap, err := e.extraDataCodec.DecodeExtraArgs(message.ExtraArgs, chainReport.SourceChainSelector)
if err != nil {
return nil, err
}

gasLimit, err := parseExtraDataMap(decodedExtraArgsMap)
if err != nil {
return nil, fmt.Errorf("decode extra args to get gas limit: %w", err)
}
Expand Down
69 changes: 52 additions & 17 deletions core/capabilities/ccip/ccipevm/executecodec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ccipevm

import (
"encoding/base64"
"math/big"
"math/rand"
"testing"

Expand All @@ -10,7 +11,11 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipsolana"
ccipcommon "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
Expand All @@ -25,7 +30,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
)

var randomExecuteReport = func(t *testing.T, d *testSetupData) cciptypes.ExecutePluginReport {
var randomExecuteReport = func(t *testing.T, d *testSetupData, chainSelector uint64, gasLimit *big.Int, destGasAmount uint32) cciptypes.ExecutePluginReport {
const numChainReports = 10
const msgsPerReport = 10
const numTokensPerMsg = 3
Expand All @@ -39,7 +44,7 @@ var randomExecuteReport = func(t *testing.T, d *testSetupData) cciptypes.Execute

tokenAmounts := make([]cciptypes.RampTokenAmount, numTokensPerMsg)
for z := 0; z < numTokensPerMsg; z++ {
encodedDestExecData, err2 := abiEncodeUint32(rand.Uint32())
encodedDestExecData, err2 := abiEncodeUint32(destGasAmount)
require.NoError(t, err2)

tokenAmounts[z] = cciptypes.RampTokenAmount{
Expand All @@ -52,7 +57,7 @@ var randomExecuteReport = func(t *testing.T, d *testSetupData) cciptypes.Execute
}

extraArgs, err := d.contract.EncodeEVMExtraArgsV1(nil, message_hasher.ClientEVMExtraArgsV1{
GasLimit: utils.RandUint256(),
GasLimit: gasLimit,
})
assert.NoError(t, err)

Expand Down Expand Up @@ -82,7 +87,7 @@ var randomExecuteReport = func(t *testing.T, d *testSetupData) cciptypes.Execute
}

chainReports[i] = cciptypes.ExecutePluginReportSingleChain{
SourceChainSelector: cciptypes.ChainSelector(rand.Uint64()),
SourceChainSelector: cciptypes.ChainSelector(chainSelector),
Messages: reportMessages,
OffchainTokenData: tokenData,
Proofs: []cciptypes.Bytes32{utils.RandomBytes32(), utils.RandomBytes32()},
Expand All @@ -95,16 +100,41 @@ var randomExecuteReport = func(t *testing.T, d *testSetupData) cciptypes.Execute

func TestExecutePluginCodecV1(t *testing.T) {
d := testSetup(t)
ctx := testutils.Context(t)
mockExtraDataCodec := &mocks.ExtraDataCodec{}
destGasAmount := rand.Uint32()
gasLimit := utils.RandUint256()
mockExtraDataCodec.On("DecodeTokenAmountDestExecData", mock.Anything, mock.Anything).Return(map[string]any{
"destgasamount": destGasAmount,
}, nil)
mockExtraDataCodec.On("DecodeExtraArgs", mock.Anything, mock.Anything).Return(map[string]any{
"gasLimit": utils.RandUint256(),
"accountIsWritableBitmap": gasLimit,
}, nil)

testCases := []struct {
name string
report func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport
expErr bool
name string
report func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport
expErr bool
chainSelector uint64
destGasAmount uint32
gasLimit *big.Int
}{
{
name: "base report",
report: func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport { return report },
expErr: false,
name: "base report",
report: func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport { return report },
expErr: false,
chainSelector: 5009297550715157269, // ETH mainnet chain selector
gasLimit: gasLimit,
destGasAmount: destGasAmount,
},
{
name: "base report",
report: func(report cciptypes.ExecutePluginReport) cciptypes.ExecutePluginReport { return report },
expErr: false,
chainSelector: 124615329519749607, // Solana mainnet chain selector
gasLimit: gasLimit,
destGasAmount: destGasAmount,
},
{
name: "reports have empty msgs",
Expand All @@ -113,7 +143,10 @@ func TestExecutePluginCodecV1(t *testing.T) {
report.ChainReports[4].Messages = []cciptypes.Message{}
return report
},
expErr: false,
expErr: false,
chainSelector: 5009297550715157269, // ETH mainnet chain selector
gasLimit: gasLimit,
destGasAmount: destGasAmount,
},
{
name: "reports have empty offchain token data",
Expand All @@ -122,12 +155,13 @@ func TestExecutePluginCodecV1(t *testing.T) {
report.ChainReports[4].OffchainTokenData[1] = [][]byte{}
return report
},
expErr: false,
expErr: false,
chainSelector: 5009297550715157269, // ETH mainnet chain selector
gasLimit: gasLimit,
destGasAmount: destGasAmount,
},
}

ctx := testutils.Context(t)

// Deploy the contract
transactor := evmtestutils.MustNewSimTransactor(t)
simulatedBackend := backends.NewSimulatedBackend(core.GenesisAlloc{
Expand All @@ -141,8 +175,8 @@ func TestExecutePluginCodecV1(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
codec := NewExecutePluginCodecV1()
report := tc.report(randomExecuteReport(t, d))
codec := NewExecutePluginCodecV1(mockExtraDataCodec)
report := tc.report(randomExecuteReport(t, d, tc.chainSelector, tc.gasLimit, tc.destGasAmount))
bytes, err := codec.Encode(ctx, report)
if tc.expErr {
assert.Error(t, err)
Expand Down Expand Up @@ -183,6 +217,7 @@ func TestExecutePluginCodecV1(t *testing.T) {
}

func Test_DecodeReport(t *testing.T) {
ExtraDataCodec := ccipcommon.NewExtraDataCodec(ccipcommon.NewExtraDataCodecParams(ExtraDataDecoder{}, ccipsolana.ExtraDataDecoder{}))
offRampABI, err := offramp.OffRampMetaData.GetAbi()
require.NoError(t, err)

Expand All @@ -201,7 +236,7 @@ func Test_DecodeReport(t *testing.T) {

rawReport := *abi.ConvertType(executeInputs[1], new([]byte)).(*[]byte)

codec := NewExecutePluginCodecV1()
codec := NewExecutePluginCodecV1(ExtraDataCodec)
decoded, err := codec.Decode(tests.Context(t), rawReport)
require.NoError(t, err)

Expand Down
11 changes: 8 additions & 3 deletions core/capabilities/ccip/ccipevm/extradatadecoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import (
cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
)

func DecodeDestExecDataToMap(DestExecData cciptypes.Bytes) (map[string]interface{}, error) {
destGasAmount, err := abiDecodeUint32(DestExecData)
// ExtraDataDecoder is a concrete implementation of ExtraDataDecoder
type ExtraDataDecoder struct{}

// DecodeDestExecDataToMap reformats bytes into a chain agnostic map[string]interface{} representation for dest exec data
func (d ExtraDataDecoder) DecodeDestExecDataToMap(destExecData cciptypes.Bytes) (map[string]interface{}, error) {
destGasAmount, err := abiDecodeUint32(destExecData)
if err != nil {
return nil, fmt.Errorf("decode dest gas amount: %w", err)
}
Expand All @@ -17,7 +21,8 @@ func DecodeDestExecDataToMap(DestExecData cciptypes.Bytes) (map[string]interface
}, nil
}

func DecodeExtraArgsToMap(extraArgs []byte) (map[string]any, error) {
// DecodeExtraArgsToMap reformats bytes into a chain agnostic map[string]any representation for extra args
func (d ExtraDataDecoder) DecodeExtraArgsToMap(extraArgs cciptypes.Bytes) (map[string]any, error) {
if len(extraArgs) < 4 {
return nil, fmt.Errorf("extra args too short: %d, should be at least 4 (i.e the extraArgs tag)", len(extraArgs))
}
Expand Down
9 changes: 5 additions & 4 deletions core/capabilities/ccip/ccipevm/extradatadecoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import (
func Test_decodeExtraData(t *testing.T) {
d := testSetup(t)
gasLimit := big.NewInt(rand.Int63())
extraDataDecoder := &ExtraDataDecoder{}

t.Run("decode extra args into map evm v1", func(t *testing.T) {
encoded, err := d.contract.EncodeEVMExtraArgsV1(nil, message_hasher.ClientEVMExtraArgsV1{
GasLimit: gasLimit,
})
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
m, err := extraDataDecoder.DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 1)

Expand All @@ -37,7 +38,7 @@ func Test_decodeExtraData(t *testing.T) {
})
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
m, err := extraDataDecoder.DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 2)

Expand Down Expand Up @@ -65,7 +66,7 @@ func Test_decodeExtraData(t *testing.T) {
encoded, err := d.contract.EncodeSVMExtraArgsV1(nil, decoded)
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
m, err := extraDataDecoder.DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 5)

Expand Down Expand Up @@ -94,7 +95,7 @@ func Test_decodeExtraData(t *testing.T) {
destGasAmount := uint32(10000)
encoded, err := abiEncodeUint32(destGasAmount)
require.NoError(t, err)
m, err := DecodeDestExecDataToMap(encoded)
m, err := extraDataDecoder.DecodeDestExecDataToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 1)

Expand Down
5 changes: 3 additions & 2 deletions core/capabilities/ccip/ccipevm/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
func Test_decodeExtraArgs(t *testing.T) {
d := testSetup(t)
gasLimit := big.NewInt(rand.Int63())
extraDataDecoder := &ExtraDataDecoder{}

t.Run("v1", func(t *testing.T) {
encoded, err := d.contract.EncodeEVMExtraArgsV1(nil, message_hasher.ClientEVMExtraArgsV1{
Expand Down Expand Up @@ -45,7 +46,7 @@ func Test_decodeExtraArgs(t *testing.T) {
})
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
m, err := extraDataDecoder.DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 1)

Expand All @@ -61,7 +62,7 @@ func Test_decodeExtraArgs(t *testing.T) {
})
require.NoError(t, err)

m, err := DecodeExtraArgsToMap(encoded)
m, err := extraDataDecoder.DecodeExtraArgsToMap(encoded)
require.NoError(t, err)
require.Len(t, m, 2)

Expand Down
Loading

0 comments on commit 13b7d5a

Please sign in to comment.