Skip to content

Commit

Permalink
execute plugin report codec (#1179)
Browse files Browse the repository at this point in the history
Implementation of execution plugin evm codec.

Some information marked with `todo` is lost on encoding but not required
at the moment.
We will revisit this later by adding some generic data field or with a
different approach.
  • Loading branch information
dimkouv authored Jul 12, 2024
1 parent e0e18c5 commit 87138ca
Show file tree
Hide file tree
Showing 12 changed files with 1,013 additions and 62 deletions.
1 change: 1 addition & 0 deletions contracts/scripts/native_solc_compile_all_ccip
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ compileContract ccip/NonceManager.sol
compileContract ccip/test/helpers/BurnMintERC677Helper.sol
compileContract ccip/test/helpers/CommitStoreHelper.sol
compileContract ccip/test/helpers/MessageHasher.sol
compileContract ccip/test/helpers/ReportCodec.sol
compileContract ccip/test/helpers/receivers/MaybeRevertMessageReceiver.sol
compileContract ccip/test/mocks/MockRMN1_0.sol
compileContract ccip/test/mocks/MockE2EUSDCTokenMessenger.sol
Expand Down
18 changes: 18 additions & 0 deletions contracts/src/v0.8/ccip/test/helpers/ReportCodec.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Internal} from "../../libraries/Internal.sol";
import {EVM2EVMMultiOffRamp} from "../../offRamp/EVM2EVMMultiOffRamp.sol";

contract ReportCodec {
event ExecuteReportDecoded(Internal.ExecutionReportSingleChain[] report);
event CommitReportDecoded(EVM2EVMMultiOffRamp.CommitReport report);

function decodeExecuteReport(bytes memory report) public pure returns (Internal.ExecutionReportSingleChain[] memory) {
return abi.decode(report, (Internal.ExecutionReportSingleChain[]));
}

function decodeCommitReport(bytes memory report) public pure returns (EVM2EVMMultiOffRamp.CommitReport memory) {
return abi.decode(report, (EVM2EVMMultiOffRamp.CommitReport));
}
}
559 changes: 559 additions & 0 deletions core/gethwrappers/ccip/generated/report_codec/report_codec.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ocr3_config_encoder: ../../../contracts/solc/v0.8.24/IOCR3ConfigEncoder/IOCR3Con
ping_pong_demo: ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.abi ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.bin 1588313bb5e781d181a825247d30828f59007700f36b4b9b00391592b06ff4b4
price_registry: ../../../contracts/solc/v0.8.24/PriceRegistry/PriceRegistry.abi ../../../contracts/solc/v0.8.24/PriceRegistry/PriceRegistry.bin 0b3e253684d7085aa11f9179b71453b9db9d11cabea41605d5b4ac4128f85bfb
registry_module_owner_custom: ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.abi ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.bin cbe7698bfd811b485ac3856daf073a7bdebeefdf2583403ca4a19d5b7e2d4ae8
report_codec: ../../../contracts/solc/v0.8.24/ReportCodec/ReportCodec.abi ../../../contracts/solc/v0.8.24/ReportCodec/ReportCodec.bin c07af8433bf8dbc7981725b18922a9c4e2dea068dd204bc62adc0e926cb499c3
router: ../../../contracts/solc/v0.8.24/Router/Router.abi ../../../contracts/solc/v0.8.24/Router/Router.bin 42576577e81beea9a069bd9229caaa9a71227fbaef3871a1a2e69fd218216290
self_funded_ping_pong: ../../../contracts/solc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.abi ../../../contracts/solc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.bin 86e169636e5633854ed0b709c804066b615040bceba25aa5137450fbe6f76fa3
token_admin_registry: ../../../contracts/solc/v0.8.24/TokenAdminRegistry/TokenAdminRegistry.abi ../../../contracts/solc/v0.8.24/TokenAdminRegistry/TokenAdminRegistry.bin fb06d2cf5f7476e512c6fb7aab8eab43545efd7f0f6ca133c64ff4e3963902c4
Expand Down
1 change: 1 addition & 0 deletions core/gethwrappers/ccip/go_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ package ccip
//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.abi ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.bin PingPongDemo ping_pong_demo
//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.abi ../../../contracts/solc/v0.8.24/SelfFundedPingPong/SelfFundedPingPong.bin SelfFundedPingPong self_funded_ping_pong
//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/MessageHasher/MessageHasher.abi ../../../contracts/solc/v0.8.24/MessageHasher/MessageHasher.bin MessageHasher message_hasher
//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/ReportCodec/ReportCodec.abi ../../../contracts/solc/v0.8.24/ReportCodec/ReportCodec.bin ReportCodec report_codec
//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.abi ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.bin EtherSenderReceiver ether_sender_receiver
//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/WETH9/WETH9.abi ../../../contracts/solc/v0.8.24/WETH9/WETH9.bin WETH9 weth9

