Skip to content

Commit

Permalink
[CCIP-5233] CCIP: price-only commit report method override. (#16422)
Browse files Browse the repository at this point in the history
* Option to send price only commit reports to a different method.

* Update test compilation.

* Lint

* PR feedback.

* Add named params.

* Add missing import.
  • Loading branch information
winder authored Feb 27, 2025
1 parent 30bbf70 commit 3f33b49
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 62 deletions.
139 changes: 79 additions & 60 deletions core/capabilities/ccip/ocrimpls/contract_transmitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,59 +18,76 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key"
)

// ToCalldataFunc is a function that takes in the OCR3 report and signature data and processes them.
// It returns the contract name, method name, and arguments for the on-chain contract call.
// The ReportWithInfo bytes field is also decoded according to the implementation of this function,
// the commit and execute plugins have different representations for this data.
type ToCalldataFunc func(
rawReportCtx [2][32]byte,
report ocr3types.ReportWithInfo[[]byte],
rs, ss [][32]byte,
vs [32]byte,
) (any, error)
) (contract string, method string, args any, err error)

// NewToCommitCalldataFunc returns a ToCalldataFunc that is used to generate the calldata for the commit method.
// Multiple methods are accepted in order to allow for different methods to be called based on the report data.
// The Solana on-chain contract has two methods, one for the default commit and one for the price-only commit.
func NewToCommitCalldataFunc(defaultMethod, priceOnlyMethod string) ToCalldataFunc {
return func(
rawReportCtx [2][32]byte,
report ocr3types.ReportWithInfo[[]byte],
rs, ss [][32]byte,
vs [32]byte,
) (contract string, method string, args any, err error) {
// Note that the name of the struct field is very important, since the encoder used
// by the chainwriter uses mapstructure, which will use the struct field name to map
// to the argument name in the function call.
// If, for whatever reason, we want to change the field name, make sure to add a `mapstructure:"<arg_name>"` tag
// for that field.
var info ccipocr3.CommitReportInfo
if len(report.Info) != 0 {
var err error
info, err = ccipocr3.DecodeCommitReportInfo(report.Info)
if err != nil {
return "", "", nil, err
}
}

func ToCommitCalldata(
rawReportCtx [2][32]byte,
report ocr3types.ReportWithInfo[[]byte],
rs, ss [][32]byte,
vs [32]byte,
) (any, error) {
// Note that the name of the struct field is very important, since the encoder used
// by the chainwriter uses mapstructure, which will use the struct field name to map
// to the argument name in the function call.
// If, for whatever reason, we want to change the field name, make sure to add a `mapstructure:"<arg_name>"` tag
// for that field.
var info ccipocr3.CommitReportInfo
if len(report.Info) != 0 {
var err error
info, err = ccipocr3.DecodeCommitReportInfo(report.Info)
if err != nil {
return nil, err
method = defaultMethod
if priceOnlyMethod != "" && len(info.MerkleRoots) == 0 && len(info.TokenPrices) > 0 {
method = priceOnlyMethod
}
}

// WARNING: Be careful if you change the data types.
// Using a different type e.g. `type Foo [32]byte` instead of `[32]byte`
// will trigger undefined chainWriter behavior, e.g. transactions submitted with wrong arguments.
return struct {
ReportContext [2][32]byte
Report []byte
Rs [][32]byte
Ss [][32]byte
RawVs [32]byte
Info ccipocr3.CommitReportInfo
}{
ReportContext: rawReportCtx,
Report: report.Report,
Rs: rs,
Ss: ss,
RawVs: vs,
Info: info,
}, nil
// WARNING: Be careful if you change the data types.
// Using a different type e.g. `type Foo [32]byte` instead of `[32]byte`
// will trigger undefined chainWriter behavior, e.g. transactions submitted with wrong arguments.
return consts.ContractNameOffRamp,
method,
struct {
ReportContext [2][32]byte
Report []byte
Rs [][32]byte
Ss [][32]byte
RawVs [32]byte
Info ccipocr3.CommitReportInfo
}{
ReportContext: rawReportCtx,
Report: report.Report,
Rs: rs,
Ss: ss,
RawVs: vs,
Info: info,
}, nil
}
}

// ToExecCalldata is a ToCalldataFunc that is used to generate the calldata for the execute method.
func ToExecCalldata(
rawReportCtx [2][32]byte,
report ocr3types.ReportWithInfo[[]byte],
_, _ [][32]byte,
_ [32]byte,
) (any, error) {
) (contract string, method string, args any, err error) {
// Note that the name of the struct field is very important, since the encoder used
// by the chainwriter uses mapstructure, which will use the struct field name to map
// to the argument name in the function call.
Expand All @@ -85,28 +102,28 @@ func ToExecCalldata(
var err error
info, err = ccipocr3.DecodeExecuteReportInfo(report.Info)
if err != nil {
return nil, err
return "", "", nil, err
}
}

return struct {
ReportContext [2][32]byte
Report []byte
Info ccipocr3.ExecuteReportInfo
}{
ReportContext: rawReportCtx,
Report: report.Report,
Info: info,
}, nil
return consts.ContractNameOffRamp,
consts.MethodExecute,
struct {
ReportContext [2][32]byte
Report []byte
Info ccipocr3.ExecuteReportInfo
}{
ReportContext: rawReportCtx,
Report: report.Report,
Info: info,
}, nil
}

var _ ocr3types.ContractTransmitter[[]byte] = &ccipTransmitter{}

type ccipTransmitter struct {
cw commontypes.ContractWriter
fromAccount ocrtypes.Account
contractName string
method string
offrampAddress string
toCalldataFn ToCalldataFunc
}
Expand All @@ -119,28 +136,32 @@ func XXXNewContractTransmitterTestsOnly(
offrampAddress string,
toCalldataFn ToCalldataFunc,
) ocr3types.ContractTransmitter[[]byte] {
wrappedToCalldataFunc := func(rawReportCtx [2][32]byte,
report ocr3types.ReportWithInfo[[]byte],
rs, ss [][32]byte,
vs [32]byte) (string, string, any, error) {
_, _, args, err := toCalldataFn(rawReportCtx, report, rs, ss, vs)
return contractName, method, args, err
}
return &ccipTransmitter{
cw: cw,
fromAccount: fromAccount,
contractName: contractName,
method: method,
offrampAddress: offrampAddress,
toCalldataFn: toCalldataFn,
toCalldataFn: wrappedToCalldataFunc,
}
}

func NewCommitContractTransmitter(
cw commontypes.ContractWriter,
fromAccount ocrtypes.Account,
offrampAddress string,
defaultMethod, priceOnlyMethod string,
) ocr3types.ContractTransmitter[[]byte] {
return &ccipTransmitter{
cw: cw,
fromAccount: fromAccount,
contractName: consts.ContractNameOffRamp,
method: consts.MethodCommit,
offrampAddress: offrampAddress,
toCalldataFn: ToCommitCalldata,
toCalldataFn: NewToCommitCalldataFunc(defaultMethod, priceOnlyMethod),
}
}

Expand All @@ -152,8 +173,6 @@ func NewExecContractTransmitter(
return &ccipTransmitter{
cw: cw,
fromAccount: fromAccount,
contractName: consts.ContractNameOffRamp,
method: consts.MethodExecute,
offrampAddress: offrampAddress,
toCalldataFn: ToExecCalldata,
}
Expand Down Expand Up @@ -198,7 +217,7 @@ func (c *ccipTransmitter) Transmit(
}

// chain writer takes in the raw calldata and packs it on its own.
args, err := c.toCalldataFn(rawReportCtx, reportWithInfo, rs, ss, vs)
contract, method, args, err := c.toCalldataFn(rawReportCtx, reportWithInfo, rs, ss, vs)
if err != nil {
return fmt.Errorf("failed to generate call data: %w", err)
}
Expand All @@ -211,7 +230,7 @@ func (c *ccipTransmitter) Transmit(
return fmt.Errorf("failed to generate UUID: %w", err)
}
zero := big.NewInt(0)
if err := c.cw.SubmitTransaction(ctx, c.contractName, c.method, args, fmt.Sprintf("%s-%s-%s", c.contractName, c.offrampAddress, txID.String()), c.offrampAddress, &meta, zero); err != nil {
if err := c.cw.SubmitTransaction(ctx, contract, method, args, fmt.Sprintf("%s-%s-%s", contract, c.offrampAddress, txID.String()), c.offrampAddress, &meta, zero); err != nil {
return fmt.Errorf("failed to submit transaction thru chainwriter: %w", err)
}

Expand Down
5 changes: 3 additions & 2 deletions core/capabilities/ccip/ocrimpls/contract_transmitter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import (

"github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox"
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
"github.com/smartcontractkit/chainlink-integrations/evm/heads"

"github.com/smartcontractkit/chainlink-ccip/pkg/consts"
"github.com/smartcontractkit/chainlink-integrations/evm/assets"
"github.com/smartcontractkit/chainlink-integrations/evm/client"
evmconfig "github.com/smartcontractkit/chainlink-integrations/evm/config"
"github.com/smartcontractkit/chainlink-integrations/evm/config/chaintype"
"github.com/smartcontractkit/chainlink-integrations/evm/config/toml"
"github.com/smartcontractkit/chainlink-integrations/evm/gas"
"github.com/smartcontractkit/chainlink-integrations/evm/heads"
"github.com/smartcontractkit/chainlink-integrations/evm/keystore"
"github.com/smartcontractkit/chainlink-integrations/evm/logpoller"
evmtestutils "github.com/smartcontractkit/chainlink-integrations/evm/testutils"
Expand Down Expand Up @@ -316,7 +317,7 @@ func newTestUniverse(t *testing.T, ks *keyringsAndSigners[[]byte]) *testUniverse
contractName,
methodTransmitWithSignatures,
ocr3HelperAddr.Hex(),
ocrimpls.ToCommitCalldata,
ocrimpls.NewToCommitCalldataFunc(consts.MethodCommit, ""),
)
transmitterWithoutSigs := ocrimpls.XXXNewContractTransmitterTestsOnly(
chainWriter,
Expand Down
5 changes: 5 additions & 0 deletions core/capabilities/ccip/oraclecreator/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ var plugins = map[string]plugin{
TokenDataEncoder: ccipsolana.NewSolanaTokenDataEncoder(),
GasEstimateProvider: ccipsolana.NewGasEstimateProvider(),
RMNCrypto: func(lggr logger.Logger) cciptypes.RMNCrypto { return nil },
PriceOnlyCommitFn: consts.MethodCommitPriceOnly,
},
}

Expand All @@ -94,6 +95,8 @@ type plugin struct {
TokenDataEncoder cciptypes.TokenDataEncoder
GasEstimateProvider cciptypes.EstimateProvider
RMNCrypto func(lggr logger.Logger) cciptypes.RMNCrypto
// PriceOnlyCommitFn optional method override for price only commit reports.
PriceOnlyCommitFn string
}

// pluginOracleCreator creates oracles that reference plugins running
Expand Down Expand Up @@ -354,6 +357,8 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter(
transmitter = ocrimpls.NewCommitContractTransmitter(destChainWriter,
ocrtypes.Account(destFromAccounts[0]),
offrampAddrStr,
consts.MethodCommit,
plugins[chainFamily].PriceOnlyCommitFn,
)
} else if config.Config.PluginType == uint8(cctypes.PluginTypeCCIPExec) {
factory = execocr3.NewExecutePluginFactory(
Expand Down

0 comments on commit 3f33b49

Please sign in to comment.