-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add generic codec for CCIP plugin #15454
Draft
huangzhen1997
wants to merge
12
commits into
develop
Choose a base branch
from
NONEVM-935/general-codec-using-contract-reader
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+515
−1
Draft
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
2a4e659
add unit test for using evm codec to encode and decode plugin report,…
huangzhen1997 9804484
add modifier config, unit test passes
huangzhen1997 aef5f26
added exec report, but need to debug on modifier
huangzhen1997 d9f0c07
fixed
huangzhen1997 6a3c1d4
need to fix unit tests
huangzhen1997 3ea3a4f
fix commit test
huangzhen1997 8bae2c9
remove contract decode
huangzhen1997 090d18e
update exec to remove few values
huangzhen1997 7a9173b
changeset
huangzhen1997 a41ccbb
rename file
huangzhen1997 a1fd4dd
add more validation checks
huangzhen1997 e03ec6b
update constructor
huangzhen1997 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"chainlink": minor | ||
--- | ||
|
||
add new commit and exec codec using evm codec #added |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package ccipevm | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math/big" | ||
|
||
"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" | ||
) | ||
|
||
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 { | ||
codec commontypes.RemoteCodec | ||
} | ||
|
||
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 { | ||
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 | ||
} | ||
|
||
return c.codec.Encode(ctx, report, "CommitPluginReport") | ||
} | ||
|
||
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) | ||
} | ||
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{} | ||
|
||
err := c.codec.Decode(ctx, bytes, &report, "CommitPluginReport") | ||
if err != nil { | ||
return report, err | ||
} | ||
|
||
if err = commitPostProcess(&report); err != nil { | ||
return report, err | ||
} | ||
|
||
return report, nil | ||
} | ||
|
||
// Ensure CommitPluginCodec implements the CommitPluginCodec interface | ||
var _ cciptypes.CommitPluginCodec = (*CommitPluginCodecV2)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package ccipevm | ||
|
||
import ( | ||
"math/big" | ||
"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, err := NewCommitPluginCodecV2() | ||
require.NoError(t, err) | ||
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, err := NewCommitPluginCodecV2() | ||
require.NoError(b, err) | ||
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, err := NewCommitPluginCodecV2() | ||
require.NoError(b, err) | ||
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package ccipevm | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"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" | ||
|
||
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 { | ||
codec commontypes.RemoteCodec | ||
} | ||
|
||
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 { | ||
for i, chainReport := range report.ChainReports { | ||
if chainReport.ProofFlagBits.IsEmpty() { | ||
return errors.New("proof flag bits are empty") | ||
} | ||
|
||
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 _, 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 | ||
} | ||
|
||
return e.codec.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{} | ||
err := e.codec.Decode(ctx, encodedReport, &report, "ExecPluginReport") | ||
if err != nil { | ||
return report, err | ||
} | ||
|
||
if err = execPostProcess(&report); err != nil { | ||
return report, err | ||
} | ||
|
||
return report, err | ||
} | ||
|
||
// Ensure ExecutePluginCodec implements the ExecutePluginCodec interface | ||
var _ cciptypes.ExecutePluginCodec = (*ExecutePluginCodecV2)(nil) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be generated from the contracts eventually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would be ideal. The existing generated ABI seems match with the format from generated MerkleRoot, not with the original report format. See discussion from slack thread