Expand Down
14 changes: 7 additions & 7 deletions core/services/ocr3/plugins/ccipevm/commitcodec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
)

var randomReport = func() cciptypes.CommitPluginReport {
var randomCommitReport = func() cciptypes.CommitPluginReport {
return cciptypes.CommitPluginReport{
MerkleRoots: []cciptypes.MerkleRootChain{
{
Expand Down Expand Up @@ -50,7 +50,7 @@ var randomReport = func() cciptypes.CommitPluginReport {
}
}

func TestCommitPluginCodec(t *testing.T) {
func TestCommitPluginCodecV1(t *testing.T) {
testCases := []struct {
name string
report func(report cciptypes.CommitPluginReport) cciptypes.CommitPluginReport
Expand Down Expand Up @@ -95,7 +95,7 @@ func TestCommitPluginCodec(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
report := tc.report(randomReport())
report := tc.report(randomCommitReport())
commitCodec := NewCommitPluginCodecV1()
ctx := testutils.Context(t)
encodedReport, err := commitCodec.Encode(ctx, report)
Expand All @@ -111,21 +111,21 @@ func TestCommitPluginCodec(t *testing.T) {
}
}

func BenchmarkCommitPluginCodec_Encode(b *testing.B) {
func BenchmarkCommitPluginCodecV1_Encode(b *testing.B) {
commitCodec := NewCommitPluginCodecV1()
ctx := testutils.Context(b)

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

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

for i := 0; i < b.N; i++ {
Expand Down
181 changes: 181 additions & 0 deletions core/services/ocr3/plugins/ccipevm/executecodec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package ccipevm

import (
"context"
"fmt"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"

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"
)

// ExecutePluginCodecV1 is a codec for encoding and decoding execute plugin reports.
// Compatible with:
// - "EVM2EVMMultiOffRamp 1.6.0-dev"
type ExecutePluginCodecV1 struct {
executeReportMethodInputs abi.Arguments
}

func NewExecutePluginCodecV1() *ExecutePluginCodecV1 {
abiParsed, err := abi.JSON(strings.NewReader(evm_2_evm_multi_offramp.EVM2EVMMultiOffRampABI))
if err != nil {
panic(fmt.Errorf("parse multi offramp abi: %s", err))
}
methodInputs := abihelpers.MustGetMethodInputs("manuallyExecute", abiParsed)
if len(methodInputs) == 0 {
panic("no inputs found for method: manuallyExecute")
}

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

func (e *ExecutePluginCodecV1) Encode(ctx context.Context, report cciptypes.ExecutePluginReport) ([]byte, error) {
evmReport := make([]evm_2_evm_multi_offramp.InternalExecutionReportSingleChain, 0, len(report.ChainReports))

for _, chainReport := range report.ChainReports {
if chainReport.ProofFlagBits.IsEmpty() {
return nil, fmt.Errorf("proof flag bits are empty")
}

evmProofs := make([][32]byte, 0, len(chainReport.Proofs))
for _, proof := range chainReport.Proofs {
evmProofs = append(evmProofs, proof)
}

evmMessages := make([]evm_2_evm_multi_offramp.InternalAny2EVMRampMessage, 0, len(chainReport.Messages))
for _, message := range chainReport.Messages {
receiver := common.BytesToAddress(message.Receiver)

tokenAmounts := make([]evm_2_evm_multi_offramp.InternalRampTokenAmount, 0, len(message.TokenAmounts))
for _, tokenAmount := range message.TokenAmounts {
if tokenAmount.Amount.IsEmpty() {
return nil, fmt.Errorf("empty amount for token: %s", tokenAmount.DestTokenAddress)
}

tokenAmounts = append(tokenAmounts, evm_2_evm_multi_offramp.InternalRampTokenAmount{
SourcePoolAddress: tokenAmount.SourcePoolAddress,
DestTokenAddress: tokenAmount.DestTokenAddress,
ExtraData: tokenAmount.ExtraData,
Amount: tokenAmount.Amount.Int,
})
}

gasLimit, err := decodeExtraArgsV1V2(message.ExtraArgs)
if err != nil {
return nil, fmt.Errorf("decode extra args to get gas limit: %w", err)
}

evmMessages = append(evmMessages, evm_2_evm_multi_offramp.InternalAny2EVMRampMessage{
Header: evm_2_evm_multi_offramp.InternalRampMessageHeader{
MessageId: message.Header.MessageID,
SourceChainSelector: uint64(message.Header.SourceChainSelector),
DestChainSelector: uint64(message.Header.DestChainSelector),
SequenceNumber: uint64(message.Header.SequenceNumber),
Nonce: message.Header.Nonce,
},
Sender: message.Sender,
Data: message.Data,
Receiver: receiver,
GasLimit: gasLimit,
TokenAmounts: tokenAmounts,
})
}

evmChainReport := evm_2_evm_multi_offramp.InternalExecutionReportSingleChain{
SourceChainSelector: uint64(chainReport.SourceChainSelector),
Messages: evmMessages,
OffchainTokenData: chainReport.OffchainTokenData,
Proofs: evmProofs,
ProofFlagBits: chainReport.ProofFlagBits.Int,
}
evmReport = append(evmReport, evmChainReport)
}

return e.executeReportMethodInputs.PackValues([]interface{}{&evmReport})
}

func (e *ExecutePluginCodecV1) Decode(ctx context.Context, encodedReport []byte) (cciptypes.ExecutePluginReport, error) {
unpacked, err := e.executeReportMethodInputs.Unpack(encodedReport)
if err != nil {
return cciptypes.ExecutePluginReport{}, fmt.Errorf("unpack encoded report: %w", err)
}
if len(unpacked) != 1 {
return cciptypes.ExecutePluginReport{}, fmt.Errorf("unpacked report is empty")
}

evmReportRaw := abi.ConvertType(unpacked[0], new([]evm_2_evm_multi_offramp.InternalExecutionReportSingleChain))
evmReportPtr, is := evmReportRaw.(*[]evm_2_evm_multi_offramp.InternalExecutionReportSingleChain)
if !is {
return cciptypes.ExecutePluginReport{}, fmt.Errorf("got an unexpected report type %T", unpacked[0])
}
if evmReportPtr == nil {
return cciptypes.ExecutePluginReport{}, fmt.Errorf("evm report is nil")
}

evmReport := *evmReportPtr
executeReport := cciptypes.ExecutePluginReport{
ChainReports: make([]cciptypes.ExecutePluginReportSingleChain, 0, len(evmReport)),
}

for _, evmChainReport := range evmReport {
proofs := make([]cciptypes.Bytes32, 0, len(evmChainReport.Proofs))
for _, proof := range evmChainReport.Proofs {
proofs = append(proofs, proof)
}

messages := make([]cciptypes.Message, 0, len(evmChainReport.Messages))
for _, evmMessage := range evmChainReport.Messages {
tokenAmounts := make([]cciptypes.RampTokenAmount, 0, len(evmMessage.TokenAmounts))
for _, tokenAmount := range evmMessage.TokenAmounts {
tokenAmounts = append(tokenAmounts, cciptypes.RampTokenAmount{
SourcePoolAddress: tokenAmount.SourcePoolAddress,
DestTokenAddress: tokenAmount.DestTokenAddress,
ExtraData: tokenAmount.ExtraData,
Amount: cciptypes.NewBigInt(tokenAmount.Amount),
})
}

message := cciptypes.Message{
Header: cciptypes.RampMessageHeader{
MessageID: evmMessage.Header.MessageId,
SourceChainSelector: cciptypes.ChainSelector(evmMessage.Header.SourceChainSelector),
DestChainSelector: cciptypes.ChainSelector(evmMessage.Header.DestChainSelector),
SequenceNumber: cciptypes.SeqNum(evmMessage.Header.SequenceNumber),
Nonce: evmMessage.Header.Nonce,
MsgHash: cciptypes.Bytes32{}, // <-- todo: info not available, but not required atm
OnRamp: cciptypes.Bytes{}, // <-- todo: info not available, but not required atm
},
Sender: evmMessage.Sender,
Data: evmMessage.Data,
Receiver: evmMessage.Receiver.Bytes(),
ExtraArgs: cciptypes.Bytes{}, // <-- todo: info not available, but not required atm
FeeToken: cciptypes.Bytes{}, // <-- todo: info not available, but not required atm
FeeTokenAmount: cciptypes.BigInt{}, // <-- todo: info not available, but not required atm
TokenAmounts: tokenAmounts,
}
messages = append(messages, message)
}

chainReport := cciptypes.ExecutePluginReportSingleChain{
SourceChainSelector: cciptypes.ChainSelector(evmChainReport.SourceChainSelector),
Messages: messages,
OffchainTokenData: evmChainReport.OffchainTokenData,
Proofs: proofs,
ProofFlagBits: cciptypes.NewBigInt(evmChainReport.ProofFlagBits),
}

executeReport.ChainReports = append(executeReport.ChainReports, chainReport)
}

return executeReport, nil
}

// Ensure ExecutePluginCodec implements the ExecutePluginCodec interface
var _ cciptypes.ExecutePluginCodec = (*ExecutePluginCodecV1)(nil)
Loading

0 comments on commit 87138ca

Please sign in to comment.