Skip to content

Commit

Permalink
ocr3 - evm commit report encoder (#1114)
Browse files Browse the repository at this point in the history
Implementation of evm commit report encoder.
  • Loading branch information
dimkouv authored Jul 1, 2024
1 parent ccb215a commit f08d3de
Show file tree
Hide file tree
Showing 2 changed files with 259 additions and 9 deletions.
133 changes: 124 additions & 9 deletions core/services/ocr3/plugins/ccipevm/commitcodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,137 @@ package ccipevm

import (
"context"
"fmt"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/libocr/offchainreporting2plus/types"

cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_multi_offramp"
"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers"
)

var _ cciptypes.CommitPluginCodec = (*CommitPluginCodec)(nil)

type CommitPluginCodec struct{}
// CommitPluginCodecV1 is a codec for encoding and decoding commit plugin reports.
// Compatible with:
// - "EVM2EVMMultiOffRamp 1.6.0-dev"
type CommitPluginCodecV1 struct {
commitReportAcceptedEventInputs abi.Arguments
}

func NewCommitPluginCodec() *CommitPluginCodec {
return &CommitPluginCodec{}
func NewCommitPluginCodecV1() *CommitPluginCodecV1 {
abiParsed, err := abi.JSON(strings.NewReader(evm_2_evm_multi_offramp.EVM2EVMMultiOffRampABI))
if err != nil {
panic(fmt.Errorf("parse multi offramp abi: %s", err))
}
eventInputs := abihelpers.MustGetEventInputs("CommitReportAccepted", abiParsed)
return &CommitPluginCodecV1{commitReportAcceptedEventInputs: eventInputs}
}

func (c *CommitPluginCodec) Encode(ctx context.Context, report cciptypes.CommitPluginReport) ([]byte, error) {
panic("implement me")
func (c *CommitPluginCodecV1) Encode(ctx context.Context, report cciptypes.CommitPluginReport) ([]byte, error) {
merkleRoots := make([]evm_2_evm_multi_offramp.EVM2EVMMultiOffRampMerkleRoot, 0, len(report.MerkleRoots))
for _, root := range report.MerkleRoots {
merkleRoots = append(merkleRoots, evm_2_evm_multi_offramp.EVM2EVMMultiOffRampMerkleRoot{
SourceChainSelector: uint64(root.ChainSel),
Interval: evm_2_evm_multi_offramp.EVM2EVMMultiOffRampInterval{
Min: uint64(root.SeqNumsRange.Start()),
Max: uint64(root.SeqNumsRange.End()),
},
MerkleRoot: root.MerkleRoot,
})
}

tokenPriceUpdates := make([]evm_2_evm_multi_offramp.InternalTokenPriceUpdate, 0, len(report.PriceUpdates.TokenPriceUpdates))
for _, update := range report.PriceUpdates.TokenPriceUpdates {
if !common.IsHexAddress(string(update.TokenID)) {
return nil, fmt.Errorf("invalid token address: %s", update.TokenID)
}
if update.Price.IsEmpty() {
return nil, fmt.Errorf("empty price for token: %s", update.TokenID)
}
tokenPriceUpdates = append(tokenPriceUpdates, evm_2_evm_multi_offramp.InternalTokenPriceUpdate{
SourceToken: common.HexToAddress(string(update.TokenID)),
UsdPerToken: update.Price.Int,
})
}

gasPriceUpdates := make([]evm_2_evm_multi_offramp.InternalGasPriceUpdate, 0, len(report.PriceUpdates.GasPriceUpdates))
for _, update := range report.PriceUpdates.GasPriceUpdates {
if update.GasPrice.IsEmpty() {
return nil, fmt.Errorf("empty gas price for chain: %d", update.ChainSel)
}

gasPriceUpdates = append(gasPriceUpdates, evm_2_evm_multi_offramp.InternalGasPriceUpdate{
DestChainSelector: uint64(update.ChainSel),
UsdPerUnitGas: update.GasPrice.Int,
})
}

evmReport := evm_2_evm_multi_offramp.EVM2EVMMultiOffRampCommitReport{
PriceUpdates: evm_2_evm_multi_offramp.InternalPriceUpdates{
TokenPriceUpdates: tokenPriceUpdates,
GasPriceUpdates: gasPriceUpdates,
},
MerkleRoots: merkleRoots,
}

return c.commitReportAcceptedEventInputs.PackValues([]interface{}{evmReport})
}

func (c *CommitPluginCodec) Decode(ctx context.Context, bytes []byte) (cciptypes.CommitPluginReport, error) {
panic("implement me")
func (c *CommitPluginCodecV1) Decode(ctx context.Context, bytes []byte) (cciptypes.CommitPluginReport, error) {
unpacked, err := c.commitReportAcceptedEventInputs.Unpack(bytes)
if err != nil {
return cciptypes.CommitPluginReport{}, err
}
if len(unpacked) != 1 {
return cciptypes.CommitPluginReport{}, fmt.Errorf("expected 1 argument, got %d", len(unpacked))
}

commitReportRaw := abi.ConvertType(unpacked[0], new(evm_2_evm_multi_offramp.EVM2EVMMultiOffRampCommitReport))
commitReport, is := commitReportRaw.(*evm_2_evm_multi_offramp.EVM2EVMMultiOffRampCommitReport)
if !is {
return cciptypes.CommitPluginReport{},
fmt.Errorf("expected EVM2EVMMultiOffRampCommitReport, got %T", unpacked[0])
}

merkleRoots := make([]cciptypes.MerkleRootChain, 0, len(commitReport.MerkleRoots))
for _, root := range commitReport.MerkleRoots {
merkleRoots = append(merkleRoots, cciptypes.MerkleRootChain{
ChainSel: cciptypes.ChainSelector(root.SourceChainSelector),
SeqNumsRange: cciptypes.NewSeqNumRange(
cciptypes.SeqNum(root.Interval.Min),
cciptypes.SeqNum(root.Interval.Max),
),
MerkleRoot: root.MerkleRoot,
})
}

tokenPriceUpdates := make([]cciptypes.TokenPrice, 0, len(commitReport.PriceUpdates.TokenPriceUpdates))
for _, update := range commitReport.PriceUpdates.TokenPriceUpdates {
tokenPriceUpdates = append(tokenPriceUpdates, cciptypes.TokenPrice{
TokenID: types.Account(update.SourceToken.String()),
Price: cciptypes.NewBigInt(big.NewInt(0).Set(update.UsdPerToken)),
})
}

gasPriceUpdates := make([]cciptypes.GasPriceChain, 0, len(commitReport.PriceUpdates.GasPriceUpdates))
for _, update := range commitReport.PriceUpdates.GasPriceUpdates {
gasPriceUpdates = append(gasPriceUpdates, cciptypes.GasPriceChain{
GasPrice: cciptypes.NewBigInt(big.NewInt(0).Set(update.UsdPerUnitGas)),
ChainSel: cciptypes.ChainSelector(update.DestChainSelector),
})
}

return cciptypes.CommitPluginReport{
MerkleRoots: merkleRoots,
PriceUpdates: cciptypes.PriceUpdates{
TokenPriceUpdates: tokenPriceUpdates,
GasPriceUpdates: gasPriceUpdates,
},
}, nil
}

// Ensure CommitPluginCodec implements the CommitPluginCodec interface
var _ cciptypes.CommitPluginCodec = (*CommitPluginCodecV1)(nil)
135 changes: 135 additions & 0 deletions core/services/ocr3/plugins/ccipevm/commitcodec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package ccipevm

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

"github.com/smartcontractkit/libocr/offchainreporting2plus/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils"
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
)

var randomReport = func() cciptypes.CommitPluginReport {
return cciptypes.CommitPluginReport{
MerkleRoots: []cciptypes.MerkleRootChain{
{
ChainSel: cciptypes.ChainSelector(rand.Uint64()),
SeqNumsRange: cciptypes.NewSeqNumRange(
cciptypes.SeqNum(rand.Uint64()),
cciptypes.SeqNum(rand.Uint64()),
),
MerkleRoot: utils.RandomBytes32(),
},
{
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: types.Account(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())},
},
},
}
}

func TestCommitPluginCodec(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(randomReport())
commitCodec := NewCommitPluginCodecV1()
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 BenchmarkCommitPluginCodec_Encode(b *testing.B) {
commitCodec := NewCommitPluginCodecV1()
ctx := testutils.Context(b)

rep := randomReport()
for i := 0; i < b.N; i++ {
_, err := commitCodec.Encode(ctx, rep)
require.NoError(b, err)
}
}

func BenchmarkCommitPluginCodec_Decode(b *testing.B) {
commitCodec := NewCommitPluginCodecV1()
ctx := testutils.Context(b)
encodedReport, err := commitCodec.Encode(ctx, randomReport())
require.NoError(b, err)

for i := 0; i < b.N; i++ {
_, err := commitCodec.Decode(ctx, encodedReport)
require.NoError(b, err)
}
}

0 comments on commit f08d3de

Please sign in to comment.