diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/hasher.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/hasher.go new file mode 100644 index 0000000000..62a16b5983 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/hasher.go @@ -0,0 +1,84 @@ +package v1_0_0 + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp_1_0_0" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" +) + +const ( + MetaDataHashPrefix = "EVM2EVMMessageEvent" +) + +var LeafDomainSeparator = [1]byte{0x00} + +type LeafHasher struct { + metaDataHash [32]byte + ctx hashlib.Ctx[[32]byte] + onRamp *evm_2_evm_onramp_1_0_0.EVM2EVMOnRamp +} + +func GetMetaDataHash[H hashlib.Hash](ctx hashlib.Ctx[H], prefix [32]byte, sourceChainSelector uint64, onRampId common.Address, destChainSelector uint64) H { + paddedOnRamp := common.BytesToHash(onRampId[:]) + return ctx.Hash(utils.ConcatBytes(prefix[:], math.U256Bytes(big.NewInt(0).SetUint64(sourceChainSelector)), math.U256Bytes(big.NewInt(0).SetUint64(destChainSelector)), paddedOnRamp[:])) +} + +func NewLeafHasher(sourceChainSelector uint64, destChainSelector uint64, onRampId common.Address, ctx hashlib.Ctx[[32]byte], onRamp *evm_2_evm_onramp_1_0_0.EVM2EVMOnRamp) *LeafHasher { + return &LeafHasher{ + metaDataHash: GetMetaDataHash(ctx, ctx.Hash([]byte(MetaDataHashPrefix)), sourceChainSelector, onRampId, destChainSelector), + ctx: ctx, + onRamp: onRamp, + } +} + +func (t *LeafHasher) HashLeaf(log types.Log) ([32]byte, error) { + message, err := t.onRamp.ParseCCIPSendRequested(log) + if err != nil { + return [32]byte{}, err + } + encodedTokens, err := utils.ABIEncode( + `[ +{"components": [{"name":"token","type":"address"},{"name":"amount","type":"uint256"}], "type":"tuple[]"}]`, message.Message.TokenAmounts) + if err != nil { + return [32]byte{}, err + } + + packedValues, err := utils.ABIEncode( + `[ +{"name": "leafDomainSeparator","type":"bytes1"}, +{"name": "metadataHash", "type":"bytes32"}, +{"name": "sequenceNumber", "type":"uint64"}, +{"name": "nonce", "type":"uint64"}, +{"name": "sender", "type":"address"}, +{"name": "receiver", "type":"address"}, +{"name": "dataHash", "type":"bytes32"}, +{"name": "tokenAmountsHash", "type":"bytes32"}, +{"name": "gasLimit", "type":"uint256"}, +{"name": "strict", "type":"bool"}, +{"name": "feeToken","type": "address"}, +{"name": "feeTokenAmount","type": "uint256"} +]`, + LeafDomainSeparator, + t.metaDataHash, + message.Message.SequenceNumber, + message.Message.Nonce, + message.Message.Sender, + message.Message.Receiver, + t.ctx.Hash(message.Message.Data), + t.ctx.Hash(encodedTokens), + message.Message.GasLimit, + message.Message.Strict, + message.Message.FeeToken, + message.Message.FeeTokenAmount, + ) + if err != nil { + return [32]byte{}, err + } + return t.ctx.Hash(packedValues), nil +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/hasher_test.go similarity index 100% rename from core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp_test.go rename to core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/hasher_test.go diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go index bc59b50e9c..cad984a526 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go @@ -2,17 +2,14 @@ package v1_0_0 import ( "context" + "errors" "fmt" - "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" - "github.com/pkg/errors" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp_1_0_0" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" @@ -25,76 +22,8 @@ import ( const ( CCIPSendRequestedEventName = "CCIPSendRequested" - MetaDataHashPrefix = "EVM2EVMMessageEvent" ) -var LeafDomainSeparator = [1]byte{0x00} - -type LeafHasher struct { - metaDataHash [32]byte - ctx hashlib.Ctx[[32]byte] - onRamp *evm_2_evm_onramp_1_0_0.EVM2EVMOnRamp -} - -func GetMetaDataHash[H hashlib.Hash](ctx hashlib.Ctx[H], prefix [32]byte, sourceChainSelector uint64, onRampId common.Address, destChainSelector uint64) H { - paddedOnRamp := common.BytesToHash(onRampId[:]) - return ctx.Hash(utils.ConcatBytes(prefix[:], math.U256Bytes(big.NewInt(0).SetUint64(sourceChainSelector)), math.U256Bytes(big.NewInt(0).SetUint64(destChainSelector)), paddedOnRamp[:])) -} - -func NewLeafHasher(sourceChainSelector uint64, destChainSelector uint64, onRampId common.Address, ctx hashlib.Ctx[[32]byte], onRamp *evm_2_evm_onramp_1_0_0.EVM2EVMOnRamp) *LeafHasher { - return &LeafHasher{ - metaDataHash: GetMetaDataHash(ctx, ctx.Hash([]byte(MetaDataHashPrefix)), sourceChainSelector, onRampId, destChainSelector), - ctx: ctx, - onRamp: onRamp, - } -} - -func (t *LeafHasher) HashLeaf(log types.Log) ([32]byte, error) { - message, err := t.onRamp.ParseCCIPSendRequested(log) - if err != nil { - return [32]byte{}, err - } - encodedTokens, err := utils.ABIEncode( - `[ -{"components": [{"name":"token","type":"address"},{"name":"amount","type":"uint256"}], "type":"tuple[]"}]`, message.Message.TokenAmounts) - if err != nil { - return [32]byte{}, err - } - - packedValues, err := utils.ABIEncode( - `[ -{"name": "leafDomainSeparator","type":"bytes1"}, -{"name": "metadataHash", "type":"bytes32"}, -{"name": "sequenceNumber", "type":"uint64"}, -{"name": "nonce", "type":"uint64"}, -{"name": "sender", "type":"address"}, -{"name": "receiver", "type":"address"}, -{"name": "dataHash", "type":"bytes32"}, -{"name": "tokenAmountsHash", "type":"bytes32"}, -{"name": "gasLimit", "type":"uint256"}, -{"name": "strict", "type":"bool"}, -{"name": "feeToken","type": "address"}, -{"name": "feeTokenAmount","type": "uint256"} -]`, - LeafDomainSeparator, - t.metaDataHash, - message.Message.SequenceNumber, - message.Message.Nonce, - message.Message.Sender, - message.Message.Receiver, - t.ctx.Hash(message.Message.Data), - t.ctx.Hash(encodedTokens), - message.Message.GasLimit, - message.Message.Strict, - message.Message.FeeToken, - message.Message.FeeTokenAmount, - ) - if err != nil { - return [32]byte{}, err - } - return t.ctx.Hash(packedValues), nil -} - var _ ccipdata.OnRampReader = &OnRamp{} type OnRamp struct { @@ -109,6 +38,34 @@ type OnRamp struct { filters []logpoller.Filter } +func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAddress common.Address, sourceLP logpoller.LogPoller, source client.Client) (*OnRamp, error) { + onRamp, err := evm_2_evm_onramp_1_0_0.NewEVM2EVMOnRamp(onRampAddress, source) + if err != nil { + return nil, err + } + onRampABI := abihelpers.MustParseABI(evm_2_evm_onramp_1_0_0.EVM2EVMOnRampABI) + eventSig := abihelpers.MustGetEventID(CCIPSendRequestedEventName, onRampABI) + filters := []logpoller.Filter{ + { + Name: logpoller.FilterName(ccipdata.COMMIT_CCIP_SENDS, onRampAddress), + EventSigs: []common.Hash{eventSig}, + Addresses: []common.Address{onRampAddress}, + }, + } + return &OnRamp{ + lggr: lggr, + address: onRampAddress, + onRamp: onRamp, + client: source, + filters: filters, + lp: sourceLP, + leafHasher: NewLeafHasher(sourceSelector, destSelector, onRampAddress, hashlib.NewKeccakCtx(), onRamp), + // offset || sourceChainID || seqNum || ... + sendRequestedSeqNumberWord: 2, + sendRequestedEventSig: eventSig, + }, nil +} + func (o *OnRamp) Address() (cciptypes.Address, error) { return cciptypes.Address(o.onRamp.Address().String()), nil } @@ -135,6 +92,42 @@ func (o *OnRamp) GetDynamicConfig() (cciptypes.OnRampDynamicConfig, error) { }, nil } +func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, seqNumMax uint64, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { + logs, err := o.lp.LogsDataWordRange( + o.sendRequestedEventSig, + o.address, + o.sendRequestedSeqNumberWord, + logpoller.EvmWord(seqNumMin), + logpoller.EvmWord(seqNumMax), + ccipdata.LogsConfirmations(finalized), + pg.WithParentCtx(ctx)) + if err != nil { + return nil, err + } + + parsedLogs, err := ccipdata.ParseLogs[cciptypes.EVM2EVMMessage](logs, o.lggr, o.logToMessage) + if err != nil { + return nil, err + } + + res := make([]cciptypes.EVM2EVMMessageWithTxMeta, 0, len(parsedLogs)) + for _, log := range parsedLogs { + res = append(res, cciptypes.EVM2EVMMessageWithTxMeta{ + TxMeta: log.TxMeta, + EVM2EVMMessage: log.Data, + }) + } + return res, nil +} + +func (o *OnRamp) RouterAddress() (cciptypes.Address, error) { + config, err := o.onRamp.GetDynamicConfig(nil) + if err != nil { + return "", err + } + return cciptypes.Address(config.Router.String()), nil +} + func (o *OnRamp) GetLastUSDCMessagePriorToLogIndexInTx(ctx context.Context, logIndex int64, txHash common.Hash) ([]byte, error) { return nil, errors.New("USDC not supported in < 1.2.0") } @@ -147,34 +140,6 @@ func (o *OnRamp) RegisterFilters(qopts ...pg.QOpt) error { return logpollerutil.RegisterLpFilters(o.lp, o.filters, qopts...) } -func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAddress common.Address, sourceLP logpoller.LogPoller, source client.Client) (*OnRamp, error) { - onRamp, err := evm_2_evm_onramp_1_0_0.NewEVM2EVMOnRamp(onRampAddress, source) - if err != nil { - return nil, err - } - onRampABI := abihelpers.MustParseABI(evm_2_evm_onramp_1_0_0.EVM2EVMOnRampABI) - eventSig := abihelpers.MustGetEventID(CCIPSendRequestedEventName, onRampABI) - filters := []logpoller.Filter{ - { - Name: logpoller.FilterName(ccipdata.COMMIT_CCIP_SENDS, onRampAddress), - EventSigs: []common.Hash{eventSig}, - Addresses: []common.Address{onRampAddress}, - }, - } - return &OnRamp{ - lggr: lggr, - address: onRampAddress, - onRamp: onRamp, - client: source, - filters: filters, - lp: sourceLP, - leafHasher: NewLeafHasher(sourceSelector, destSelector, onRampAddress, hashlib.NewKeccakCtx(), onRamp), - // offset || sourceChainID || seqNum || ... - sendRequestedSeqNumberWord: 2, - sendRequestedEventSig: eventSig, - }, nil -} - func (o *OnRamp) logToMessage(log types.Log) (*cciptypes.EVM2EVMMessage, error) { msg, err := o.onRamp.ParseCCIPSendRequested(log) if err != nil { @@ -208,39 +173,3 @@ func (o *OnRamp) logToMessage(log types.Log) (*cciptypes.EVM2EVMMessage, error) Hash: h, }, nil } - -func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, seqNumMax uint64, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { - logs, err := o.lp.LogsDataWordRange( - o.sendRequestedEventSig, - o.address, - o.sendRequestedSeqNumberWord, - logpoller.EvmWord(seqNumMin), - logpoller.EvmWord(seqNumMax), - ccipdata.LogsConfirmations(finalized), - pg.WithParentCtx(ctx)) - if err != nil { - return nil, err - } - - parsedLogs, err := ccipdata.ParseLogs[cciptypes.EVM2EVMMessage](logs, o.lggr, o.logToMessage) - if err != nil { - return nil, err - } - - res := make([]cciptypes.EVM2EVMMessageWithTxMeta, 0, len(parsedLogs)) - for _, log := range parsedLogs { - res = append(res, cciptypes.EVM2EVMMessageWithTxMeta{ - TxMeta: log.TxMeta, - EVM2EVMMessage: log.Data, - }) - } - return res, nil -} - -func (o *OnRamp) RouterAddress() (cciptypes.Address, error) { - config, err := o.onRamp.GetDynamicConfig(nil) - if err != nil { - return "", err - } - return cciptypes.Address(config.Router.String()), nil -} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/hasher.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/hasher.go new file mode 100644 index 0000000000..50f26ed815 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/hasher.go @@ -0,0 +1,100 @@ +package v1_2_0 + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp_1_2_0" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" +) + +const ( + MetaDataHashPrefix = "EVM2EVMMessageHashV2" +) + +type LeafHasher struct { + metaDataHash [32]byte + ctx hashlib.Ctx[[32]byte] + onRamp *evm_2_evm_onramp_1_2_0.EVM2EVMOnRamp +} + +func NewLeafHasher(sourceChainSelector uint64, destChainSelector uint64, onRampId common.Address, ctx hashlib.Ctx[[32]byte], onRamp *evm_2_evm_onramp_1_2_0.EVM2EVMOnRamp) *LeafHasher { + return &LeafHasher{ + metaDataHash: v1_0_0.GetMetaDataHash(ctx, ctx.Hash([]byte(MetaDataHashPrefix)), sourceChainSelector, onRampId, destChainSelector), + ctx: ctx, + onRamp: onRamp, + } +} + +func (t *LeafHasher) HashLeaf(log types.Log) ([32]byte, error) { + msg, err := t.onRamp.ParseCCIPSendRequested(log) + if err != nil { + return [32]byte{}, err + } + message := msg.Message + encodedTokens, err := utils.ABIEncode( + `[ +{"components": [{"name":"token","type":"address"},{"name":"amount","type":"uint256"}], "type":"tuple[]"}]`, message.TokenAmounts) + if err != nil { + return [32]byte{}, err + } + + bytesArray, err := abi.NewType("bytes[]", "bytes[]", nil) + if err != nil { + return [32]byte{}, err + } + + encodedSourceTokenData, err := abi.Arguments{abi.Argument{Type: bytesArray}}.PackValues([]interface{}{message.SourceTokenData}) + if err != nil { + return [32]byte{}, err + } + + packedFixedSizeValues, err := utils.ABIEncode( + `[ +{"name": "sender", "type":"address"}, +{"name": "receiver", "type":"address"}, +{"name": "sequenceNumber", "type":"uint64"}, +{"name": "gasLimit", "type":"uint256"}, +{"name": "strict", "type":"bool"}, +{"name": "nonce", "type":"uint64"}, +{"name": "feeToken","type": "address"}, +{"name": "feeTokenAmount","type": "uint256"} +]`, + message.Sender, + message.Receiver, + message.SequenceNumber, + message.GasLimit, + message.Strict, + message.Nonce, + message.FeeToken, + message.FeeTokenAmount, + ) + if err != nil { + return [32]byte{}, err + } + fixedSizeValuesHash := t.ctx.Hash(packedFixedSizeValues) + + packedValues, err := utils.ABIEncode( + `[ +{"name": "leafDomainSeparator","type":"bytes1"}, +{"name": "metadataHash", "type":"bytes32"}, +{"name": "fixedSizeValuesHash", "type":"bytes32"}, +{"name": "dataHash", "type":"bytes32"}, +{"name": "tokenAmountsHash", "type":"bytes32"}, +{"name": "sourceTokenDataHash", "type":"bytes32"} +]`, + v1_0_0.LeafDomainSeparator, + t.metaDataHash, + fixedSizeValuesHash, + t.ctx.Hash(message.Data), + t.ctx.Hash(encodedTokens), + t.ctx.Hash(encodedSourceTokenData), + ) + if err != nil { + return [32]byte{}, err + } + return t.ctx.Hash(packedValues), nil +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/hasher_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/hasher_test.go new file mode 100644 index 0000000000..cde0a51a01 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/hasher_test.go @@ -0,0 +1,77 @@ +package v1_2_0 + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/test-go/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp_1_2_0" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" +) + +func TestHasherV1_2_0(t *testing.T) { + sourceChainSelector, destChainSelector := uint64(1), uint64(4) + onRampAddress := common.HexToAddress("0x5550000000000000000000000000000000000001") + onRampABI := abihelpers.MustParseABI(evm_2_evm_onramp_1_2_0.EVM2EVMOnRampABI) + + hashingCtx := hashlib.NewKeccakCtx() + ramp, err := evm_2_evm_onramp_1_2_0.NewEVM2EVMOnRamp(onRampAddress, nil) + require.NoError(t, err) + hasher := NewLeafHasher(sourceChainSelector, destChainSelector, onRampAddress, hashingCtx, ramp) + + message := evm_2_evm_onramp_1_2_0.InternalEVM2EVMMessage{ + SourceChainSelector: sourceChainSelector, + Sender: common.HexToAddress("0x1110000000000000000000000000000000000001"), + Receiver: common.HexToAddress("0x2220000000000000000000000000000000000001"), + SequenceNumber: 1337, + GasLimit: big.NewInt(100), + Strict: false, + Nonce: 1337, + FeeToken: common.Address{}, + FeeTokenAmount: big.NewInt(1), + Data: []byte{}, + TokenAmounts: []evm_2_evm_onramp_1_2_0.ClientEVMTokenAmount{{Token: common.HexToAddress("0x4440000000000000000000000000000000000001"), Amount: big.NewInt(12345678900)}}, + SourceTokenData: [][]byte{}, + MessageId: [32]byte{}, + } + + data, err := onRampABI.Events[CCIPSendRequestedEventName].Inputs.Pack(message) + require.NoError(t, err) + hash, err := hasher.HashLeaf(types.Log{Topics: []common.Hash{CCIPSendRequestEventSig}, Data: data}) + require.NoError(t, err) + + // NOTE: Must match spec + require.Equal(t, "46ad031bfb052db2e4a2514fed8dc480b98e5ce4acb55d5640d91407e0d8a3e9", hex.EncodeToString(hash[:])) + + message = evm_2_evm_onramp_1_2_0.InternalEVM2EVMMessage{ + SourceChainSelector: sourceChainSelector, + Sender: common.HexToAddress("0x1110000000000000000000000000000000000001"), + Receiver: common.HexToAddress("0x2220000000000000000000000000000000000001"), + SequenceNumber: 1337, + GasLimit: big.NewInt(100), + Strict: false, + Nonce: 1337, + FeeToken: common.Address{}, + FeeTokenAmount: big.NewInt(1e12), + Data: []byte("foo bar baz"), + TokenAmounts: []evm_2_evm_onramp_1_2_0.ClientEVMTokenAmount{ + {Token: common.HexToAddress("0x4440000000000000000000000000000000000001"), Amount: big.NewInt(12345678900)}, + {Token: common.HexToAddress("0x6660000000000000000000000000000000000001"), Amount: big.NewInt(4204242)}, + }, + SourceTokenData: [][]byte{{0x2, 0x1}}, + MessageId: [32]byte{}, + } + + data, err = onRampABI.Events[CCIPSendRequestedEventName].Inputs.Pack(message) + require.NoError(t, err) + hash, err = hasher.HashLeaf(types.Log{Topics: []common.Hash{CCIPSendRequestEventSig}, Data: data}) + require.NoError(t, err) + + // NOTE: Must match spec + require.Equal(t, "4362a13a42e52ff5ce4324e7184dc7aa41704c3146bc842d35d95b94b32a78b6", hex.EncodeToString(hash[:])) +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp.go index 94fcc2d4c4..854ca6ee6a 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp.go @@ -12,13 +12,11 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp_1_2_0" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/cciptypes" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/logpollerutil" "github.com/smartcontractkit/chainlink/v2/core/services/pg" @@ -32,8 +30,6 @@ var ( const ( CCIPSendRequestSeqNumIndex = 4 CCIPSendRequestedEventName = "CCIPSendRequested" - EVM2EVMOffRampEventName = "EVM2EVMMessage" - MetaDataHashPrefix = "EVM2EVMMessageHashV2" ) func init() { @@ -44,90 +40,6 @@ func init() { CCIPSendRequestEventSig = abihelpers.MustGetEventID(CCIPSendRequestedEventName, onRampABI) } -type LeafHasher struct { - metaDataHash [32]byte - ctx hashlib.Ctx[[32]byte] - onRamp *evm_2_evm_onramp_1_2_0.EVM2EVMOnRamp -} - -func NewLeafHasher(sourceChainSelector uint64, destChainSelector uint64, onRampId common.Address, ctx hashlib.Ctx[[32]byte], onRamp *evm_2_evm_onramp_1_2_0.EVM2EVMOnRamp) *LeafHasher { - return &LeafHasher{ - metaDataHash: v1_0_0.GetMetaDataHash(ctx, ctx.Hash([]byte(MetaDataHashPrefix)), sourceChainSelector, onRampId, destChainSelector), - ctx: ctx, - onRamp: onRamp, - } -} - -func (t *LeafHasher) HashLeaf(log types.Log) ([32]byte, error) { - msg, err := t.onRamp.ParseCCIPSendRequested(log) - if err != nil { - return [32]byte{}, err - } - message := msg.Message - encodedTokens, err := utils.ABIEncode( - `[ -{"components": [{"name":"token","type":"address"},{"name":"amount","type":"uint256"}], "type":"tuple[]"}]`, message.TokenAmounts) - if err != nil { - return [32]byte{}, err - } - - bytesArray, err := abi.NewType("bytes[]", "bytes[]", nil) - if err != nil { - return [32]byte{}, err - } - - encodedSourceTokenData, err := abi.Arguments{abi.Argument{Type: bytesArray}}.PackValues([]interface{}{message.SourceTokenData}) - if err != nil { - return [32]byte{}, err - } - - packedFixedSizeValues, err := utils.ABIEncode( - `[ -{"name": "sender", "type":"address"}, -{"name": "receiver", "type":"address"}, -{"name": "sequenceNumber", "type":"uint64"}, -{"name": "gasLimit", "type":"uint256"}, -{"name": "strict", "type":"bool"}, -{"name": "nonce", "type":"uint64"}, -{"name": "feeToken","type": "address"}, -{"name": "feeTokenAmount","type": "uint256"} -]`, - message.Sender, - message.Receiver, - message.SequenceNumber, - message.GasLimit, - message.Strict, - message.Nonce, - message.FeeToken, - message.FeeTokenAmount, - ) - if err != nil { - return [32]byte{}, err - } - fixedSizeValuesHash := t.ctx.Hash(packedFixedSizeValues) - - packedValues, err := utils.ABIEncode( - `[ -{"name": "leafDomainSeparator","type":"bytes1"}, -{"name": "metadataHash", "type":"bytes32"}, -{"name": "fixedSizeValuesHash", "type":"bytes32"}, -{"name": "dataHash", "type":"bytes32"}, -{"name": "tokenAmountsHash", "type":"bytes32"}, -{"name": "sourceTokenDataHash", "type":"bytes32"} -]`, - v1_0_0.LeafDomainSeparator, - t.metaDataHash, - fixedSizeValuesHash, - t.ctx.Hash(message.Data), - t.ctx.Hash(encodedTokens), - t.ctx.Hash(encodedSourceTokenData), - ) - if err != nil { - return [32]byte{}, err - } - return t.ctx.Hash(packedValues), nil -} - var _ ccipdata.OnRampReader = &OnRamp{} // Significant change in 1.2: @@ -144,6 +56,33 @@ type OnRamp struct { filters []logpoller.Filter } +func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAddress common.Address, sourceLP logpoller.LogPoller, source client.Client) (*OnRamp, error) { + onRamp, err := evm_2_evm_onramp_1_2_0.NewEVM2EVMOnRamp(onRampAddress, source) + if err != nil { + return nil, err + } + // Subscribe to the relevant logs + // Note we can keep the same prefix across 1.0/1.1 and 1.2 because the onramp addresses will be different + filters := []logpoller.Filter{ + { + Name: logpoller.FilterName(ccipdata.COMMIT_CCIP_SENDS, onRampAddress), + EventSigs: []common.Hash{CCIPSendRequestEventSig}, + Addresses: []common.Address{onRampAddress}, + }, + } + return &OnRamp{ + lggr: lggr, + client: source, + lp: sourceLP, + leafHasher: NewLeafHasher(sourceSelector, destSelector, onRampAddress, hashlib.NewKeccakCtx(), onRamp), + onRamp: onRamp, + filters: filters, + address: onRampAddress, + sendRequestedSeqNumberWord: CCIPSendRequestSeqNumIndex, + sendRequestedEventSig: CCIPSendRequestEventSig, + }, nil +} + func (o *OnRamp) Address() (cciptypes.Address, error) { return cciptypes.Address(o.onRamp.Address().String()), nil } @@ -170,41 +109,6 @@ func (o *OnRamp) GetDynamicConfig() (cciptypes.OnRampDynamicConfig, error) { }, nil } -func (o *OnRamp) logToMessage(log types.Log) (*cciptypes.EVM2EVMMessage, error) { - msg, err := o.onRamp.ParseCCIPSendRequested(log) - if err != nil { - return nil, err - } - h, err := o.leafHasher.HashLeaf(log) - if err != nil { - return nil, err - } - tokensAndAmounts := make([]cciptypes.TokenAmount, len(msg.Message.TokenAmounts)) - for i, tokenAndAmount := range msg.Message.TokenAmounts { - tokensAndAmounts[i] = cciptypes.TokenAmount{ - Token: cciptypes.Address(tokenAndAmount.Token.String()), - Amount: tokenAndAmount.Amount, - } - } - - return &cciptypes.EVM2EVMMessage{ - SequenceNumber: msg.Message.SequenceNumber, - GasLimit: msg.Message.GasLimit, - Nonce: msg.Message.Nonce, - MessageID: msg.Message.MessageId, - SourceChainSelector: msg.Message.SourceChainSelector, - Sender: cciptypes.Address(msg.Message.Sender.String()), - Receiver: cciptypes.Address(msg.Message.Receiver.String()), - Strict: msg.Message.Strict, - FeeToken: cciptypes.Address(msg.Message.FeeToken.String()), - FeeTokenAmount: msg.Message.FeeTokenAmount, - Data: msg.Message.Data, - TokenAmounts: tokensAndAmounts, - SourceTokenData: msg.Message.SourceTokenData, // Breaking change 1.2 - Hash: h, - }, nil -} - func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, seqNumMax uint64, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { logs, err := o.lp.LogsDataWordRange( o.sendRequestedEventSig, @@ -250,29 +154,37 @@ func (o *OnRamp) RegisterFilters(qopts ...pg.QOpt) error { return logpollerutil.RegisterLpFilters(o.lp, o.filters, qopts...) } -func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAddress common.Address, sourceLP logpoller.LogPoller, source client.Client) (*OnRamp, error) { - onRamp, err := evm_2_evm_onramp_1_2_0.NewEVM2EVMOnRamp(onRampAddress, source) +func (o *OnRamp) logToMessage(log types.Log) (*cciptypes.EVM2EVMMessage, error) { + msg, err := o.onRamp.ParseCCIPSendRequested(log) if err != nil { return nil, err } - // Subscribe to the relevant logs - // Note we can keep the same prefix across 1.0/1.1 and 1.2 because the onramp addresses will be different - filters := []logpoller.Filter{ - { - Name: logpoller.FilterName(ccipdata.COMMIT_CCIP_SENDS, onRampAddress), - EventSigs: []common.Hash{CCIPSendRequestEventSig}, - Addresses: []common.Address{onRampAddress}, - }, + h, err := o.leafHasher.HashLeaf(log) + if err != nil { + return nil, err } - return &OnRamp{ - lggr: lggr, - client: source, - lp: sourceLP, - leafHasher: NewLeafHasher(sourceSelector, destSelector, onRampAddress, hashlib.NewKeccakCtx(), onRamp), - onRamp: onRamp, - filters: filters, - address: onRampAddress, - sendRequestedSeqNumberWord: CCIPSendRequestSeqNumIndex, - sendRequestedEventSig: CCIPSendRequestEventSig, + tokensAndAmounts := make([]cciptypes.TokenAmount, len(msg.Message.TokenAmounts)) + for i, tokenAndAmount := range msg.Message.TokenAmounts { + tokensAndAmounts[i] = cciptypes.TokenAmount{ + Token: cciptypes.Address(tokenAndAmount.Token.String()), + Amount: tokenAndAmount.Amount, + } + } + + return &cciptypes.EVM2EVMMessage{ + SequenceNumber: msg.Message.SequenceNumber, + GasLimit: msg.Message.GasLimit, + Nonce: msg.Message.Nonce, + MessageID: msg.Message.MessageId, + SourceChainSelector: msg.Message.SourceChainSelector, + Sender: cciptypes.Address(msg.Message.Sender.String()), + Receiver: cciptypes.Address(msg.Message.Receiver.String()), + Strict: msg.Message.Strict, + FeeToken: cciptypes.Address(msg.Message.FeeToken.String()), + FeeTokenAmount: msg.Message.FeeTokenAmount, + Data: msg.Message.Data, + TokenAmounts: tokensAndAmounts, + SourceTokenData: msg.Message.SourceTokenData, // Breaking change 1.2 + Hash: h, }, nil } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp_test.go index c8793991cb..bd183c3cb1 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp_test.go @@ -2,12 +2,8 @@ package v1_2_0 import ( "context" - "encoding/hex" - "math/big" "testing" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -15,74 +11,10 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp_1_2_0" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" ) -func TestHasherV1_2_0(t *testing.T) { - sourceChainSelector, destChainSelector := uint64(1), uint64(4) - onRampAddress := common.HexToAddress("0x5550000000000000000000000000000000000001") - onRampABI := abihelpers.MustParseABI(evm_2_evm_onramp_1_2_0.EVM2EVMOnRampABI) - - hashingCtx := hashlib.NewKeccakCtx() - ramp, err := evm_2_evm_onramp_1_2_0.NewEVM2EVMOnRamp(onRampAddress, nil) - require.NoError(t, err) - hasher := NewLeafHasher(sourceChainSelector, destChainSelector, onRampAddress, hashingCtx, ramp) - - message := evm_2_evm_onramp_1_2_0.InternalEVM2EVMMessage{ - SourceChainSelector: sourceChainSelector, - Sender: common.HexToAddress("0x1110000000000000000000000000000000000001"), - Receiver: common.HexToAddress("0x2220000000000000000000000000000000000001"), - SequenceNumber: 1337, - GasLimit: big.NewInt(100), - Strict: false, - Nonce: 1337, - FeeToken: common.Address{}, - FeeTokenAmount: big.NewInt(1), - Data: []byte{}, - TokenAmounts: []evm_2_evm_onramp_1_2_0.ClientEVMTokenAmount{{Token: common.HexToAddress("0x4440000000000000000000000000000000000001"), Amount: big.NewInt(12345678900)}}, - SourceTokenData: [][]byte{}, - MessageId: [32]byte{}, - } - - data, err := onRampABI.Events[CCIPSendRequestedEventName].Inputs.Pack(message) - require.NoError(t, err) - hash, err := hasher.HashLeaf(types.Log{Topics: []common.Hash{CCIPSendRequestEventSig}, Data: data}) - require.NoError(t, err) - - // NOTE: Must match spec - require.Equal(t, "46ad031bfb052db2e4a2514fed8dc480b98e5ce4acb55d5640d91407e0d8a3e9", hex.EncodeToString(hash[:])) - - message = evm_2_evm_onramp_1_2_0.InternalEVM2EVMMessage{ - SourceChainSelector: sourceChainSelector, - Sender: common.HexToAddress("0x1110000000000000000000000000000000000001"), - Receiver: common.HexToAddress("0x2220000000000000000000000000000000000001"), - SequenceNumber: 1337, - GasLimit: big.NewInt(100), - Strict: false, - Nonce: 1337, - FeeToken: common.Address{}, - FeeTokenAmount: big.NewInt(1e12), - Data: []byte("foo bar baz"), - TokenAmounts: []evm_2_evm_onramp_1_2_0.ClientEVMTokenAmount{ - {Token: common.HexToAddress("0x4440000000000000000000000000000000000001"), Amount: big.NewInt(12345678900)}, - {Token: common.HexToAddress("0x6660000000000000000000000000000000000001"), Amount: big.NewInt(4204242)}, - }, - SourceTokenData: [][]byte{{0x2, 0x1}}, - MessageId: [32]byte{}, - } - - data, err = onRampABI.Events[CCIPSendRequestedEventName].Inputs.Pack(message) - require.NoError(t, err) - hash, err = hasher.HashLeaf(types.Log{Topics: []common.Hash{CCIPSendRequestEventSig}, Data: data}) - require.NoError(t, err) - - // NOTE: Must match spec - require.Equal(t, "4362a13a42e52ff5ce4324e7184dc7aa41704c3146bc842d35d95b94b32a78b6", hex.EncodeToString(hash[:])) -} - func TestLogPollerClient_GetSendRequestsBetweenSeqNumsV1_2_0(t *testing.T) { onRampAddr := utils.RandomAddress() seqNum := uint64(100) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/hasher.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/hasher.go new file mode 100644 index 0000000000..dfd2f59c70 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/hasher.go @@ -0,0 +1,100 @@ +package v1_5_0 + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" +) + +const ( + MetaDataHashPrefix = "EVM2EVMMessageHashV2" +) + +type LeafHasher struct { + metaDataHash [32]byte + ctx hashlib.Ctx[[32]byte] + onRamp *evm_2_evm_onramp.EVM2EVMOnRamp +} + +func NewLeafHasher(sourceChainSelector uint64, destChainSelector uint64, onRampId common.Address, ctx hashlib.Ctx[[32]byte], onRamp *evm_2_evm_onramp.EVM2EVMOnRamp) *LeafHasher { + return &LeafHasher{ + metaDataHash: v1_0_0.GetMetaDataHash(ctx, ctx.Hash([]byte(MetaDataHashPrefix)), sourceChainSelector, onRampId, destChainSelector), + ctx: ctx, + onRamp: onRamp, + } +} + +func (t *LeafHasher) HashLeaf(log types.Log) ([32]byte, error) { + msg, err := t.onRamp.ParseCCIPSendRequested(log) + if err != nil { + return [32]byte{}, err + } + message := msg.Message + encodedTokens, err := utils.ABIEncode( + `[ +{"components": [{"name":"token","type":"address"},{"name":"amount","type":"uint256"}], "type":"tuple[]"}]`, message.TokenAmounts) + if err != nil { + return [32]byte{}, err + } + + bytesArray, err := abi.NewType("bytes[]", "bytes[]", nil) + if err != nil { + return [32]byte{}, err + } + + encodedSourceTokenData, err := abi.Arguments{abi.Argument{Type: bytesArray}}.PackValues([]interface{}{message.SourceTokenData}) + if err != nil { + return [32]byte{}, err + } + + packedFixedSizeValues, err := utils.ABIEncode( + `[ +{"name": "sender", "type":"address"}, +{"name": "receiver", "type":"address"}, +{"name": "sequenceNumber", "type":"uint64"}, +{"name": "gasLimit", "type":"uint256"}, +{"name": "strict", "type":"bool"}, +{"name": "nonce", "type":"uint64"}, +{"name": "feeToken","type": "address"}, +{"name": "feeTokenAmount","type": "uint256"} +]`, + message.Sender, + message.Receiver, + message.SequenceNumber, + message.GasLimit, + message.Strict, + message.Nonce, + message.FeeToken, + message.FeeTokenAmount, + ) + if err != nil { + return [32]byte{}, err + } + fixedSizeValuesHash := t.ctx.Hash(packedFixedSizeValues) + + packedValues, err := utils.ABIEncode( + `[ +{"name": "leafDomainSeparator","type":"bytes1"}, +{"name": "metadataHash", "type":"bytes32"}, +{"name": "fixedSizeValuesHash", "type":"bytes32"}, +{"name": "dataHash", "type":"bytes32"}, +{"name": "tokenAmountsHash", "type":"bytes32"}, +{"name": "sourceTokenDataHash", "type":"bytes32"} +]`, + v1_0_0.LeafDomainSeparator, + t.metaDataHash, + fixedSizeValuesHash, + t.ctx.Hash(message.Data), + t.ctx.Hash(encodedTokens), + t.ctx.Hash(encodedSourceTokenData), + ) + if err != nil { + return [32]byte{}, err + } + return t.ctx.Hash(packedValues), nil +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/hasher_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/hasher_test.go new file mode 100644 index 0000000000..77ec30003d --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/hasher_test.go @@ -0,0 +1,77 @@ +package v1_5_0 + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/test-go/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" +) + +func TestHasherV1_4_0(t *testing.T) { + sourceChainSelector, destChainSelector := uint64(1), uint64(4) + onRampAddress := common.HexToAddress("0x5550000000000000000000000000000000000001") + onRampABI := abihelpers.MustParseABI(evm_2_evm_onramp.EVM2EVMOnRampABI) + + hashingCtx := hashlib.NewKeccakCtx() + ramp, err := evm_2_evm_onramp.NewEVM2EVMOnRamp(onRampAddress, nil) + require.NoError(t, err) + hasher := NewLeafHasher(sourceChainSelector, destChainSelector, onRampAddress, hashingCtx, ramp) + + message := evm_2_evm_onramp.InternalEVM2EVMMessage{ + SourceChainSelector: sourceChainSelector, + Sender: common.HexToAddress("0x1110000000000000000000000000000000000001"), + Receiver: common.HexToAddress("0x2220000000000000000000000000000000000001"), + SequenceNumber: 1337, + GasLimit: big.NewInt(100), + Strict: false, + Nonce: 1337, + FeeToken: common.Address{}, + FeeTokenAmount: big.NewInt(1), + Data: []byte{}, + TokenAmounts: []evm_2_evm_onramp.ClientEVMTokenAmount{{Token: common.HexToAddress("0x4440000000000000000000000000000000000001"), Amount: big.NewInt(12345678900)}}, + SourceTokenData: [][]byte{}, + MessageId: [32]byte{}, + } + + data, err := onRampABI.Events[CCIPSendRequestedEventName].Inputs.Pack(message) + require.NoError(t, err) + hash, err := hasher.HashLeaf(types.Log{Topics: []common.Hash{CCIPSendRequestEventSig}, Data: data}) + require.NoError(t, err) + + // NOTE: Must match spec + require.Equal(t, "46ad031bfb052db2e4a2514fed8dc480b98e5ce4acb55d5640d91407e0d8a3e9", hex.EncodeToString(hash[:])) + + message = evm_2_evm_onramp.InternalEVM2EVMMessage{ + SourceChainSelector: sourceChainSelector, + Sender: common.HexToAddress("0x1110000000000000000000000000000000000001"), + Receiver: common.HexToAddress("0x2220000000000000000000000000000000000001"), + SequenceNumber: 1337, + GasLimit: big.NewInt(100), + Strict: false, + Nonce: 1337, + FeeToken: common.Address{}, + FeeTokenAmount: big.NewInt(1e12), + Data: []byte("foo bar baz"), + TokenAmounts: []evm_2_evm_onramp.ClientEVMTokenAmount{ + {Token: common.HexToAddress("0x4440000000000000000000000000000000000001"), Amount: big.NewInt(12345678900)}, + {Token: common.HexToAddress("0x6660000000000000000000000000000000000001"), Amount: big.NewInt(4204242)}, + }, + SourceTokenData: [][]byte{{0x2, 0x1}}, + MessageId: [32]byte{}, + } + + data, err = onRampABI.Events[CCIPSendRequestedEventName].Inputs.Pack(message) + require.NoError(t, err) + hash, err = hasher.HashLeaf(types.Log{Topics: []common.Hash{CCIPSendRequestEventSig}, Data: data}) + require.NoError(t, err) + + // NOTE: Must match spec + require.Equal(t, "4362a13a42e52ff5ce4324e7184dc7aa41704c3146bc842d35d95b94b32a78b6", hex.EncodeToString(hash[:])) +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go index 3a9a9e9214..1412d39d70 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go @@ -12,14 +12,12 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/cciptypes" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/logpollerutil" "github.com/smartcontractkit/chainlink/v2/core/services/pg" @@ -33,8 +31,6 @@ var ( const ( CCIPSendRequestSeqNumIndex = 4 CCIPSendRequestedEventName = "CCIPSendRequested" - EVM2EVMOffRampEventName = "EVM2EVMMessage" - MetaDataHashPrefix = "EVM2EVMMessageHashV2" ) func init() { @@ -45,90 +41,6 @@ func init() { CCIPSendRequestEventSig = abihelpers.MustGetEventID(CCIPSendRequestedEventName, onRampABI) } -type LeafHasher struct { - metaDataHash [32]byte - ctx hashlib.Ctx[[32]byte] - onRamp *evm_2_evm_onramp.EVM2EVMOnRamp -} - -func NewLeafHasher(sourceChainSelector uint64, destChainSelector uint64, onRampId common.Address, ctx hashlib.Ctx[[32]byte], onRamp *evm_2_evm_onramp.EVM2EVMOnRamp) *LeafHasher { - return &LeafHasher{ - metaDataHash: v1_0_0.GetMetaDataHash(ctx, ctx.Hash([]byte(MetaDataHashPrefix)), sourceChainSelector, onRampId, destChainSelector), - ctx: ctx, - onRamp: onRamp, - } -} - -func (t *LeafHasher) HashLeaf(log types.Log) ([32]byte, error) { - msg, err := t.onRamp.ParseCCIPSendRequested(log) - if err != nil { - return [32]byte{}, err - } - message := msg.Message - encodedTokens, err := utils.ABIEncode( - `[ -{"components": [{"name":"token","type":"address"},{"name":"amount","type":"uint256"}], "type":"tuple[]"}]`, message.TokenAmounts) - if err != nil { - return [32]byte{}, err - } - - bytesArray, err := abi.NewType("bytes[]", "bytes[]", nil) - if err != nil { - return [32]byte{}, err - } - - encodedSourceTokenData, err := abi.Arguments{abi.Argument{Type: bytesArray}}.PackValues([]interface{}{message.SourceTokenData}) - if err != nil { - return [32]byte{}, err - } - - packedFixedSizeValues, err := utils.ABIEncode( - `[ -{"name": "sender", "type":"address"}, -{"name": "receiver", "type":"address"}, -{"name": "sequenceNumber", "type":"uint64"}, -{"name": "gasLimit", "type":"uint256"}, -{"name": "strict", "type":"bool"}, -{"name": "nonce", "type":"uint64"}, -{"name": "feeToken","type": "address"}, -{"name": "feeTokenAmount","type": "uint256"} -]`, - message.Sender, - message.Receiver, - message.SequenceNumber, - message.GasLimit, - message.Strict, - message.Nonce, - message.FeeToken, - message.FeeTokenAmount, - ) - if err != nil { - return [32]byte{}, err - } - fixedSizeValuesHash := t.ctx.Hash(packedFixedSizeValues) - - packedValues, err := utils.ABIEncode( - `[ -{"name": "leafDomainSeparator","type":"bytes1"}, -{"name": "metadataHash", "type":"bytes32"}, -{"name": "fixedSizeValuesHash", "type":"bytes32"}, -{"name": "dataHash", "type":"bytes32"}, -{"name": "tokenAmountsHash", "type":"bytes32"}, -{"name": "sourceTokenDataHash", "type":"bytes32"} -]`, - v1_0_0.LeafDomainSeparator, - t.metaDataHash, - fixedSizeValuesHash, - t.ctx.Hash(message.Data), - t.ctx.Hash(encodedTokens), - t.ctx.Hash(encodedSourceTokenData), - ) - if err != nil { - return [32]byte{}, err - } - return t.ctx.Hash(packedValues), nil -} - var _ ccipdata.OnRampReader = &OnRamp{} // Significant change in 1.2: @@ -145,6 +57,33 @@ type OnRamp struct { filters []logpoller.Filter } +func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAddress common.Address, sourceLP logpoller.LogPoller, source client.Client) (*OnRamp, error) { + onRamp, err := evm_2_evm_onramp.NewEVM2EVMOnRamp(onRampAddress, source) + if err != nil { + return nil, err + } + // Subscribe to the relevant logs + // Note we can keep the same prefix across 1.0/1.1 and 1.2 because the onramp addresses will be different + filters := []logpoller.Filter{ + { + Name: logpoller.FilterName(ccipdata.COMMIT_CCIP_SENDS, onRampAddress), + EventSigs: []common.Hash{CCIPSendRequestEventSig}, + Addresses: []common.Address{onRampAddress}, + }, + } + return &OnRamp{ + lggr: lggr, + client: source, + lp: sourceLP, + leafHasher: NewLeafHasher(sourceSelector, destSelector, onRampAddress, hashlib.NewKeccakCtx(), onRamp), + onRamp: onRamp, + filters: filters, + address: onRampAddress, + sendRequestedSeqNumberWord: CCIPSendRequestSeqNumIndex, + sendRequestedEventSig: CCIPSendRequestEventSig, + }, nil +} + func (o *OnRamp) Address() (cciptypes.Address, error) { return ccipcalc.EvmAddrToGeneric(o.onRamp.Address()), nil } @@ -171,41 +110,6 @@ func (o *OnRamp) GetDynamicConfig() (cciptypes.OnRampDynamicConfig, error) { }, nil } -func (o *OnRamp) logToMessage(log types.Log) (*cciptypes.EVM2EVMMessage, error) { - msg, err := o.onRamp.ParseCCIPSendRequested(log) - if err != nil { - return nil, err - } - h, err := o.leafHasher.HashLeaf(log) - if err != nil { - return nil, err - } - tokensAndAmounts := make([]cciptypes.TokenAmount, len(msg.Message.TokenAmounts)) - for i, tokenAndAmount := range msg.Message.TokenAmounts { - tokensAndAmounts[i] = cciptypes.TokenAmount{ - Token: ccipcalc.EvmAddrToGeneric(tokenAndAmount.Token), - Amount: tokenAndAmount.Amount, - } - } - - return &cciptypes.EVM2EVMMessage{ - SequenceNumber: msg.Message.SequenceNumber, - GasLimit: msg.Message.GasLimit, - Nonce: msg.Message.Nonce, - MessageID: msg.Message.MessageId, - SourceChainSelector: msg.Message.SourceChainSelector, - Sender: ccipcalc.EvmAddrToGeneric(msg.Message.Sender), - Receiver: ccipcalc.EvmAddrToGeneric(msg.Message.Receiver), - Strict: msg.Message.Strict, - FeeToken: ccipcalc.EvmAddrToGeneric(msg.Message.FeeToken), - FeeTokenAmount: msg.Message.FeeTokenAmount, - Data: msg.Message.Data, - TokenAmounts: tokensAndAmounts, - SourceTokenData: msg.Message.SourceTokenData, // Breaking change 1.2 - Hash: h, - }, nil -} - func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, seqNumMax uint64, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { logs, err := o.lp.LogsDataWordRange( o.sendRequestedEventSig, @@ -250,29 +154,37 @@ func (o *OnRamp) RegisterFilters(qopts ...pg.QOpt) error { return logpollerutil.RegisterLpFilters(o.lp, o.filters, qopts...) } -func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAddress common.Address, sourceLP logpoller.LogPoller, source client.Client) (*OnRamp, error) { - onRamp, err := evm_2_evm_onramp.NewEVM2EVMOnRamp(onRampAddress, source) +func (o *OnRamp) logToMessage(log types.Log) (*cciptypes.EVM2EVMMessage, error) { + msg, err := o.onRamp.ParseCCIPSendRequested(log) if err != nil { return nil, err } - // Subscribe to the relevant logs - // Note we can keep the same prefix across 1.0/1.1 and 1.2 because the onramp addresses will be different - filters := []logpoller.Filter{ - { - Name: logpoller.FilterName(ccipdata.COMMIT_CCIP_SENDS, onRampAddress), - EventSigs: []common.Hash{CCIPSendRequestEventSig}, - Addresses: []common.Address{onRampAddress}, - }, + h, err := o.leafHasher.HashLeaf(log) + if err != nil { + return nil, err } - return &OnRamp{ - lggr: lggr, - client: source, - lp: sourceLP, - leafHasher: NewLeafHasher(sourceSelector, destSelector, onRampAddress, hashlib.NewKeccakCtx(), onRamp), - onRamp: onRamp, - filters: filters, - address: onRampAddress, - sendRequestedSeqNumberWord: CCIPSendRequestSeqNumIndex, - sendRequestedEventSig: CCIPSendRequestEventSig, + tokensAndAmounts := make([]cciptypes.TokenAmount, len(msg.Message.TokenAmounts)) + for i, tokenAndAmount := range msg.Message.TokenAmounts { + tokensAndAmounts[i] = cciptypes.TokenAmount{ + Token: ccipcalc.EvmAddrToGeneric(tokenAndAmount.Token), + Amount: tokenAndAmount.Amount, + } + } + + return &cciptypes.EVM2EVMMessage{ + SequenceNumber: msg.Message.SequenceNumber, + GasLimit: msg.Message.GasLimit, + Nonce: msg.Message.Nonce, + MessageID: msg.Message.MessageId, + SourceChainSelector: msg.Message.SourceChainSelector, + Sender: ccipcalc.EvmAddrToGeneric(msg.Message.Sender), + Receiver: ccipcalc.EvmAddrToGeneric(msg.Message.Receiver), + Strict: msg.Message.Strict, + FeeToken: ccipcalc.EvmAddrToGeneric(msg.Message.FeeToken), + FeeTokenAmount: msg.Message.FeeTokenAmount, + Data: msg.Message.Data, + TokenAmounts: tokensAndAmounts, + SourceTokenData: msg.Message.SourceTokenData, // Breaking change 1.2 + Hash: h, }, nil } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp_test.go index 0bfbff4a8a..669166f0a5 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp_test.go @@ -2,12 +2,8 @@ package v1_5_0 import ( "context" - "encoding/hex" - "math/big" "testing" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -15,74 +11,10 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/hashlib" ) -func TestHasherV1_4_0(t *testing.T) { - sourceChainSelector, destChainSelector := uint64(1), uint64(4) - onRampAddress := common.HexToAddress("0x5550000000000000000000000000000000000001") - onRampABI := abihelpers.MustParseABI(evm_2_evm_onramp.EVM2EVMOnRampABI) - - hashingCtx := hashlib.NewKeccakCtx() - ramp, err := evm_2_evm_onramp.NewEVM2EVMOnRamp(onRampAddress, nil) - require.NoError(t, err) - hasher := NewLeafHasher(sourceChainSelector, destChainSelector, onRampAddress, hashingCtx, ramp) - - message := evm_2_evm_onramp.InternalEVM2EVMMessage{ - SourceChainSelector: sourceChainSelector, - Sender: common.HexToAddress("0x1110000000000000000000000000000000000001"), - Receiver: common.HexToAddress("0x2220000000000000000000000000000000000001"), - SequenceNumber: 1337, - GasLimit: big.NewInt(100), - Strict: false, - Nonce: 1337, - FeeToken: common.Address{}, - FeeTokenAmount: big.NewInt(1), - Data: []byte{}, - TokenAmounts: []evm_2_evm_onramp.ClientEVMTokenAmount{{Token: common.HexToAddress("0x4440000000000000000000000000000000000001"), Amount: big.NewInt(12345678900)}}, - SourceTokenData: [][]byte{}, - MessageId: [32]byte{}, - } - - data, err := onRampABI.Events[CCIPSendRequestedEventName].Inputs.Pack(message) - require.NoError(t, err) - hash, err := hasher.HashLeaf(types.Log{Topics: []common.Hash{CCIPSendRequestEventSig}, Data: data}) - require.NoError(t, err) - - // NOTE: Must match spec - require.Equal(t, "46ad031bfb052db2e4a2514fed8dc480b98e5ce4acb55d5640d91407e0d8a3e9", hex.EncodeToString(hash[:])) - - message = evm_2_evm_onramp.InternalEVM2EVMMessage{ - SourceChainSelector: sourceChainSelector, - Sender: common.HexToAddress("0x1110000000000000000000000000000000000001"), - Receiver: common.HexToAddress("0x2220000000000000000000000000000000000001"), - SequenceNumber: 1337, - GasLimit: big.NewInt(100), - Strict: false, - Nonce: 1337, - FeeToken: common.Address{}, - FeeTokenAmount: big.NewInt(1e12), - Data: []byte("foo bar baz"), - TokenAmounts: []evm_2_evm_onramp.ClientEVMTokenAmount{ - {Token: common.HexToAddress("0x4440000000000000000000000000000000000001"), Amount: big.NewInt(12345678900)}, - {Token: common.HexToAddress("0x6660000000000000000000000000000000000001"), Amount: big.NewInt(4204242)}, - }, - SourceTokenData: [][]byte{{0x2, 0x1}}, - MessageId: [32]byte{}, - } - - data, err = onRampABI.Events[CCIPSendRequestedEventName].Inputs.Pack(message) - require.NoError(t, err) - hash, err = hasher.HashLeaf(types.Log{Topics: []common.Hash{CCIPSendRequestEventSig}, Data: data}) - require.NoError(t, err) - - // NOTE: Must match spec - require.Equal(t, "4362a13a42e52ff5ce4324e7184dc7aa41704c3146bc842d35d95b94b32a78b6", hex.EncodeToString(hash[:])) -} - func TestLogPollerClient_GetSendRequestsBetweenSeqNums1_4_0(t *testing.T) { onRampAddr := utils.RandomAddress() seqNum := uint64(100)