diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/abi.go b/core/services/ocr2/plugins/ocr2keeper/evm21/abi.go index 1e39712b3dc..145fb89da45 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/abi.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/abi.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" ) @@ -32,12 +33,17 @@ const ( type UpkeepInfo = iregistry21.KeeperRegistryBase21UpkeepInfo +// triggerWrapper is a wrapper for the different trigger types (log and condition triggers). +// NOTE: we use log trigger because it extends condition trigger, +type triggerWrapper = automation_utils_2_1.KeeperRegistryBase21LogTrigger + type evmRegistryPackerV2_1 struct { - abi abi.ABI + abi abi.ABI + utilsAbi abi.ABI } -func NewEvmRegistryPackerV2_1(abi abi.ABI) *evmRegistryPackerV2_1 { - return &evmRegistryPackerV2_1{abi: abi} +func NewEvmRegistryPackerV2_1(abi abi.ABI, utilsAbi abi.ABI) *evmRegistryPackerV2_1 { + return &evmRegistryPackerV2_1{abi: abi, utilsAbi: utilsAbi} } // TODO: remove for 2.1 @@ -175,3 +181,88 @@ func (rp *evmRegistryPackerV2_1) UnpackLogTriggerConfig(raw []byte) (iregistry21 } return *converted, nil } + +// PackTrigger packs the trigger into the format expected by the contract, +// according to the upkeep type of the given id. +func (rp *evmRegistryPackerV2_1) PackTrigger(id *big.Int, trig triggerWrapper) ([]byte, error) { + var trigger []byte + var err error + upkeepType := getUpkeepType(id.Bytes()) + switch upkeepType { + case conditionTrigger: + trig := automation_utils_2_1.KeeperRegistryBase21ConditionalTrigger{ + BlockNum: trig.BlockNum, + BlockHash: trig.BlockHash, + } + trigger, err = rp.utilsAbi.Pack("_conditionalTrigger", &trig) + case logTrigger: + logTrig := automation_utils_2_1.KeeperRegistryBase21LogTrigger{ + BlockNum: trig.BlockNum, + BlockHash: trig.BlockHash, + LogIndex: trig.LogIndex, + TxHash: trig.TxHash, + } + trigger, err = rp.utilsAbi.Pack("_logTrigger", &logTrig) + default: + err = fmt.Errorf("unknown trigger type: %d", upkeepType) + } + if err != nil { + return nil, err + } + return trigger[4:], nil +} + +// UnpackTrigger unpacks the trigger from the given raw data, according to the upkeep type of the given id. +func (rp *evmRegistryPackerV2_1) UnpackTrigger(id *big.Int, raw []byte) (triggerWrapper, error) { + upkeepType := getUpkeepType(id.Bytes()) + switch upkeepType { + case conditionTrigger: + unpacked, err := rp.utilsAbi.Methods["_conditionalTrigger"].Inputs.Unpack(raw) + if err != nil { + return triggerWrapper{}, fmt.Errorf("%w: failed to unpack conditional trigger", err) + } + converted, ok := abi.ConvertType(unpacked[0], new(automation_utils_2_1.KeeperRegistryBase21ConditionalTrigger)).(*automation_utils_2_1.KeeperRegistryBase21ConditionalTrigger) + if !ok { + return automation_utils_2_1.KeeperRegistryBase21LogTrigger{}, fmt.Errorf("failed to convert type") + } + return triggerWrapper{ + BlockNum: converted.BlockNum, + BlockHash: converted.BlockHash, + }, nil + case logTrigger: + unpacked, err := rp.utilsAbi.Methods["_logTrigger"].Inputs.Unpack(raw) + if err != nil { + return triggerWrapper{}, fmt.Errorf("%w: failed to unpack log trigger", err) + } + converted, ok := abi.ConvertType(unpacked[0], new(automation_utils_2_1.KeeperRegistryBase21LogTrigger)).(*automation_utils_2_1.KeeperRegistryBase21LogTrigger) + if !ok { + return automation_utils_2_1.KeeperRegistryBase21LogTrigger{}, fmt.Errorf("failed to convert type") + } + return triggerWrapper(*converted), nil + default: + return triggerWrapper{}, fmt.Errorf("unknown trigger type: %d", upkeepType) + } +} + +// PackReport packs the report with abi definitions from the contract. +func (rp *evmRegistryPackerV2_1) PackReport(report automation_utils_2_1.KeeperRegistryBase21Report) ([]byte, error) { + bts, err := rp.utilsAbi.Pack("_report", &report) + if err != nil { + return nil, fmt.Errorf("%w: failed to pack report", err) + } + + return bts[4:], nil +} + +// UnpackReport unpacks the report from the given raw data. +func (rp *evmRegistryPackerV2_1) UnpackReport(raw []byte) (automation_utils_2_1.KeeperRegistryBase21Report, error) { + unpacked, err := rp.utilsAbi.Methods["_report"].Inputs.Unpack(raw) + if err != nil { + return automation_utils_2_1.KeeperRegistryBase21Report{}, fmt.Errorf("%w: failed to unpack report", err) + } + converted, ok := abi.ConvertType(unpacked[0], new(automation_utils_2_1.KeeperRegistryBase21Report)).(*automation_utils_2_1.KeeperRegistryBase21Report) + if !ok { + return automation_utils_2_1.KeeperRegistryBase21Report{}, fmt.Errorf("failed to convert type") + } + return *converted, nil +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/abi_test.go b/core/services/ocr2/plugins/ocr2keeper/evm21/abi_test.go index 2cfb718a154..9df9795e155 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/abi_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/abi_test.go @@ -1,6 +1,7 @@ package evm import ( + "fmt" "math/big" "strings" "testing" @@ -10,10 +11,13 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/assert" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" + + ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg" ) -func TestUnpackTransmitTxInputErrors(t *testing.T) { +func TestPacker_UnpackTransmitTxInputErrors(t *testing.T) { tests := []struct { Name string @@ -30,22 +34,15 @@ func TestUnpackTransmitTxInputErrors(t *testing.T) { } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - abi, err := abi.JSON(strings.NewReader(iregistry21.IKeeperRegistryMasterABI)) - assert.Nil(t, err) - - packer := NewEvmRegistryPackerV2_1(abi) + packer, err := newPacker() + assert.NoError(t, err) _, err = packer.UnpackTransmitTxInput(hexutil.MustDecode(test.RawData)) assert.NotNil(t, err) }) } } -func TestUnpackPerformResult(t *testing.T) { - registryABI, err := abi.JSON(strings.NewReader(iregistry21.IKeeperRegistryMasterABI)) - if err != nil { - assert.Nil(t, err) - } - +func TestPacker_UnpackPerformResult(t *testing.T) { tests := []struct { Name string RawData string @@ -57,7 +54,8 @@ func TestUnpackPerformResult(t *testing.T) { } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - packer := NewEvmRegistryPackerV2_1(registryABI) + packer, err := newPacker() + assert.NoError(t, err) rs, err := packer.UnpackPerformResult(test.RawData) assert.Nil(t, err) assert.True(t, rs) @@ -65,12 +63,7 @@ func TestUnpackPerformResult(t *testing.T) { } } -func TestUnpackCheckCallbackResult(t *testing.T) { - registryABI, err := abi.JSON(strings.NewReader(iregistry21.IKeeperRegistryMasterABI)) - if err != nil { - assert.Nil(t, err) - } - +func TestPacker_UnpackCheckCallbackResult(t *testing.T) { tests := []struct { Name string CallbackResp []byte @@ -106,7 +99,9 @@ func TestUnpackCheckCallbackResult(t *testing.T) { } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - packer := NewEvmRegistryPackerV2_1(registryABI) + packer, err := newPacker() + assert.NoError(t, err) + needed, pd, failureReason, gasUsed, err := packer.UnpackCheckCallbackResult(test.CallbackResp) if test.ErrorString != "" { @@ -122,9 +117,7 @@ func TestUnpackCheckCallbackResult(t *testing.T) { } } -func TestUnpackLogTriggerConfig(t *testing.T) { - keeperRegistryABI, err := abi.JSON(strings.NewReader(iregistry21.IKeeperRegistryMasterABI)) - assert.NoError(t, err) +func TestPacker_UnpackLogTriggerConfig(t *testing.T) { tests := []struct { name string raw []byte @@ -154,11 +147,10 @@ func TestUnpackLogTriggerConfig(t *testing.T) { }, } - packer := NewEvmRegistryPackerV2_1(keeperRegistryABI) - for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - + packer, err := newPacker() + assert.NoError(t, err) res, err := packer.UnpackLogTriggerConfig(tc.raw) if tc.errored { assert.Error(t, err) @@ -169,3 +161,102 @@ func TestUnpackLogTriggerConfig(t *testing.T) { }) } } + +func TestPacker_PackingTrigger(t *testing.T) { + tests := []struct { + name string + id ocr2keepers.UpkeepIdentifier + trigger triggerWrapper + encoded []byte + err error + }{ + { + "happy flow log trigger", + append([]byte{1}, common.LeftPadBytes([]byte{1}, 15)...), + triggerWrapper{ + BlockNum: 1, + BlockHash: common.HexToHash("0x01111111"), + LogIndex: 1, + TxHash: common.HexToHash("0x01111111"), + }, + func() []byte { + b, _ := hexutil.Decode("0x0000000000000000000000000000000000000000000000000000000001111111000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") + return b + }(), + nil, + }, + { + "happy flow conditional trigger", + append([]byte{1}, common.LeftPadBytes([]byte{0}, 15)...), + triggerWrapper{ + BlockNum: 1, + BlockHash: common.HexToHash("0x01111111"), + }, + func() []byte { + b, _ := hexutil.Decode("0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") + return b + }(), + nil, + }, + { + "invalid type", + append([]byte{1}, common.LeftPadBytes([]byte{8}, 15)...), + triggerWrapper{ + BlockNum: 1, + BlockHash: common.HexToHash("0x01111111"), + }, + []byte{}, + fmt.Errorf("unknown trigger type: %d", 8), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + packer, err := newPacker() + assert.NoError(t, err) + id, ok := big.NewInt(0).SetString(hexutil.Encode(tc.id)[2:], 16) + assert.True(t, ok) + + encoded, err := packer.PackTrigger(id, tc.trigger) + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.encoded, encoded) + decoded, err := packer.UnpackTrigger(id, encoded) + assert.NoError(t, err) + assert.Equal(t, tc.trigger.BlockNum, decoded.BlockNum) + } + }) + } + + t.Run("unpacking invalid trigger", func(t *testing.T) { + packer, err := newPacker() + assert.NoError(t, err) + _, err = packer.UnpackTrigger(big.NewInt(0), []byte{1, 2, 3}) + assert.Error(t, err) + }) + + t.Run("unpacking unknown type", func(t *testing.T) { + packer, err := newPacker() + assert.NoError(t, err) + uid := append([]byte{1}, common.LeftPadBytes([]byte{8}, 15)...) + id, ok := big.NewInt(0).SetString(hexutil.Encode(uid)[2:], 16) + assert.True(t, ok) + decoded, _ := hexutil.Decode("0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001111111") + _, err = packer.UnpackTrigger(id, decoded) + assert.EqualError(t, err, "unknown trigger type: 8") + }) +} + +func newPacker() (*evmRegistryPackerV2_1, error) { + keepersABI, err := abi.JSON(strings.NewReader(iregistry21.IKeeperRegistryMasterABI)) + if err != nil { + return nil, err + } + utilsABI, err := abi.JSON(strings.NewReader(automation_utils_2_1.AutomationUtilsABI)) + if err != nil { + return nil, err + } + return NewEvmRegistryPackerV2_1(keepersABI, utilsABI), nil +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/encoder.go b/core/services/ocr2/plugins/ocr2keeper/evm21/encoder.go index 8b3c0e6b8fd..943e6f42379 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/encoder.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/encoder.go @@ -5,44 +5,19 @@ import ( "math/big" "reflect" - "github.com/ethereum/go-ethereum/accounts/abi" ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg" "github.com/smartcontractkit/ocr2keepers/pkg/encoding" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" ) type EVMAutomationEncoder21 struct { encoding.BasicEncoder -} - -func mustNewType(t string, internalType string, components []abi.ArgumentMarshaling) abi.Type { - a, err := abi.NewType(t, internalType, components) - if err != nil { - panic(err) - } - return a + packer *evmRegistryPackerV2_1 } var ( - Uint256 = mustNewType("uint256", "", nil) - Uint256Arr = mustNewType("uint256[]", "", nil) - BytesArr = mustNewType("bytes[]", "", nil) - TriggerMarshalingArgs = []abi.ArgumentMarshaling{ - {Name: "blockNumber", Type: "uint32"}, - {Name: "blockHash", Type: "bytes32"}, - } - TriggerArr = mustNewType("tuple(uint32,bytes32)[]", "", TriggerMarshalingArgs) ErrUnexpectedResult = fmt.Errorf("unexpected result struct") - packFn = reportArgs.Pack - unpackIntoMapFn = reportArgs.UnpackIntoMap - mKeys = []string{"fastGasWei", "linkNative", "upkeepIds", "gasLimits", "triggers", "performDatas"} - reportArgs = abi.Arguments{ - {Name: mKeys[0], Type: Uint256}, - {Name: mKeys[1], Type: Uint256}, - {Name: mKeys[2], Type: Uint256Arr}, - {Name: mKeys[3], Type: Uint256Arr}, - {Name: mKeys[4], Type: TriggerArr}, - {Name: mKeys[5], Type: BytesArr}, - } ) type EVMAutomationUpkeepResult21 struct { @@ -64,20 +39,18 @@ type EVMAutomationUpkeepResult21 struct { Retryable bool } +// TODO: align once we merge with ocr2keepers new types (ocr2keepers.CheckResult) func (enc EVMAutomationEncoder21) EncodeReport(toReport []ocr2keepers.UpkeepResult) ([]byte, error) { if len(toReport) == 0 { return nil, nil } - var ( - fastGas *big.Int - link *big.Int - ) - - ids := make([]*big.Int, len(toReport)) - gasLimits := make([]*big.Int, len(toReport)) - triggers := make([]wrappedTrigger, len(toReport)) - performDatas := make([][]byte, len(toReport)) + report := automation_utils_2_1.KeeperRegistryBase21Report{ + UpkeepIds: make([]*big.Int, len(toReport)), + GasLimits: make([]*big.Int, len(toReport)), + Triggers: make([][]byte, len(toReport)), + PerformDatas: make([][]byte, len(toReport)), + } for i, result := range toReport { res, ok := result.(EVMAutomationUpkeepResult21) @@ -88,101 +61,57 @@ func (enc EVMAutomationEncoder21) EncodeReport(toReport []ocr2keepers.UpkeepResu // only take these values from the first result // TODO: find a new way to get these values if i == 0 { - fastGas = res.FastGasWei - link = res.LinkNative + report.FastGasWei = res.FastGasWei + report.LinkNative = res.LinkNative } - ids[i] = res.ID - gasLimits[i] = res.GasUsed - triggers[i] = wrappedTrigger{ - BlockNumber: res.CheckBlockNumber, - BlockHash: res.CheckBlockHash, + report.UpkeepIds[i] = res.ID + report.GasLimits[i] = res.GasUsed + trigger, err := enc.packer.PackTrigger(res.ID, triggerWrapper{ + BlockNum: res.CheckBlockNumber, + BlockHash: res.CheckBlockHash, + // TODO: fill with real info + // LogIndex: 0, + // TxHash: [32]byte{}, + }) + if err != nil { + return nil, fmt.Errorf("%w: failed to pack trigger", err) } - performDatas[i] = res.PerformData + report.Triggers[i] = trigger + report.PerformDatas[i] = res.PerformData } - bts, err := packFn(fastGas, link, ids, gasLimits, triggers, performDatas) - if err != nil { - return []byte{}, fmt.Errorf("%w: failed to pack report data", err) - } - - return bts, nil + return enc.packer.PackReport(report) } -func (enc EVMAutomationEncoder21) DecodeReport(report []byte) ([]ocr2keepers.UpkeepResult, error) { - m := make(map[string]interface{}) - if err := unpackIntoMapFn(m, report); err != nil { +func (enc EVMAutomationEncoder21) DecodeReport(raw []byte) ([]ocr2keepers.UpkeepResult, error) { + report, err := enc.packer.UnpackReport(raw) + if err != nil { return nil, err } - for _, key := range mKeys { - if _, ok := m[key]; !ok { - return nil, fmt.Errorf("decoding error: %s missing from struct", key) - } - } - - res := []ocr2keepers.UpkeepResult{} - - var ( - ok bool - upkeepIds []*big.Int - performs [][]byte - // gasLimits []*big.Int // TODO - wei *big.Int - link *big.Int - ) - - if upkeepIds, ok = m[mKeys[2]].([]*big.Int); !ok { - return res, fmt.Errorf("upkeep ids of incorrect type in report") - } - - // TODO: a type assertion on `wrappedTrigger` did not work, even with the - // exact same struct definition as what follows. reflect was used to get the - // struct definition. not sure yet how to clean this up. - // ex: - // t := reflect.TypeOf(rawPerforms) - // fmt.Printf("%v\n", t) - triggers, ok := m[mKeys[4]].([]struct { - BlockNumber uint32 `abi:"blockNumber"` - BlockHash [32]byte `abi:"blockHash"` - }) - if !ok { - return res, fmt.Errorf("triggers of incorrect structure in report") - } - - if len(upkeepIds) != len(triggers) { - return res, fmt.Errorf("upkeep ids and triggers should have matching length") - } - - if wei, ok = m[mKeys[0]].(*big.Int); !ok { - return res, fmt.Errorf("fast gas as wrong type") - } - - if link, ok = m[mKeys[1]].(*big.Int); !ok { - return res, fmt.Errorf("link native as wrong type") - } - // if gasLimits, ok = m[mKeys[3]].([]*big.Int); !ok { - // return res, fmt.Errorf("gas limits as wrong type") - // } - - if performs, ok = m[mKeys[5]].([][]byte); !ok { - return res, fmt.Errorf("perform datas as wrong type") + if err := enc.validateReport(report); err != nil { + return nil, err } - res = make([]ocr2keepers.UpkeepResult, len(upkeepIds)) + res := make([]ocr2keepers.UpkeepResult, len(report.UpkeepIds)) - for i := 0; i < len(upkeepIds); i++ { + for i := 0; i < len(report.UpkeepIds); i++ { + trigger, err := enc.packer.UnpackTrigger(report.UpkeepIds[i], report.Triggers[i]) + if err != nil { + // TODO: log error and continue instead? + return nil, fmt.Errorf("%w: failed to unpack trigger", err) + } r := EVMAutomationUpkeepResult21{ - Block: triggers[i].BlockNumber, - ID: upkeepIds[i], + Block: trigger.BlockNum, + ID: report.UpkeepIds[i], Eligible: true, - PerformData: performs[i], - FastGasWei: wei, - LinkNative: link, - CheckBlockNumber: triggers[i].BlockNumber, - CheckBlockHash: triggers[i].BlockHash, + PerformData: report.PerformDatas[i], + FastGasWei: report.FastGasWei, + LinkNative: report.LinkNative, + CheckBlockNumber: trigger.BlockNum, + CheckBlockHash: trigger.BlockHash, } - res[i] = ocr2keepers.UpkeepResult(r) } @@ -230,9 +159,20 @@ func (enc EVMAutomationEncoder21) KeysFromReport(b []byte) ([]ocr2keepers.Upkeep return keys, nil } -type wrappedTrigger struct { - BlockNumber uint32 `abi:"blockNumber"` - BlockHash [32]byte `abi:"blockHash"` +// validateReport checks that the report is valid, currently checking that all +// lists are the same length. +// TODO: add more validations? e.g. parse validate triggers +func (enc EVMAutomationEncoder21) validateReport(report automation_utils_2_1.KeeperRegistryBase21Report) error { + if len(report.UpkeepIds) != len(report.GasLimits) { + return fmt.Errorf("invalid report: upkeepIds and gasLimits must be the same length") + } + if len(report.UpkeepIds) != len(report.Triggers) { + return fmt.Errorf("invalid report: upkeepIds and triggers must be the same length") + } + if len(report.UpkeepIds) != len(report.PerformDatas) { + return fmt.Errorf("invalid report: upkeepIds and performDatas must be the same length") + } + return nil } type BlockKeyHelper[T uint32 | int64] struct { diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/encoder_test.go b/core/services/ocr2/plugins/ocr2keeper/evm21/encoder_test.go index b5280ba4368..ee7f7934046 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/encoder_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/encoder_test.go @@ -1,16 +1,27 @@ package evm import ( + "fmt" "math/big" + "strings" "testing" - "github.com/pkg/errors" + "github.com/ethereum/go-ethereum/accounts/abi" ocr2keepers "github.com/smartcontractkit/ocr2keepers/pkg" "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" + iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" ) func TestEVMAutomationEncoder21(t *testing.T) { - encoder := EVMAutomationEncoder21{} + keepersABI, err := abi.JSON(strings.NewReader(iregistry21.IKeeperRegistryMasterABI)) + assert.Nil(t, err) + utilsABI, err := abi.JSON(strings.NewReader(automation_utils_2_1.AutomationUtilsABI)) + assert.Nil(t, err) + encoder := EVMAutomationEncoder21{ + packer: NewEvmRegistryPackerV2_1(keepersABI, utilsABI), + } t.Run("encoding an empty list of upkeep results returns a nil byte array", func(t *testing.T) { b, err := encoder.EncodeReport([]ocr2keepers.UpkeepResult{}) @@ -24,198 +35,76 @@ func TestEVMAutomationEncoder21(t *testing.T) { assert.Equal(t, b, []byte(nil)) }) - // t.Run("successfully encodes a single upkeep result", func(t *testing.T) { - // upkeepResult := EVMAutomationUpkeepResult21{ - // Block: 1, - // ID: big.NewInt(10), - // Eligible: true, - // GasUsed: big.NewInt(100), - // PerformData: []byte("data"), - // FastGasWei: big.NewInt(100), - // LinkNative: big.NewInt(100), - // CheckBlockNumber: 1, - // CheckBlockHash: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, - // ExecuteGas: 10, - // } - // b, err := encoder.EncodeReport([]ocr2keepers.UpkeepResult{upkeepResult}) - // assert.Nil(t, err) - // assert.Len(t, b, 416) - - // t.Run("successfully decodes a report with a single upkeep result", func(t *testing.T) { - // upkeeps, err := encoder.DecodeReport(b) - // assert.Nil(t, err) - // assert.Len(t, upkeeps, 1) - - // upkeep := upkeeps[0].(EVMAutomationUpkeepResult21) - - // // some fields aren't populated by the decode so we compare field-by-field for those that are populated - // assert.Equal(t, upkeep.Block, upkeepResult.Block) - // assert.Equal(t, upkeep.ID, upkeepResult.ID) - // assert.Equal(t, upkeep.Eligible, upkeepResult.Eligible) - // assert.Equal(t, upkeep.PerformData, upkeepResult.PerformData) - // assert.Equal(t, upkeep.FastGasWei, upkeepResult.FastGasWei) - // assert.Equal(t, upkeep.LinkNative, upkeepResult.LinkNative) - // assert.Equal(t, upkeep.CheckBlockNumber, upkeepResult.CheckBlockNumber) - // assert.Equal(t, upkeep.CheckBlockHash, upkeepResult.CheckBlockHash) - // }) - - // t.Run("an error is returned when unpacking into a map fails", func(t *testing.T) { - // oldUnpackIntoMapFn := unpackIntoMapFn - // unpackIntoMapFn = func(v map[string]interface{}, data []byte) error { - // return errors.New("failed to unpack into map") - // } - // defer func() { - // unpackIntoMapFn = oldUnpackIntoMapFn - // }() - - // upkeeps, err := encoder.DecodeReport(b) - // assert.Error(t, err, "failed to unpack into map") - // assert.Len(t, upkeeps, 0) - // }) - - // t.Run("an error is returned when an expected key is missing from the map", func(t *testing.T) { - // oldMKeys := mKeys - // mKeys = []string{"fastGasWei", "linkNative", "upkeepIds", "wrappedPerformDatas", "thisKeyWontExist"} - // defer func() { - // mKeys = oldMKeys - // }() - - // upkeeps, err := encoder.DecodeReport(b) - // assert.Error(t, err, "decoding error") - // assert.Len(t, upkeeps, 0) - // }) - - // t.Run("an error is returned when the third element of the map is not a slice of big.Int", func(t *testing.T) { - // oldMKeys := mKeys - // mKeys = []string{"fastGasWei", "linkNative", "wrappedPerformDatas", "upkeepIds"} - // defer func() { - // mKeys = oldMKeys - // }() - - // upkeeps, err := encoder.DecodeReport(b) - // assert.Error(t, err, "upkeep ids of incorrect type in report") - // assert.Len(t, upkeeps, 0) - // }) - - // t.Run("an error is returned when the fourth element of the map is not a struct of perform data", func(t *testing.T) { - // oldMKeys := mKeys - // mKeys = []string{"fastGasWei", "linkNative", "upkeepIds", "upkeepIds"} - // defer func() { - // mKeys = oldMKeys - // }() - - // upkeeps, err := encoder.DecodeReport(b) - // assert.Error(t, err, "performs of incorrect structure in report") - // assert.Len(t, upkeeps, 0) - // }) - - // t.Run("an error is returned when the upkeep ids and performDatas are of different lengths", func(t *testing.T) { - // oldUnpackIntoMapFn := unpackIntoMapFn - // unpackIntoMapFn = func(v map[string]interface{}, data []byte) error { - // v["fastGasWei"] = 1 - // v["linkNative"] = 2 - // v["upkeepIds"] = []*big.Int{big.NewInt(123), big.NewInt(456)} - // v["wrappedPerformDatas"] = []struct { - // CheckBlockNumber uint32 `json:"checkBlockNumber"` - // CheckBlockhash [32]byte `json:"checkBlockhash"` - // PerformData []byte `json:"performData"` - // }{ - // { - // CheckBlockNumber: 1, - // CheckBlockhash: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, - // PerformData: []byte{}, - // }, - // } - // return nil - // } - // defer func() { - // unpackIntoMapFn = oldUnpackIntoMapFn - // }() - - // upkeeps, err := encoder.DecodeReport(b) - // assert.Error(t, err, "upkeep ids and performs should have matching length") - // assert.Len(t, upkeeps, 0) - // }) - - // t.Run("an error is returned when the first element of the map is not a big int", func(t *testing.T) { - // oldMKeys := mKeys - // mKeys = []string{"upkeepIds", "linkNative", "upkeepIds", "wrappedPerformDatas"} - // defer func() { - // mKeys = oldMKeys - // }() - - // upkeeps, err := encoder.DecodeReport(b) - // assert.Error(t, err, "fast gas as wrong type") - // assert.Len(t, upkeeps, 0) - // }) - - // t.Run("an error is returned when the second element of the map is not a big int", func(t *testing.T) { - // oldMKeys := mKeys - // mKeys = []string{"fastGasWei", "upkeepIds", "upkeepIds", "wrappedPerformDatas"} - // defer func() { - // mKeys = oldMKeys - // }() - - // upkeeps, err := encoder.DecodeReport(b) - // assert.Error(t, err, "link native as wrong type") - // assert.Len(t, upkeeps, 0) - // }) - // }) - - // t.Run("successfully encodes multiple upkeep results", func(t *testing.T) { - // upkeepResult0 := EVMAutomationUpkeepResult21{ - // Block: 1, - // ID: big.NewInt(10), - // Eligible: true, - // GasUsed: big.NewInt(100), - // PerformData: []byte("data0"), - // FastGasWei: big.NewInt(100), - // LinkNative: big.NewInt(100), - // CheckBlockNumber: 1, - // CheckBlockHash: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, - // ExecuteGas: 10, - // } - // upkeepResult1 := EVMAutomationUpkeepResult21{ - // Block: 1, - // ID: big.NewInt(10), - // Eligible: true, - // GasUsed: big.NewInt(200), - // PerformData: []byte("data1"), - // FastGasWei: big.NewInt(200), - // LinkNative: big.NewInt(200), - // CheckBlockNumber: 2, - // CheckBlockHash: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, - // ExecuteGas: 20, - // } - // b, err := encoder.EncodeReport([]ocr2keepers.UpkeepResult{upkeepResult0, upkeepResult1}) - // assert.Nil(t, err) - // assert.Len(t, b, 640) - // }) - - t.Run("an error is returned when pack fails", func(t *testing.T) { - oldPackFn := packFn - packFn = func(args ...interface{}) ([]byte, error) { - return nil, errors.New("pack failed") - } - defer func() { - packFn = oldPackFn - }() - - upkeepResult0 := EVMAutomationUpkeepResult21{ + t.Run("successfully encodes and decodes a single upkeep result", func(t *testing.T) { + upkeepResult := EVMAutomationUpkeepResult21{ Block: 1, ID: big.NewInt(10), Eligible: true, GasUsed: big.NewInt(100), - PerformData: []byte("data0"), + PerformData: []byte("data"), FastGasWei: big.NewInt(100), LinkNative: big.NewInt(100), CheckBlockNumber: 1, CheckBlockHash: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, ExecuteGas: 10, } - b, err := encoder.EncodeReport([]ocr2keepers.UpkeepResult{upkeepResult0}) - assert.Errorf(t, err, "pack failed: failed to pack report data") - assert.Len(t, b, 0) + b, err := encoder.EncodeReport([]ocr2keepers.UpkeepResult{upkeepResult}) + assert.Nil(t, err) + assert.Len(t, b, 640) + + upkeeps, err := encoder.DecodeReport(b) + assert.Nil(t, err) + assert.Len(t, upkeeps, 1) + + upkeep := upkeeps[0].(EVMAutomationUpkeepResult21) + // some fields aren't populated by the decode so we compare field-by-field for those that are populated + assert.Equal(t, upkeep.Block, upkeepResult.Block) + assert.Equal(t, upkeep.ID, upkeepResult.ID) + assert.Equal(t, upkeep.Eligible, upkeepResult.Eligible) + assert.Equal(t, upkeep.PerformData, upkeepResult.PerformData) + assert.Equal(t, upkeep.FastGasWei, upkeepResult.FastGasWei) + assert.Equal(t, upkeep.LinkNative, upkeepResult.LinkNative) + assert.Equal(t, upkeep.CheckBlockNumber, upkeepResult.CheckBlockNumber) + assert.Equal(t, upkeep.CheckBlockHash, upkeepResult.CheckBlockHash) }) + t.Run("successfully encodes and decodes multiple upkeep results", func(t *testing.T) { + n := 5 + results := make([]ocr2keepers.UpkeepResult, n) + for i := 0; i < n; i++ { + block := uint32(i + 1) + results[i] = EVMAutomationUpkeepResult21{ + Block: block, + ID: big.NewInt(int64(block) * 10), + Eligible: true, + GasUsed: big.NewInt(100), + PerformData: []byte(fmt.Sprintf("data-%d", i)), + FastGasWei: big.NewInt(100), + LinkNative: big.NewInt(100), + CheckBlockNumber: block, + CheckBlockHash: [32]byte{uint8(block), 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + ExecuteGas: 10, + } + } + + b, err := encoder.EncodeReport(results) + assert.Nil(t, err) + assert.Len(t, b, 1792) + + decoded, err := encoder.DecodeReport(b) + assert.Nil(t, err) + assert.Len(t, decoded, len(results)) + for i, dec := range decoded { + result := dec.(EVMAutomationUpkeepResult21) + expected := results[i].(EVMAutomationUpkeepResult21) + assert.Equal(t, result.Block, expected.Block) + assert.Equal(t, result.ID, expected.ID) + assert.Equal(t, result.Eligible, expected.Eligible) + assert.Equal(t, result.PerformData, expected.PerformData) + assert.Equal(t, result.FastGasWei, expected.FastGasWei) + assert.Equal(t, result.LinkNative, expected.LinkNative) + assert.Equal(t, result.CheckBlockNumber, expected.CheckBlockNumber) + assert.Equal(t, result.CheckBlockHash, expected.CheckBlockHash) + } + }) } diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/feed_lookup_test.go b/core/services/ocr2/plugins/ocr2keeper/evm21/feed_lookup_test.go index 3d33672e479..8b9f256eead 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/feed_lookup_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/feed_lookup_test.go @@ -27,6 +27,7 @@ import ( evmClientMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/feed_lookup_compatible_interface" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -39,6 +40,8 @@ func setupEVMRegistry(t *testing.T) *EvmRegistry { addr := common.HexToAddress("0x6cA639822c6C241Fa9A7A6b5032F6F7F1C513CAD") keeperRegistryABI, err := abi.JSON(strings.NewReader(i_keeper_registry_master_wrapper_2_1.IKeeperRegistryMasterABI)) require.Nil(t, err, "need registry abi") + utilsABI, err := abi.JSON(strings.NewReader(automation_utils_2_1.AutomationUtilsABI)) + require.Nil(t, err, "need utils abi") feedLookupCompatibleABI, err := abi.JSON(strings.NewReader(feed_lookup_compatible_interface.FeedLookupCompatibleInterfaceABI)) require.Nil(t, err, "need mercury abi") var headTracker httypes.HeadTracker @@ -62,7 +65,7 @@ func setupEVMRegistry(t *testing.T) *EvmRegistry { registry: mockRegistry, abi: keeperRegistryABI, active: make(map[string]activeUpkeep), - packer: NewEvmRegistryPackerV2_1(keeperRegistryABI), + packer: NewEvmRegistryPackerV2_1(keeperRegistryABI, utilsABI), headFunc: func(ocr2keepers.BlockKey) {}, chLog: make(chan logpoller.Log, 1000), mercury: &MercuryConfig{ diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/log_provider.go b/core/services/ocr2/plugins/ocr2keeper/evm21/log_provider.go index 6ea6d6a17e9..3afefd12670 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/log_provider.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/log_provider.go @@ -16,6 +16,7 @@ import ( evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" @@ -64,6 +65,10 @@ func NewLogProvider( if err != nil { return nil, fmt.Errorf("%w: %s", ErrABINotParsable, err) } + utilsABI, err := abi.JSON(strings.NewReader(automation_utils_2_1.AutomationUtilsABI)) + if err != nil { + return nil, fmt.Errorf("%w: %s", ErrABINotParsable, err) + } // Add log filters for the log poller so that it can poll and find the logs that // we need. @@ -88,7 +93,7 @@ func NewLogProvider( lookbackBlocks: lookbackBlocks, registry: contract, client: client, - packer: NewEvmRegistryPackerV2_1(keeperABI), + packer: NewEvmRegistryPackerV2_1(keeperABI, utilsABI), txCheckBlockCache: pluginutils.NewCache[string](time.Hour), cacheCleaner: pluginutils.NewIntervalCacheCleaner[string](time.Minute), }, nil diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/abi.go b/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/abi.go index 415bec70f26..86caebd9bc7 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/abi.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/abi.go @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_log_automation" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" ) type LogDataPacker interface { @@ -17,8 +17,8 @@ type logEventsPacker struct { abi abi.ABI } -func NewLogEventsPacker(logDataABI abi.ABI) *logEventsPacker { - return &logEventsPacker{abi: logDataABI} +func NewLogEventsPacker(utilsABI abi.ABI) *logEventsPacker { + return &logEventsPacker{abi: utilsABI} } func (p *logEventsPacker) PackLogData(log logpoller.Log) ([]byte, error) { @@ -26,7 +26,7 @@ func (p *logEventsPacker) PackLogData(log logpoller.Log) ([]byte, error) { for _, topic := range log.GetTopics() { topics = append(topics, topic) } - b, err := p.abi.Pack("checkLog", &i_log_automation.Log{ + b, err := p.abi.Pack("_log", &automation_utils_2_1.Log{ Index: big.NewInt(log.LogIndex), TxIndex: big.NewInt(0), // TODO TxHash: log.TxHash, diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/integration_test.go b/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/integration_test.go index 7bc0cea7dfe..c62bc4c8801 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/integration_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider/integration_test.go @@ -27,7 +27,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_log_automation" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_upkeep_counter_wrapper" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" @@ -319,7 +319,7 @@ func setupLogProvider(t *testing.T, db *sqlx.DB, backend *backends.SimulatedBack lp := logpoller.NewLogPoller(lorm, ethClient, pollerLggr, 100*time.Millisecond, 1, 2, 2, 1000) lggr := logger.TestLogger(t) - logDataABI, err := abi.JSON(strings.NewReader(i_log_automation.ILogAutomationABI)) + logDataABI, err := abi.JSON(strings.NewReader(automation_utils_2_1.AutomationUtilsABI)) require.NoError(t, err) logProvider := logprovider.New(lggr, lp, logprovider.NewLogEventsPacker(logDataABI), opts) diff --git a/core/services/ocr2/plugins/ocr2keeper/evm21/registry.go b/core/services/ocr2/plugins/ocr2keeper/evm21/registry.go index 9a6b886758b..fd285e8c1a5 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evm21/registry.go +++ b/core/services/ocr2/plugins/ocr2keeper/evm21/registry.go @@ -25,9 +25,9 @@ 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/gethwrappers/generated" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/automation_utils_2_1" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/feed_lookup_compatible_interface" iregistry21 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_keeper_registry_master_wrapper_2_1" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/i_log_automation" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/models" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evm21/logprovider" @@ -88,12 +88,12 @@ func NewEVMRegistryService(addr common.Address, client evm.Chain, mc *models.Mer if err != nil { return nil, fmt.Errorf("%w: %s", ErrABINotParsable, err) } - packer := NewEvmRegistryPackerV2_1(keeperRegistryABI) - logDataABI, err := abi.JSON(strings.NewReader(i_log_automation.ILogAutomationABI)) + utilsABI, err := abi.JSON(strings.NewReader(automation_utils_2_1.AutomationUtilsABI)) if err != nil { return nil, fmt.Errorf("%w: %s", ErrABINotParsable, err) } - logPacker := logprovider.NewLogEventsPacker(logDataABI) + packer := NewEvmRegistryPackerV2_1(keeperRegistryABI, utilsABI) + logPacker := logprovider.NewLogEventsPacker(utilsABI) registry, err := iregistry21.NewIKeeperRegistryMaster(addr, client.Client()) if err != nil {