From fc7a5087b5596cd20c9fef1524292822320e7c1a Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Tue, 2 Jan 2024 22:59:02 +0200 Subject: [PATCH 01/16] feat: multichain ocr3 contract transmitter --- .../relay/evm/contract_transmitter_ocr3.go | 110 ++++++++++++++++++ .../relay/evm/multichain_transmitter_ocr3.go | 56 +++++++++ 2 files changed, 166 insertions(+) create mode 100644 core/services/relay/evm/contract_transmitter_ocr3.go create mode 100644 core/services/relay/evm/multichain_transmitter_ocr3.go diff --git a/core/services/relay/evm/contract_transmitter_ocr3.go b/core/services/relay/evm/contract_transmitter_ocr3.go new file mode 100644 index 00000000000..38f1cf5a82a --- /dev/null +++ b/core/services/relay/evm/contract_transmitter_ocr3.go @@ -0,0 +1,110 @@ +package evm + +import ( + "context" + "encoding/binary" + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +var _ ocr3types.ContractTransmitter[any] = &contractTransmitterOCR3[any]{} + +type contractTransmitterOCR3[RI any] struct { + contractAddress gethcommon.Address + contractABI abi.ABI + transmitter Transmitter + transmittedEventSig gethcommon.Hash + lp logpoller.LogPoller + lggr logger.Logger + reportToEvmTxMeta ReportToEthMetadata +} + +func NewOCR3ContractTransmitter[RI any]( + address gethcommon.Address, + contractABI abi.ABI, + transmitter Transmitter, + lp logpoller.LogPoller, + lggr logger.Logger, + reportToEvmTxMeta ReportToEthMetadata, +) (*contractTransmitterOCR3[RI], error) { + transmitted, ok := contractABI.Events["Transmitted"] + if !ok { + return nil, fmt.Errorf("abi missing Transmitted event") + } + + err := lp.RegisterFilter(logpoller.Filter{ + Name: transmitterFilterName(address), + EventSigs: []gethcommon.Hash{transmitted.ID}, + Addresses: []gethcommon.Address{address}, + }) + if err != nil { + return nil, err + } + if reportToEvmTxMeta == nil { + reportToEvmTxMeta = reportToEvmTxMetaNoop + } + return &contractTransmitterOCR3[RI]{ + contractAddress: address, + contractABI: contractABI, + transmitter: transmitter, + transmittedEventSig: transmitted.ID, + lp: lp, + lggr: lggr, + reportToEvmTxMeta: reportToEvmTxMeta, + }, nil +} + +// FromAccount implements ocr3types.ContractTransmitter. +func (c *contractTransmitterOCR3[RI]) FromAccount() (types.Account, error) { + return types.Account(c.transmitter.FromAddress().Hex()), nil +} + +// Transmit implements ocr3types.ContractTransmitter. +func (c *contractTransmitterOCR3[RI]) Transmit(ctx context.Context, configDigest types.ConfigDigest, seqNum uint64, rwi ocr3types.ReportWithInfo[RI], sigs []types.AttributedOnchainSignature) error { + var rs [][32]byte + var ss [][32]byte + var vs [32]byte + if len(sigs) > 32 { + return errors.New("too many signatures, maximum is 32") + } + for i, as := range sigs { + r, s, v, err := evmutil.SplitSignature(as.Signature) + if err != nil { + panic("eventTransmit(ev): error in SplitSignature") + } + rs = append(rs, r) + ss = append(ss, s) + vs[i] = v + } + + // report ctx for OCR3 consists of the following + // reportContext[0]: ConfigDigest + // reportContext[1]: 24 byte padding, 8 byte sequence number + // reportContext[2]: unused + var rawReportCtx [3][32]byte + copy(rawReportCtx[0][:], configDigest[:]) + binary.BigEndian.PutUint64(rawReportCtx[1][24:], seqNum) + + txMeta, err := c.reportToEvmTxMeta(rwi.Report) + if err != nil { + c.lggr.Warnw("failed to generate tx metadata for report", "err", err) + } + + c.lggr.Debugw("Transmitting report", "report", hex.EncodeToString(rwi.Report), "rawReportCtx", rawReportCtx, "contractAddress", c.contractAddress, "txMeta", txMeta) + + payload, err := c.contractABI.Pack("transmit", rawReportCtx, []byte(rwi.Report), rs, ss, vs) + if err != nil { + return errors.Wrapf(err, "abi.Pack failed with args: (%v, %s, %v, %v, %v)", rawReportCtx, hex.EncodeToString(rwi.Report), rs, ss, vs) + } + + return errors.Wrap(c.transmitter.CreateEthTransaction(ctx, c.contractAddress, payload, txMeta), "failed to send Eth transaction") +} diff --git a/core/services/relay/evm/multichain_transmitter_ocr3.go b/core/services/relay/evm/multichain_transmitter_ocr3.go new file mode 100644 index 00000000000..ae8a7d1f407 --- /dev/null +++ b/core/services/relay/evm/multichain_transmitter_ocr3.go @@ -0,0 +1,56 @@ +package evm + +import ( + "context" + "fmt" + "strings" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +type MultichainMeta interface { + GetDestinationChainID() string +} + +type multichainTransmitterOCR3[RI MultichainMeta] struct { + transmitters map[string]ocr3types.ContractTransmitter[RI] + lp logpoller.LogPoller + lggr logger.Logger +} + +func NewMultichainTransmitterOCR3[RI MultichainMeta]( + transmitters map[string]ocr3types.ContractTransmitter[RI], + lp logpoller.LogPoller, + lggr logger.Logger, +) (*multichainTransmitterOCR3[RI], error) { + return &multichainTransmitterOCR3[RI]{ + transmitters: transmitters, + lp: lp, + lggr: lggr, + }, nil +} + +// FromAccount implements ocr3types.ContractTransmitter. +func (m *multichainTransmitterOCR3[RI]) FromAccount() (types.Account, error) { + var accounts []string + for _, t := range m.transmitters { + account, err := t.FromAccount() + if err != nil { + return "", err + } + accounts = append(accounts, string(account)) + } + return types.Account(strings.Join(accounts, ",")), nil +} + +// Transmit implements ocr3types.ContractTransmitter. +func (m *multichainTransmitterOCR3[RI]) Transmit(ctx context.Context, configDigest types.ConfigDigest, seqNr uint64, rwi ocr3types.ReportWithInfo[RI], sigs []types.AttributedOnchainSignature) error { + transmitter, ok := m.transmitters[rwi.Info.GetDestinationChainID()] + if !ok { + return fmt.Errorf("no transmitter for chain %s", rwi.Info.GetDestinationChainID()) + } + return transmitter.Transmit(ctx, configDigest, seqNr, rwi, sigs) +} From 42d4b2170c9d4b8135adb2698631649e09881089 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Wed, 3 Jan 2024 15:35:55 +0200 Subject: [PATCH 02/16] make FromAccount() return a hex-ified address --- core/services/relay/evm/multichain_transmitter_ocr3.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/services/relay/evm/multichain_transmitter_ocr3.go b/core/services/relay/evm/multichain_transmitter_ocr3.go index ae8a7d1f407..b5925610c6d 100644 --- a/core/services/relay/evm/multichain_transmitter_ocr3.go +++ b/core/services/relay/evm/multichain_transmitter_ocr3.go @@ -5,6 +5,9 @@ import ( "fmt" "strings" + "github.com/ethereum/go-ethereum/common/hexutil" + gethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" @@ -43,7 +46,11 @@ func (m *multichainTransmitterOCR3[RI]) FromAccount() (types.Account, error) { } accounts = append(accounts, string(account)) } - return types.Account(strings.Join(accounts, ",")), nil + // return a deterministic account based on the list of accounts + // it has to be 20 bytes long because the transmitter is of type address in + // the solidity contracts. + h := gethcrypto.Keccak256Hash([]byte(strings.Join(accounts, ","))) + return types.Account(hexutil.Encode(h[:20])), nil } // Transmit implements ocr3types.ContractTransmitter. From 323b36b3126a09071bae39232901452adde00c12 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Wed, 3 Jan 2024 15:37:58 +0200 Subject: [PATCH 03/16] address PR comments --- core/services/relay/evm/contract_transmitter_ocr3.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/services/relay/evm/contract_transmitter_ocr3.go b/core/services/relay/evm/contract_transmitter_ocr3.go index 38f1cf5a82a..114dec83af7 100644 --- a/core/services/relay/evm/contract_transmitter_ocr3.go +++ b/core/services/relay/evm/contract_transmitter_ocr3.go @@ -47,7 +47,7 @@ func NewOCR3ContractTransmitter[RI any]( Addresses: []gethcommon.Address{address}, }) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to register filter: %w", err) } if reportToEvmTxMeta == nil { reportToEvmTxMeta = reportToEvmTxMetaNoop @@ -103,7 +103,7 @@ func (c *contractTransmitterOCR3[RI]) Transmit(ctx context.Context, configDigest payload, err := c.contractABI.Pack("transmit", rawReportCtx, []byte(rwi.Report), rs, ss, vs) if err != nil { - return errors.Wrapf(err, "abi.Pack failed with args: (%v, %s, %v, %v, %v)", rawReportCtx, hex.EncodeToString(rwi.Report), rs, ss, vs) + return fmt.Errorf("%w: abi.Pack failed with args: (%+v, %s, %+v, %+v, %+v)", err, rawReportCtx, hex.EncodeToString(rwi.Report), rs, ss, vs) } return errors.Wrap(c.transmitter.CreateEthTransaction(ctx, c.contractAddress, payload, txMeta), "failed to send Eth transaction") From 8a2d9bc1582f9d41a06df4821946a9ca64d816a9 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Thu, 4 Jan 2024 14:29:00 +0200 Subject: [PATCH 04/16] Revert "make FromAccount() return a hex-ified address" This reverts commit 42d4b2170c9d4b8135adb2698631649e09881089. --- core/services/relay/evm/multichain_transmitter_ocr3.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/core/services/relay/evm/multichain_transmitter_ocr3.go b/core/services/relay/evm/multichain_transmitter_ocr3.go index b5925610c6d..ae8a7d1f407 100644 --- a/core/services/relay/evm/multichain_transmitter_ocr3.go +++ b/core/services/relay/evm/multichain_transmitter_ocr3.go @@ -5,9 +5,6 @@ import ( "fmt" "strings" - "github.com/ethereum/go-ethereum/common/hexutil" - gethcrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" @@ -46,11 +43,7 @@ func (m *multichainTransmitterOCR3[RI]) FromAccount() (types.Account, error) { } accounts = append(accounts, string(account)) } - // return a deterministic account based on the list of accounts - // it has to be 20 bytes long because the transmitter is of type address in - // the solidity contracts. - h := gethcrypto.Keccak256Hash([]byte(strings.Join(accounts, ","))) - return types.Account(hexutil.Encode(h[:20])), nil + return types.Account(strings.Join(accounts, ",")), nil } // Transmit implements ocr3types.ContractTransmitter. From 8dfb0e98ae246f087ec32b28997a22cb802c901a Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Thu, 4 Jan 2024 16:23:18 +0200 Subject: [PATCH 05/16] add ocr3 base --- .../scripts/native_solc_compile_all_shared | 1 + contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol | 17 + .../src/v0.8/shared/ocr3/OCR3Abstract.sol | 122 +++ contracts/src/v0.8/shared/ocr3/OCR3Base.sol | 288 ++++++ .../shared/generated/no_op_ocr3/no_op_ocr3.go | 962 ++++++++++++++++++ ...rapper-dependency-versions-do-not-edit.txt | 1 + core/gethwrappers/shared/go_generate.go | 1 + 7 files changed, 1392 insertions(+) create mode 100644 contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol create mode 100644 contracts/src/v0.8/shared/ocr3/OCR3Abstract.sol create mode 100644 contracts/src/v0.8/shared/ocr3/OCR3Base.sol create mode 100644 core/gethwrappers/shared/generated/no_op_ocr3/no_op_ocr3.go diff --git a/contracts/scripts/native_solc_compile_all_shared b/contracts/scripts/native_solc_compile_all_shared index 9178237b8a5..2133e56b362 100755 --- a/contracts/scripts/native_solc_compile_all_shared +++ b/contracts/scripts/native_solc_compile_all_shared @@ -32,3 +32,4 @@ compileContract shared/token/ERC677/BurnMintERC677.sol compileContract shared/token/ERC677/LinkToken.sol compileContract shared/mocks/WERC20Mock.sol compileContract vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol +compileContract shared/ocr3/NoOpOCR3.sol diff --git a/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol b/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol new file mode 100644 index 00000000000..4620e87a901 --- /dev/null +++ b/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {OCR3Base} from "./OCR3Base.sol"; + +// NoOpOCR3 is a mock implementation of the OCR3Base contract that does nothing +// This is so that we can generate gethwrappers for the contract and use the OCR3 ABI in +// Go code. +contract NoOpOCR3 is OCR3Base { + string public constant override typeAndVersion = "NoOpOCR3 1.0.0"; + + constructor() OCR3Base() {} + + function _report(bytes calldata report, uint64 sequenceNumber) internal override { + // do nothing + } +} diff --git a/contracts/src/v0.8/shared/ocr3/OCR3Abstract.sol b/contracts/src/v0.8/shared/ocr3/OCR3Abstract.sol new file mode 100644 index 00000000000..9a0df98b897 --- /dev/null +++ b/contracts/src/v0.8/shared/ocr3/OCR3Abstract.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; + +abstract contract OCR3Abstract is ITypeAndVersion { + // Maximum number of oracles the offchain reporting protocol is designed for + uint256 internal constant MAX_NUM_ORACLES = 31; + + /// @notice triggers a new run of the offchain reporting protocol + /// @param previousConfigBlockNumber block in which the previous config was set, to simplify historic analysis + /// @param configDigest configDigest of this configuration + /// @param configCount ordinal number of this config setting among all config settings over the life of this contract + /// @param signers ith element is address ith oracle uses to sign a report + /// @param transmitters ith element is address ith oracle uses to transmit a report via the transmit method + /// @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly + /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) + /// @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter + /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract + event ConfigSet( + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + address[] signers, + address[] transmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig + ); + + /// @notice sets offchain reporting protocol configuration incl. participating oracles + /// @param signers addresses with which oracles sign the reports + /// @param transmitters addresses oracles use to transmit the reports + /// @param f number of faulty oracles the system can tolerate + /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) + /// @param offchainConfigVersion version number for offchainEncoding schema + /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract + function setOCR3Config( + address[] memory signers, + address[] memory transmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external virtual; + + /// @notice information about current offchain reporting protocol configuration + /// @return configCount ordinal number of current config, out of all configs applied to this contract so far + /// @return blockNumber block at which this config was set + /// @return configDigest domain-separation tag for current config (see _configDigestFromConfigData) + function latestConfigDetails() + external + view + virtual + returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest); + + function _configDigestFromConfigData( + uint256 chainId, + address contractAddress, + uint64 configCount, + address[] memory signers, + address[] memory transmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) internal pure returns (bytes32) { + uint256 h = uint256( + keccak256( + abi.encode( + chainId, + contractAddress, + configCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 prefix = 0x0001 << (256 - 16); // 0x000100..00 + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } + + /// @notice optionally emitted to indicate the latest configDigest and sequence number + /// for which a report was successfully transmitted. Alternatively, the contract may + /// use latestConfigDigestAndEpoch with scanLogs set to false. + event Transmitted(bytes32 configDigest, uint64 sequenceNumber); + + /// @notice optionally returns the latest configDigest and sequence number for which + /// a report was successfully transmitted. Alternatively, the contract may return + /// scanLogs set to true and use Transmitted events to provide this information + /// to offchain watchers. + /// @return scanLogs indicates whether to rely on the configDigest and sequence number + /// returned or whether to scan logs for the Transmitted event instead. + /// @return configDigest + /// @return sequenceNumber + function latestConfigDigestAndEpoch() + external + view + virtual + returns (bool scanLogs, bytes32 configDigest, uint64 sequenceNumber); + + /// @notice transmit is called to post a new report to the contract + /// @param report serialized report, which the signatures are signing. + /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries + /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries + /// @param rawVs ith element is the the V component of the ith signature + function transmit( + // NOTE: If these parameters are changed, expectedMsgDataLength and/or + // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly + bytes32[3] calldata reportContext, + bytes calldata report, + bytes32[] calldata rs, + bytes32[] calldata ss, + bytes32 rawVs // signatures + ) external virtual; +} diff --git a/contracts/src/v0.8/shared/ocr3/OCR3Base.sol b/contracts/src/v0.8/shared/ocr3/OCR3Base.sol new file mode 100644 index 00000000000..db2226e1dfa --- /dev/null +++ b/contracts/src/v0.8/shared/ocr3/OCR3Base.sol @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import {OwnerIsCreator} from "../../shared/access/OwnerIsCreator.sol"; +import {OCR3Abstract} from "./OCR3Abstract.sol"; + +/// @notice Onchain verification of reports from the offchain reporting protocol +/// @dev For details on its operation, see the offchain reporting protocol design +/// doc, which refers to this contract as simply the "contract". +abstract contract OCR3Base is OwnerIsCreator, OCR3Abstract { + error InvalidConfig(string message); + error WrongMessageLength(uint256 expected, uint256 actual); + error ConfigDigestMismatch(bytes32 expected, bytes32 actual); + error ForkedChain(uint256 expected, uint256 actual); + error WrongNumberOfSignatures(); + error SignaturesOutOfRegistration(); + error UnauthorizedTransmitter(); + error UnauthorizedSigner(); + error NonUniqueSignatures(); + error OracleCannotBeZeroAddress(); + error NonIncreasingSequenceNumber(uint64 sequenceNumber, uint64 latestSequenceNumber); + + // Packing these fields used on the hot path in a ConfigInfo variable reduces the + // retrieval of all of them to a minimum number of SLOADs. + struct ConfigInfo { + bytes32 latestConfigDigest; + uint8 f; + uint8 n; + } + + // Used for s_oracles[a].role, where a is an address, to track the purpose + // of the address, or to indicate that the address is unset. + enum Role { + // No oracle role has been set for address a + Unset, + // Signing address for the s_oracles[a].index'th oracle. I.e., report + // signatures from this oracle should ecrecover back to address a. + Signer, + // Transmission address for the s_oracles[a].index'th oracle. I.e., if a + // report is received by OCR3Aggregator.transmit in which msg.sender is + // a, it is attributed to the s_oracles[a].index'th oracle. + Transmitter + } + + struct Oracle { + uint8 index; // Index of oracle in s_signers/s_transmitters + Role role; // Role of the address which mapped to this struct + } + + // The current config + ConfigInfo internal s_configInfo; + + // incremented each time a new config is posted. This count is incorporated + // into the config digest, to prevent replay attacks. + uint32 internal s_configCount; + // makes it easier for offchain systems to extract config from logs. + uint32 internal s_latestConfigBlockNumber; + + uint64 internal s_latestSequenceNumber; + + // signer OR transmitter address + mapping(address signerOrTransmitter => Oracle oracle) internal s_oracles; + + // s_signers contains the signing address of each oracle + address[] internal s_signers; + + // s_transmitters contains the transmission address of each oracle, + // i.e. the address the oracle actually sends transactions to the contract from + address[] internal s_transmitters; + + // The constant-length components of the msg.data sent to transmit. + // See the "If we wanted to call sam" example on for example reasoning + // https://solidity.readthedocs.io/en/v0.7.2/abi-spec.html + uint16 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT = + 4 + // function selector + 32 * + 3 + // 3 words containing reportContext + 32 + // word containing start location of abiencoded report value + 32 + // word containing location start of abiencoded rs value + 32 + // word containing start location of abiencoded ss value + 32 + // rawVs value + 32 + // word containing length of report + 32 + // word containing length rs + 32; // word containing length of ss + + uint256 internal immutable i_chainID; + + constructor() { + i_chainID = block.chainid; + } + + // Reverts transaction if config args are invalid + modifier checkConfigValid( + uint256 numSigners, + uint256 numTransmitters, + uint256 f + ) { + if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); + if (f == 0) revert InvalidConfig("f must be positive"); + if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); + if (numSigners <= 3 * f) revert InvalidConfig("faulty-oracle f too high"); + _; + } + + /// @notice sets offchain reporting protocol configuration incl. participating oracles + /// @param signers addresses with which oracles sign the reports + /// @param transmitters addresses oracles use to transmit the reports + /// @param f number of faulty oracles the system can tolerate + /// @param onchainConfig encoded on-chain contract configuration + /// @param offchainConfigVersion version number for offchainEncoding schema + /// @param offchainConfig encoded off-chain oracle configuration + function setOCR3Config( + address[] memory signers, + address[] memory transmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external override checkConfigValid(signers.length, transmitters.length, f) onlyOwner { + _beforeSetConfig(onchainConfig); + uint256 oldSignerLength = s_signers.length; + for (uint256 i = 0; i < oldSignerLength; ++i) { + delete s_oracles[s_signers[i]]; + delete s_oracles[s_transmitters[i]]; + } + + uint256 newSignersLength = signers.length; + for (uint256 i = 0; i < newSignersLength; ++i) { + // add new signer/transmitter addresses + address signer = signers[i]; + if (s_oracles[signer].role != Role.Unset) revert InvalidConfig("repeated signer address"); + if (signer == address(0)) revert OracleCannotBeZeroAddress(); + s_oracles[signer] = Oracle(uint8(i), Role.Signer); + + address transmitter = transmitters[i]; + if (s_oracles[transmitter].role != Role.Unset) revert InvalidConfig("repeated transmitter address"); + if (transmitter == address(0)) revert OracleCannotBeZeroAddress(); + s_oracles[transmitter] = Oracle(uint8(i), Role.Transmitter); + } + + s_signers = signers; + s_transmitters = transmitters; + + s_configInfo.f = f; + s_configInfo.n = uint8(newSignersLength); + s_configInfo.latestConfigDigest = _configDigestFromConfigData( + block.chainid, + address(this), + ++s_configCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ); + + uint32 previousConfigBlockNumber = s_latestConfigBlockNumber; + s_latestConfigBlockNumber = uint32(block.number); + + emit ConfigSet( + previousConfigBlockNumber, + s_configInfo.latestConfigDigest, + s_configCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ); + } + + /// @dev Hook that is run from setOCR3Config() right after validating configuration. + /// Empty by default, please provide an implementation in a child contract if you need additional configuration processing + function _beforeSetConfig(bytes memory _onchainConfig) internal virtual {} + + /// @return list of addresses permitted to transmit reports to this contract + /// @dev The list will match the order used to specify the transmitter during setConfig + function getTransmitters() external view returns (address[] memory) { + return s_transmitters; + } + + /// @notice transmit is called to post a new report to the contract + /// @param report serialized report, which the signatures are signing. + /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries + /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries + /// @param rawVs ith element is the the V component of the ith signature + function transmit( + // NOTE: If these parameters are changed, expectedMsgDataLength and/or + // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly + bytes32[3] calldata reportContext, + bytes calldata report, + bytes32[] calldata rs, + bytes32[] calldata ss, + bytes32 rawVs // signatures + ) external override { + uint64 sequenceNumber = uint64(uint256(reportContext[1])); + if (sequenceNumber <= s_latestSequenceNumber) { + revert NonIncreasingSequenceNumber(sequenceNumber, s_latestSequenceNumber); + } + + // Scoping this reduces stack pressure and gas usage + { + _report(report, sequenceNumber); + } + + s_latestSequenceNumber = sequenceNumber; + // reportContext consists of: + // reportContext[0]: ConfigDigest + // reportContext[1]: 24 byte padding, 8 byte sequence number + bytes32 configDigest = reportContext[0]; + ConfigInfo memory configInfo = s_configInfo; + + if (configInfo.latestConfigDigest != configDigest) { + revert ConfigDigestMismatch(configInfo.latestConfigDigest, configDigest); + } + // If the cached chainID at time of deployment doesn't match the current chainID, we reject all signed reports. + // This avoids a (rare) scenario where chain A forks into chain A and A', A' still has configDigest + // calculated from chain A and so OCR reports will be valid on both forks. + if (i_chainID != block.chainid) revert ForkedChain(i_chainID, block.chainid); + + emit Transmitted(configDigest, sequenceNumber); + + if (rs.length != configInfo.f + 1) revert WrongNumberOfSignatures(); + if (rs.length != ss.length) revert SignaturesOutOfRegistration(); + + // Scoping this reduces stack pressure and gas usage + { + Oracle memory transmitter = s_oracles[msg.sender]; + // Check that sender is authorized to report + if (!(transmitter.role == Role.Transmitter && msg.sender == s_transmitters[transmitter.index])) + revert UnauthorizedTransmitter(); + } + // Scoping this reduces stack pressure and gas usage + { + uint256 expectedDataLength = uint256(TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT) + + report.length + // one byte pure entry in _report + rs.length * + 32 + // 32 bytes per entry in _rs + ss.length * + 32; // 32 bytes per entry in _ss) + if (msg.data.length != expectedDataLength) revert WrongMessageLength(expectedDataLength, msg.data.length); + } + + // Verify signatures attached to report + bytes32 h = keccak256(abi.encodePacked(keccak256(report), reportContext)); + bool[MAX_NUM_ORACLES] memory signed; + + uint256 numberOfSignatures = rs.length; + for (uint256 i = 0; i < numberOfSignatures; ++i) { + // Safe from ECDSA malleability here since we check for duplicate signers. + address signer = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); + // Since we disallow address(0) as a valid signer address, it can + // never have a signer role. + Oracle memory oracle = s_oracles[signer]; + if (oracle.role != Role.Signer) revert UnauthorizedSigner(); + if (signed[oracle.index]) revert NonUniqueSignatures(); + signed[oracle.index] = true; + } + } + + /// @notice information about current offchain reporting protocol configuration + /// @return configCount ordinal number of current config, out of all configs applied to this contract so far + /// @return blockNumber block at which this config was set + /// @return configDigest domain-separation tag for current config (see _configDigestFromConfigData) + function latestConfigDetails() + external + view + override + returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest) + { + return (s_configCount, s_latestConfigBlockNumber, s_configInfo.latestConfigDigest); + } + + /// @inheritdoc OCR3Abstract + function latestConfigDigestAndEpoch() + external + view + virtual + override + returns (bool scanLogs, bytes32 configDigest, uint64 sequenceNumber) + { + return (true, s_configInfo.latestConfigDigest, s_latestSequenceNumber); + } + + function _report(bytes calldata report, uint64 sequenceNumber) internal virtual; +} diff --git a/core/gethwrappers/shared/generated/no_op_ocr3/no_op_ocr3.go b/core/gethwrappers/shared/generated/no_op_ocr3/no_op_ocr3.go new file mode 100644 index 00000000000..507afeb60ef --- /dev/null +++ b/core/gethwrappers/shared/generated/no_op_ocr3/no_op_ocr3.go @@ -0,0 +1,962 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package no_op_ocr3 + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +var NoOpOCR3MetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"expected\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"actual\",\"type\":\"bytes32\"}],\"name\":\"ConfigDigestMismatch\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actual\",\"type\":\"uint256\"}],\"name\":\"ForkedChain\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"latestSequenceNumber\",\"type\":\"uint64\"}],\"name\":\"NonIncreasingSequenceNumber\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NonUniqueSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OracleCannotBeZeroAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SignaturesOutOfRegistration\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedTransmitter\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actual\",\"type\":\"uint256\"}],\"name\":\"WrongMessageLength\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongNumberOfSignatures\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTransmitters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"scanLogs\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setOCR3Config\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"transmit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b038481169190911790915581161561009757610097816100a3565b5050466080525061014c565b336001600160a01b038216036100fb5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b608051611c386200016f60003960008181610c9f0152610ceb0152611c386000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c806381ff704811610076578063afcb95d71161005b578063afcb95d714610184578063b1dc65a4146101bd578063f2fde38b146101d057600080fd5b806381ff70481461012c5780638da5cb5b1461015c57600080fd5b8063181f5a77146100a8578063666cab8d146100fa5780636a11ee901461010f57806379ba509714610124575b600080fd5b6100e46040518060400160405280600e81526020017f4e6f4f704f43523320312e302e3000000000000000000000000000000000000081525081565b6040516100f1919061152f565b60405180910390f35b6101026101e3565b6040516100f1919061159a565b61012261011d366004611792565b610252565b005b610122610a6a565b6004546002546040805163ffffffff808516825264010000000090940490931660208401528201526060016100f1565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100f1565b600254600454604080516001815260208101939093526801000000000000000090910467ffffffffffffffff16908201526060016100f1565b6101226101cb3660046118ab565b610b67565b6101226101de366004611990565b6111d6565b6060600780548060200260200160405190810160405280929190818152602001828054801561024857602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff16815260019091019060200180831161021d575b5050505050905090565b855185518560ff16601f8311156102ca576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e6572730000000000000000000000000000000060448201526064015b60405180910390fd5b80600003610334576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f736974697665000000000000000000000000000060448201526064016102c1565b8183146103c2576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e0000000000000000000000000000000000000000000000000000000060648201526084016102c1565b6103cd8160036119da565b8311610435576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f2068696768000000000000000060448201526064016102c1565b61043d6111ea565b60065460005b81811015610539576005600060068381548110610462576104626119f7565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000169055600780546005929190849081106104d2576104d26119f7565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000016905561053281611a26565b9050610443565b50895160005b818110156109125760008c828151811061055b5761055b6119f7565b602002602001015190506000600281111561057857610578611a5e565b73ffffffffffffffffffffffffffffffffffffffff8216600090815260056020526040902054610100900460ff1660028111156105b7576105b7611a5e565b1461061e576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265706561746564207369676e6572206164647265737300000000000000000060448201526064016102c1565b73ffffffffffffffffffffffffffffffffffffffff811661066b576040517fd6c62c9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805180820190915260ff83168152602081016001905273ffffffffffffffffffffffffffffffffffffffff821660009081526005602090815260409091208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561071b5761071b611a5e565b021790555090505060008c8381518110610737576107376119f7565b602002602001015190506000600281111561075457610754611a5e565b73ffffffffffffffffffffffffffffffffffffffff8216600090815260056020526040902054610100900460ff16600281111561079357610793611a5e565b146107fa576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f7265706561746564207472616e736d697474657220616464726573730000000060448201526064016102c1565b73ffffffffffffffffffffffffffffffffffffffff8116610847576040517fd6c62c9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805180820190915260ff84168152602081016002905273ffffffffffffffffffffffffffffffffffffffff821660009081526005602090815260409091208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000016176101008360028111156108f7576108f7611a5e565b021790555090505050508061090b90611a26565b905061053f565b508a516109269060069060208e019061140d565b50895161093a9060079060208d019061140d565b506003805460ff838116610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000909216908c1617179055600480546109c09146913091906000906109929063ffffffff16611a8d565b91906101000a81548163ffffffff021916908363ffffffff160217905563ffffffff168e8e8e8e8e8e61126d565b600260000181905550600060048054906101000a900463ffffffff169050436004806101000a81548163ffffffff021916908363ffffffff1602179055507f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e0581600260000154600460009054906101000a900463ffffffff168f8f8f8f8f8f604051610a5499989796959493929190611ab0565b60405180910390a1505050505050505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610aeb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064016102c1565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60045460208901359067ffffffffffffffff68010000000000000000909104811690821611610bea57600480546040517f6e376b6600000000000000000000000000000000000000000000000000000000815267ffffffffffffffff808516938201939093526801000000000000000090910490911660248201526044016102c1565b600480547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff166801000000000000000067ffffffffffffffff8416021790556040805160608101825260025480825260035460ff808216602085015261010090910416928201929092528a35918214610c9c5780516040517f93df584c0000000000000000000000000000000000000000000000000000000081526004810191909152602481018390526044016102c1565b467f000000000000000000000000000000000000000000000000000000000000000014610d1d576040517f0f01ce850000000000000000000000000000000000000000000000000000000081527f000000000000000000000000000000000000000000000000000000000000000060048201524660248201526044016102c1565b6040805183815267ffffffffffffffff851660208201527fe893c2681d327421d89e1cb54fbe64645b4dcea668d6826130b62cf4c6eefea2910160405180910390a16020810151610d6f906001611b46565b60ff168714610daa576040517f71253a2500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b868514610de3576040517fa75d88af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3360009081526005602090815260408083208151808301909252805460ff80821684529293919291840191610100909104166002811115610e2657610e26611a5e565b6002811115610e3757610e37611a5e565b9052509050600281602001516002811115610e5457610e54611a5e565b148015610e9b57506007816000015160ff1681548110610e7657610e766119f7565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1633145b610ed1576040517fda0f08e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506000610edf8660206119da565b610eea8960206119da565b610ef68c610144611b5f565b610f009190611b5f565b610f0a9190611b5f565b9050368114610f4e576040517f8e1192e1000000000000000000000000000000000000000000000000000000008152600481018290523660248201526044016102c1565b5060008a8a604051610f61929190611b72565b604051908190038120610f78918e90602001611b82565b604051602081830303815290604052805190602001209050610f98611497565b8860005b818110156111c55760006001858a8460208110610fbb57610fbb6119f7565b610fc891901a601b611b46565b8f8f86818110610fda57610fda6119f7565b905060200201358e8e87818110610ff357610ff36119f7565b9050602002013560405160008152602001604052604051611030949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611052573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff8116600090815260056020908152848220848601909552845460ff80821686529397509195509293928401916101009091041660028111156110d5576110d5611a5e565b60028111156110e6576110e6611a5e565b905250905060018160200151600281111561110357611103611a5e565b1461113a576040517fca31867a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8051859060ff16601f8110611151576111516119f7565b60200201511561118d576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600185826000015160ff16601f81106111a8576111a86119f7565b91151560209092020152506111be905081611a26565b9050610f9c565b505050505050505050505050505050565b6111de6111ea565b6111e781611318565b50565b60005473ffffffffffffffffffffffffffffffffffffffff16331461126b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016102c1565b565b6000808a8a8a8a8a8a8a8a8a60405160200161129199989796959493929190611b96565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603611397576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016102c1565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b828054828255906000526020600020908101928215611487579160200282015b8281111561148757825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90911617825560209092019160019091019061142d565b506114939291506114b6565b5090565b604051806103e00160405280601f906020820280368337509192915050565b5b8082111561149357600081556001016114b7565b6000815180845260005b818110156114f1576020818501810151868301820152016114d5565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60208152600061154260208301846114cb565b9392505050565b600081518084526020808501945080840160005b8381101561158f57815173ffffffffffffffffffffffffffffffffffffffff168752958201959082019060010161155d565b509495945050505050565b6020815260006115426020830184611549565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611623576116236115ad565b604052919050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461164f57600080fd5b919050565b600082601f83011261166557600080fd5b8135602067ffffffffffffffff821115611681576116816115ad565b8160051b6116908282016115dc565b92835284810182019282810190878511156116aa57600080fd5b83870192505b848310156116d0576116c18361162b565b825291830191908301906116b0565b979650505050505050565b803560ff8116811461164f57600080fd5b600082601f8301126116fd57600080fd5b813567ffffffffffffffff811115611717576117176115ad565b61174860207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016115dc565b81815284602083860101111561175d57600080fd5b816020850160208301376000918101602001919091529392505050565b803567ffffffffffffffff8116811461164f57600080fd5b60008060008060008060c087890312156117ab57600080fd5b863567ffffffffffffffff808211156117c357600080fd5b6117cf8a838b01611654565b975060208901359150808211156117e557600080fd5b6117f18a838b01611654565b96506117ff60408a016116db565b9550606089013591508082111561181557600080fd5b6118218a838b016116ec565b945061182f60808a0161177a565b935060a089013591508082111561184557600080fd5b5061185289828a016116ec565b9150509295509295509295565b60008083601f84011261187157600080fd5b50813567ffffffffffffffff81111561188957600080fd5b6020830191508360208260051b85010111156118a457600080fd5b9250929050565b60008060008060008060008060e0898b0312156118c757600080fd5b606089018a8111156118d857600080fd5b8998503567ffffffffffffffff808211156118f257600080fd5b818b0191508b601f83011261190657600080fd5b81358181111561191557600080fd5b8c602082850101111561192757600080fd5b6020830199508098505060808b013591508082111561194557600080fd5b6119518c838d0161185f565b909750955060a08b013591508082111561196a57600080fd5b506119778b828c0161185f565b999c989b50969995989497949560c00135949350505050565b6000602082840312156119a257600080fd5b6115428261162b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820281158282048414176119f1576119f16119ab565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611a5757611a576119ab565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600063ffffffff808316818103611aa657611aa66119ab565b6001019392505050565b600061012063ffffffff808d1684528b6020850152808b16604085015250806060840152611ae08184018a611549565b90508281036080840152611af48189611549565b905060ff871660a084015282810360c0840152611b1181876114cb565b905067ffffffffffffffff851660e0840152828103610100840152611b3681856114cb565b9c9b505050505050505050505050565b60ff81811683821601908111156119f1576119f16119ab565b808201808211156119f1576119f16119ab565b8183823760009101908152919050565b828152606082602083013760800192915050565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152611bdd8285018b611549565b91508382036080850152611bf1828a611549565b915060ff881660a085015283820360c0850152611c0e82886114cb565b90861660e08501528381036101008501529050611b3681856114cb56fea164736f6c6343000813000a", +} + +var NoOpOCR3ABI = NoOpOCR3MetaData.ABI + +var NoOpOCR3Bin = NoOpOCR3MetaData.Bin + +func DeployNoOpOCR3(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *NoOpOCR3, error) { + parsed, err := NoOpOCR3MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(NoOpOCR3Bin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &NoOpOCR3{address: address, abi: *parsed, NoOpOCR3Caller: NoOpOCR3Caller{contract: contract}, NoOpOCR3Transactor: NoOpOCR3Transactor{contract: contract}, NoOpOCR3Filterer: NoOpOCR3Filterer{contract: contract}}, nil +} + +type NoOpOCR3 struct { + address common.Address + abi abi.ABI + NoOpOCR3Caller + NoOpOCR3Transactor + NoOpOCR3Filterer +} + +type NoOpOCR3Caller struct { + contract *bind.BoundContract +} + +type NoOpOCR3Transactor struct { + contract *bind.BoundContract +} + +type NoOpOCR3Filterer struct { + contract *bind.BoundContract +} + +type NoOpOCR3Session struct { + Contract *NoOpOCR3 + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type NoOpOCR3CallerSession struct { + Contract *NoOpOCR3Caller + CallOpts bind.CallOpts +} + +type NoOpOCR3TransactorSession struct { + Contract *NoOpOCR3Transactor + TransactOpts bind.TransactOpts +} + +type NoOpOCR3Raw struct { + Contract *NoOpOCR3 +} + +type NoOpOCR3CallerRaw struct { + Contract *NoOpOCR3Caller +} + +type NoOpOCR3TransactorRaw struct { + Contract *NoOpOCR3Transactor +} + +func NewNoOpOCR3(address common.Address, backend bind.ContractBackend) (*NoOpOCR3, error) { + abi, err := abi.JSON(strings.NewReader(NoOpOCR3ABI)) + if err != nil { + return nil, err + } + contract, err := bindNoOpOCR3(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &NoOpOCR3{address: address, abi: abi, NoOpOCR3Caller: NoOpOCR3Caller{contract: contract}, NoOpOCR3Transactor: NoOpOCR3Transactor{contract: contract}, NoOpOCR3Filterer: NoOpOCR3Filterer{contract: contract}}, nil +} + +func NewNoOpOCR3Caller(address common.Address, caller bind.ContractCaller) (*NoOpOCR3Caller, error) { + contract, err := bindNoOpOCR3(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &NoOpOCR3Caller{contract: contract}, nil +} + +func NewNoOpOCR3Transactor(address common.Address, transactor bind.ContractTransactor) (*NoOpOCR3Transactor, error) { + contract, err := bindNoOpOCR3(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &NoOpOCR3Transactor{contract: contract}, nil +} + +func NewNoOpOCR3Filterer(address common.Address, filterer bind.ContractFilterer) (*NoOpOCR3Filterer, error) { + contract, err := bindNoOpOCR3(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &NoOpOCR3Filterer{contract: contract}, nil +} + +func bindNoOpOCR3(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := NoOpOCR3MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_NoOpOCR3 *NoOpOCR3Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _NoOpOCR3.Contract.NoOpOCR3Caller.contract.Call(opts, result, method, params...) +} + +func (_NoOpOCR3 *NoOpOCR3Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NoOpOCR3.Contract.NoOpOCR3Transactor.contract.Transfer(opts) +} + +func (_NoOpOCR3 *NoOpOCR3Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _NoOpOCR3.Contract.NoOpOCR3Transactor.contract.Transact(opts, method, params...) +} + +func (_NoOpOCR3 *NoOpOCR3CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _NoOpOCR3.Contract.contract.Call(opts, result, method, params...) +} + +func (_NoOpOCR3 *NoOpOCR3TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NoOpOCR3.Contract.contract.Transfer(opts) +} + +func (_NoOpOCR3 *NoOpOCR3TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _NoOpOCR3.Contract.contract.Transact(opts, method, params...) +} + +func (_NoOpOCR3 *NoOpOCR3Caller) GetTransmitters(opts *bind.CallOpts) ([]common.Address, error) { + var out []interface{} + err := _NoOpOCR3.contract.Call(opts, &out, "getTransmitters") + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + +} + +func (_NoOpOCR3 *NoOpOCR3Session) GetTransmitters() ([]common.Address, error) { + return _NoOpOCR3.Contract.GetTransmitters(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3CallerSession) GetTransmitters() ([]common.Address, error) { + return _NoOpOCR3.Contract.GetTransmitters(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3Caller) LatestConfigDetails(opts *bind.CallOpts) (LatestConfigDetails, + + error) { + var out []interface{} + err := _NoOpOCR3.contract.Call(opts, &out, "latestConfigDetails") + + outstruct := new(LatestConfigDetails) + if err != nil { + return *outstruct, err + } + + outstruct.ConfigCount = *abi.ConvertType(out[0], new(uint32)).(*uint32) + outstruct.BlockNumber = *abi.ConvertType(out[1], new(uint32)).(*uint32) + outstruct.ConfigDigest = *abi.ConvertType(out[2], new([32]byte)).(*[32]byte) + + return *outstruct, err + +} + +func (_NoOpOCR3 *NoOpOCR3Session) LatestConfigDetails() (LatestConfigDetails, + + error) { + return _NoOpOCR3.Contract.LatestConfigDetails(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3CallerSession) LatestConfigDetails() (LatestConfigDetails, + + error) { + return _NoOpOCR3.Contract.LatestConfigDetails(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3Caller) LatestConfigDigestAndEpoch(opts *bind.CallOpts) (LatestConfigDigestAndEpoch, + + error) { + var out []interface{} + err := _NoOpOCR3.contract.Call(opts, &out, "latestConfigDigestAndEpoch") + + outstruct := new(LatestConfigDigestAndEpoch) + if err != nil { + return *outstruct, err + } + + outstruct.ScanLogs = *abi.ConvertType(out[0], new(bool)).(*bool) + outstruct.ConfigDigest = *abi.ConvertType(out[1], new([32]byte)).(*[32]byte) + outstruct.SequenceNumber = *abi.ConvertType(out[2], new(uint64)).(*uint64) + + return *outstruct, err + +} + +func (_NoOpOCR3 *NoOpOCR3Session) LatestConfigDigestAndEpoch() (LatestConfigDigestAndEpoch, + + error) { + return _NoOpOCR3.Contract.LatestConfigDigestAndEpoch(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3CallerSession) LatestConfigDigestAndEpoch() (LatestConfigDigestAndEpoch, + + error) { + return _NoOpOCR3.Contract.LatestConfigDigestAndEpoch(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3Caller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _NoOpOCR3.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_NoOpOCR3 *NoOpOCR3Session) Owner() (common.Address, error) { + return _NoOpOCR3.Contract.Owner(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3CallerSession) Owner() (common.Address, error) { + return _NoOpOCR3.Contract.Owner(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3Caller) TypeAndVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _NoOpOCR3.contract.Call(opts, &out, "typeAndVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_NoOpOCR3 *NoOpOCR3Session) TypeAndVersion() (string, error) { + return _NoOpOCR3.Contract.TypeAndVersion(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3CallerSession) TypeAndVersion() (string, error) { + return _NoOpOCR3.Contract.TypeAndVersion(&_NoOpOCR3.CallOpts) +} + +func (_NoOpOCR3 *NoOpOCR3Transactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NoOpOCR3.contract.Transact(opts, "acceptOwnership") +} + +func (_NoOpOCR3 *NoOpOCR3Session) AcceptOwnership() (*types.Transaction, error) { + return _NoOpOCR3.Contract.AcceptOwnership(&_NoOpOCR3.TransactOpts) +} + +func (_NoOpOCR3 *NoOpOCR3TransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _NoOpOCR3.Contract.AcceptOwnership(&_NoOpOCR3.TransactOpts) +} + +func (_NoOpOCR3 *NoOpOCR3Transactor) SetOCR3Config(opts *bind.TransactOpts, signers []common.Address, transmitters []common.Address, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _NoOpOCR3.contract.Transact(opts, "setOCR3Config", signers, transmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_NoOpOCR3 *NoOpOCR3Session) SetOCR3Config(signers []common.Address, transmitters []common.Address, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _NoOpOCR3.Contract.SetOCR3Config(&_NoOpOCR3.TransactOpts, signers, transmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_NoOpOCR3 *NoOpOCR3TransactorSession) SetOCR3Config(signers []common.Address, transmitters []common.Address, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) { + return _NoOpOCR3.Contract.SetOCR3Config(&_NoOpOCR3.TransactOpts, signers, transmitters, f, onchainConfig, offchainConfigVersion, offchainConfig) +} + +func (_NoOpOCR3 *NoOpOCR3Transactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _NoOpOCR3.contract.Transact(opts, "transferOwnership", to) +} + +func (_NoOpOCR3 *NoOpOCR3Session) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _NoOpOCR3.Contract.TransferOwnership(&_NoOpOCR3.TransactOpts, to) +} + +func (_NoOpOCR3 *NoOpOCR3TransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _NoOpOCR3.Contract.TransferOwnership(&_NoOpOCR3.TransactOpts, to) +} + +func (_NoOpOCR3 *NoOpOCR3Transactor) Transmit(opts *bind.TransactOpts, reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { + return _NoOpOCR3.contract.Transact(opts, "transmit", reportContext, report, rs, ss, rawVs) +} + +func (_NoOpOCR3 *NoOpOCR3Session) Transmit(reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { + return _NoOpOCR3.Contract.Transmit(&_NoOpOCR3.TransactOpts, reportContext, report, rs, ss, rawVs) +} + +func (_NoOpOCR3 *NoOpOCR3TransactorSession) Transmit(reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) { + return _NoOpOCR3.Contract.Transmit(&_NoOpOCR3.TransactOpts, reportContext, report, rs, ss, rawVs) +} + +type NoOpOCR3ConfigSetIterator struct { + Event *NoOpOCR3ConfigSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *NoOpOCR3ConfigSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(NoOpOCR3ConfigSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(NoOpOCR3ConfigSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *NoOpOCR3ConfigSetIterator) Error() error { + return it.fail +} + +func (it *NoOpOCR3ConfigSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type NoOpOCR3ConfigSet struct { + PreviousConfigBlockNumber uint32 + ConfigDigest [32]byte + ConfigCount uint64 + Signers []common.Address + Transmitters []common.Address + F uint8 + OnchainConfig []byte + OffchainConfigVersion uint64 + OffchainConfig []byte + Raw types.Log +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) FilterConfigSet(opts *bind.FilterOpts) (*NoOpOCR3ConfigSetIterator, error) { + + logs, sub, err := _NoOpOCR3.contract.FilterLogs(opts, "ConfigSet") + if err != nil { + return nil, err + } + return &NoOpOCR3ConfigSetIterator{contract: _NoOpOCR3.contract, event: "ConfigSet", logs: logs, sub: sub}, nil +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) WatchConfigSet(opts *bind.WatchOpts, sink chan<- *NoOpOCR3ConfigSet) (event.Subscription, error) { + + logs, sub, err := _NoOpOCR3.contract.WatchLogs(opts, "ConfigSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(NoOpOCR3ConfigSet) + if err := _NoOpOCR3.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) ParseConfigSet(log types.Log) (*NoOpOCR3ConfigSet, error) { + event := new(NoOpOCR3ConfigSet) + if err := _NoOpOCR3.contract.UnpackLog(event, "ConfigSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type NoOpOCR3OwnershipTransferRequestedIterator struct { + Event *NoOpOCR3OwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *NoOpOCR3OwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(NoOpOCR3OwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(NoOpOCR3OwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *NoOpOCR3OwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *NoOpOCR3OwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type NoOpOCR3OwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*NoOpOCR3OwnershipTransferRequestedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _NoOpOCR3.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &NoOpOCR3OwnershipTransferRequestedIterator{contract: _NoOpOCR3.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *NoOpOCR3OwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _NoOpOCR3.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(NoOpOCR3OwnershipTransferRequested) + if err := _NoOpOCR3.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) ParseOwnershipTransferRequested(log types.Log) (*NoOpOCR3OwnershipTransferRequested, error) { + event := new(NoOpOCR3OwnershipTransferRequested) + if err := _NoOpOCR3.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type NoOpOCR3OwnershipTransferredIterator struct { + Event *NoOpOCR3OwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *NoOpOCR3OwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(NoOpOCR3OwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(NoOpOCR3OwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *NoOpOCR3OwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *NoOpOCR3OwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type NoOpOCR3OwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*NoOpOCR3OwnershipTransferredIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _NoOpOCR3.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &NoOpOCR3OwnershipTransferredIterator{contract: _NoOpOCR3.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *NoOpOCR3OwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _NoOpOCR3.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(NoOpOCR3OwnershipTransferred) + if err := _NoOpOCR3.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) ParseOwnershipTransferred(log types.Log) (*NoOpOCR3OwnershipTransferred, error) { + event := new(NoOpOCR3OwnershipTransferred) + if err := _NoOpOCR3.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type NoOpOCR3TransmittedIterator struct { + Event *NoOpOCR3Transmitted + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *NoOpOCR3TransmittedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(NoOpOCR3Transmitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(NoOpOCR3Transmitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *NoOpOCR3TransmittedIterator) Error() error { + return it.fail +} + +func (it *NoOpOCR3TransmittedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type NoOpOCR3Transmitted struct { + ConfigDigest [32]byte + SequenceNumber uint64 + Raw types.Log +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) FilterTransmitted(opts *bind.FilterOpts) (*NoOpOCR3TransmittedIterator, error) { + + logs, sub, err := _NoOpOCR3.contract.FilterLogs(opts, "Transmitted") + if err != nil { + return nil, err + } + return &NoOpOCR3TransmittedIterator{contract: _NoOpOCR3.contract, event: "Transmitted", logs: logs, sub: sub}, nil +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) WatchTransmitted(opts *bind.WatchOpts, sink chan<- *NoOpOCR3Transmitted) (event.Subscription, error) { + + logs, sub, err := _NoOpOCR3.contract.WatchLogs(opts, "Transmitted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(NoOpOCR3Transmitted) + if err := _NoOpOCR3.contract.UnpackLog(event, "Transmitted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_NoOpOCR3 *NoOpOCR3Filterer) ParseTransmitted(log types.Log) (*NoOpOCR3Transmitted, error) { + event := new(NoOpOCR3Transmitted) + if err := _NoOpOCR3.contract.UnpackLog(event, "Transmitted", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type LatestConfigDetails struct { + ConfigCount uint32 + BlockNumber uint32 + ConfigDigest [32]byte +} +type LatestConfigDigestAndEpoch struct { + ScanLogs bool + ConfigDigest [32]byte + SequenceNumber uint64 +} + +func (_NoOpOCR3 *NoOpOCR3) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _NoOpOCR3.abi.Events["ConfigSet"].ID: + return _NoOpOCR3.ParseConfigSet(log) + case _NoOpOCR3.abi.Events["OwnershipTransferRequested"].ID: + return _NoOpOCR3.ParseOwnershipTransferRequested(log) + case _NoOpOCR3.abi.Events["OwnershipTransferred"].ID: + return _NoOpOCR3.ParseOwnershipTransferred(log) + case _NoOpOCR3.abi.Events["Transmitted"].ID: + return _NoOpOCR3.ParseTransmitted(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (NoOpOCR3ConfigSet) Topic() common.Hash { + return common.HexToHash("0x1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05") +} + +func (NoOpOCR3OwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (NoOpOCR3OwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (NoOpOCR3Transmitted) Topic() common.Hash { + return common.HexToHash("0xe893c2681d327421d89e1cb54fbe64645b4dcea668d6826130b62cf4c6eefea2") +} + +func (_NoOpOCR3 *NoOpOCR3) Address() common.Address { + return _NoOpOCR3.address +} + +type NoOpOCR3Interface interface { + GetTransmitters(opts *bind.CallOpts) ([]common.Address, error) + + LatestConfigDetails(opts *bind.CallOpts) (LatestConfigDetails, + + error) + + LatestConfigDigestAndEpoch(opts *bind.CallOpts) (LatestConfigDigestAndEpoch, + + error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + TypeAndVersion(opts *bind.CallOpts) (string, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + SetOCR3Config(opts *bind.TransactOpts, signers []common.Address, transmitters []common.Address, f uint8, onchainConfig []byte, offchainConfigVersion uint64, offchainConfig []byte) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + Transmit(opts *bind.TransactOpts, reportContext [3][32]byte, report []byte, rs [][32]byte, ss [][32]byte, rawVs [32]byte) (*types.Transaction, error) + + FilterConfigSet(opts *bind.FilterOpts) (*NoOpOCR3ConfigSetIterator, error) + + WatchConfigSet(opts *bind.WatchOpts, sink chan<- *NoOpOCR3ConfigSet) (event.Subscription, error) + + ParseConfigSet(log types.Log) (*NoOpOCR3ConfigSet, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*NoOpOCR3OwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *NoOpOCR3OwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*NoOpOCR3OwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*NoOpOCR3OwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *NoOpOCR3OwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*NoOpOCR3OwnershipTransferred, error) + + FilterTransmitted(opts *bind.FilterOpts) (*NoOpOCR3TransmittedIterator, error) + + WatchTransmitted(opts *bind.WatchOpts, sink chan<- *NoOpOCR3Transmitted) (event.Subscription, error) + + ParseTransmitted(log types.Log) (*NoOpOCR3Transmitted, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt index af907ce85eb..dbe4ce23fac 100644 --- a/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/shared/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -2,4 +2,5 @@ GETH_VERSION: 1.12.0 burn_mint_erc677: ../../../contracts/solc/v0.8.19/BurnMintERC677/BurnMintERC677.abi ../../../contracts/solc/v0.8.19/BurnMintERC677/BurnMintERC677.bin 405c9016171e614b17e10588653ef8d33dcea21dd569c3fddc596a46fcff68a3 erc20: ../../../contracts/solc/v0.8.19/ERC20/ERC20.abi ../../../contracts/solc/v0.8.19/ERC20/ERC20.bin 5b1a93d9b24f250e49a730c96335a8113c3f7010365cba578f313b483001d4fc link_token: ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.bin c0ef9b507103aae541ebc31d87d051c2764ba9d843076b30ec505d37cdfffaba +no_op_ocr3: ../../../contracts/solc/v0.8.19/NoOpOCR3/NoOpOCR3.abi ../../../contracts/solc/v0.8.19/NoOpOCR3/NoOpOCR3.bin 3a5ef6a7a6502339d7965b44debb5536ee9b6d2181985e52d905307315c0a853 werc20_mock: ../../../contracts/solc/v0.8.19/WERC20Mock/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock/WERC20Mock.bin ff2ca3928b2aa9c412c892cb8226c4d754c73eeb291bb7481c32c48791b2aa94 diff --git a/core/gethwrappers/shared/go_generate.go b/core/gethwrappers/shared/go_generate.go index 6f3bead7d6b..61a794063bf 100644 --- a/core/gethwrappers/shared/go_generate.go +++ b/core/gethwrappers/shared/go_generate.go @@ -6,3 +6,4 @@ package gethwrappers //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.abi ../../../contracts/solc/v0.8.19/LinkToken/LinkToken.bin LinkToken link_token //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/ERC20/ERC20.abi ../../../contracts/solc/v0.8.19/ERC20/ERC20.bin ERC20 erc20 //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/WERC20Mock/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock/WERC20Mock.bin WERC20Mock werc20_mock +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.19/NoOpOCR3/NoOpOCR3.abi ../../../contracts/solc/v0.8.19/NoOpOCR3/NoOpOCR3.bin NoOpOCR3 no_op_ocr3 From 7f482a95c4de878e6a2a14c0311ab90ec23d7157 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Thu, 4 Jan 2024 16:23:33 +0200 Subject: [PATCH 06/16] add multichain config tracker --- .../contract_transmitter.go} | 19 +- core/services/relay/evm/ocr3/helpers.go | 109 ++++++++ .../evm/ocr3/multichain_config_tracker.go | 236 ++++++++++++++++++ .../multichain_transmitter.go} | 8 +- 4 files changed, 368 insertions(+), 4 deletions(-) rename core/services/relay/evm/{contract_transmitter_ocr3.go => ocr3/contract_transmitter.go} (86%) create mode 100644 core/services/relay/evm/ocr3/helpers.go create mode 100644 core/services/relay/evm/ocr3/multichain_config_tracker.go rename core/services/relay/evm/{multichain_transmitter_ocr3.go => ocr3/multichain_transmitter.go} (83%) diff --git a/core/services/relay/evm/contract_transmitter_ocr3.go b/core/services/relay/evm/ocr3/contract_transmitter.go similarity index 86% rename from core/services/relay/evm/contract_transmitter_ocr3.go rename to core/services/relay/evm/ocr3/contract_transmitter.go index 114dec83af7..9259b971831 100644 --- a/core/services/relay/evm/contract_transmitter_ocr3.go +++ b/core/services/relay/evm/ocr3/contract_transmitter.go @@ -1,4 +1,4 @@ -package evm +package ocr3 import ( "context" @@ -7,15 +7,32 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) +type Transmitter interface { + CreateEthTransaction(ctx context.Context, toAddress gethcommon.Address, payload []byte, txMeta *txmgr.TxMeta) error + FromAddress() gethcommon.Address +} + +type ReportToEthMetadata func([]byte) (*txmgr.TxMeta, error) + +func reportToEvmTxMetaNoop([]byte) (*txmgr.TxMeta, error) { + return nil, nil +} + +func transmitterFilterName(addr common.Address) string { + return logpoller.FilterName("OCR3 ContractTransmitter", addr.String()) +} + var _ ocr3types.ContractTransmitter[any] = &contractTransmitterOCR3[any]{} type contractTransmitterOCR3[RI any] struct { diff --git a/core/services/relay/evm/ocr3/helpers.go b/core/services/relay/evm/ocr3/helpers.go new file mode 100644 index 00000000000..3ad70dcc5d5 --- /dev/null +++ b/core/services/relay/evm/ocr3/helpers.go @@ -0,0 +1,109 @@ +package ocr3 + +import ( + "fmt" + "slices" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/no_op_ocr3" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +func configTrackerFilterName(id relay.ID, addr common.Address) string { + return logpoller.FilterName("OCR3 MultichainConfigTracker", id.String(), addr.String()) +} + +func unpackLogData(d []byte) (*no_op_ocr3.NoOpOCR3ConfigSet, error) { + unpacked := new(no_op_ocr3.NoOpOCR3ConfigSet) + err := defaultABI.UnpackIntoInterface(unpacked, "ConfigSet", d) + if err != nil { + return nil, err + } + return unpacked, nil +} + +func configFromLog(logData []byte) (ocrtypes.ContractConfig, error) { + unpacked, err := unpackLogData(logData) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + + var transmitAccounts []ocrtypes.Account + for _, addr := range unpacked.Transmitters { + transmitAccounts = append(transmitAccounts, ocrtypes.Account(addr.Hex())) + } + var signers []ocrtypes.OnchainPublicKey + for _, addr := range unpacked.Signers { + addr := addr + signers = append(signers, addr[:]) + } + + return ocrtypes.ContractConfig{ + ConfigDigest: unpacked.ConfigDigest, + ConfigCount: unpacked.ConfigCount, + Signers: signers, + Transmitters: transmitAccounts, + F: unpacked.F, + OnchainConfig: unpacked.OnchainConfig, + OffchainConfigVersion: unpacked.OffchainConfigVersion, + OffchainConfig: unpacked.OffchainConfig, + }, nil +} + +// TransmitterCombiner is a CombinerFn that combines all transmitter addresses +// for the same signer on many different chains into a single string. +func TransmitterCombiner(masterConfig ocrtypes.ContractConfig, followerConfigs []ocrtypes.ContractConfig) (ocrtypes.ContractConfig, error) { + toReturn := ocrtypes.ContractConfig{ + ConfigDigest: masterConfig.ConfigDigest, + ConfigCount: masterConfig.ConfigCount, + Signers: masterConfig.Signers, + // Transmitters: []ocrtypes.Account{}, // will be filled below + F: masterConfig.F, + OnchainConfig: masterConfig.OnchainConfig, + OffchainConfigVersion: masterConfig.OffchainConfigVersion, + OffchainConfig: masterConfig.OffchainConfig, + } + + // use the hex-ified onchain pub key as a key since []byte can't + // be used as a map key. + var combinedTransmitters []ocrtypes.Account + + for i, signer := range masterConfig.Signers { + transmitters := []string{string(masterConfig.Transmitters[i])} + for _, followerConfig := range followerConfigs { + // signer might be at a different index than master chain (but ideally shouldn't be) + signerIdx := slices.IndexFunc(followerConfig.Signers, func(opk ocrtypes.OnchainPublicKey) bool { + return hexutil.Encode(opk) == hexutil.Encode(signer) + }) + if signerIdx == -1 { + // signer not found, bad config + return ocrtypes.ContractConfig{}, fmt.Errorf("unable to find signer %x (oracle index %d) in follower config %+v", signer, i, followerConfig) + } + // signer and transmitter indexes match + transmitters = append(transmitters, string(followerConfig.Transmitters[signerIdx])) + } + combinedTransmitter := joinTransmitters(transmitters) + combinedTransmitters = append(combinedTransmitters, ocrtypes.Account(combinedTransmitter)) + } + + // sanity check + if len(combinedTransmitters) != len(masterConfig.Signers) { + return ocrtypes.ContractConfig{}, fmt.Errorf("unexpected length mismatch between combined transmitters (%d) and master config signers (%d)", len(combinedTransmitters), len(masterConfig.Signers)) + } + + toReturn.Transmitters = combinedTransmitters + return toReturn, nil +} + +// joinTransmitters is a helper that combines many transmitters into one +// Note that this is pulled out so that it can be used in the CombinerFn +// and the contract transmitter since the output of FromAccount() in the +// ContractTransmitter and the ContractConfig.Transmitters output for a +// particular signer must match in order for OCR3 to work. +func joinTransmitters(transmitters []string) string { + return strings.Join(transmitters, ",") +} diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker.go b/core/services/relay/evm/ocr3/multichain_config_tracker.go new file mode 100644 index 00000000000..abc2deafc87 --- /dev/null +++ b/core/services/relay/evm/ocr3/multichain_config_tracker.go @@ -0,0 +1,236 @@ +package ocr3 + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/services" + 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/shared/generated/no_op_ocr3" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pg" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +var ( + // See https://github.com/smartcontractkit/ccip/compare/ccip-develop...CCIP-1438-op-stack-bridge-adapter-l-1#diff-2fe14bb9d1ecbc62f43cef26daff5d1f86275f16e1296fc9827b934a518d3f4cR20 + ConfigSet common.Hash + + defaultABI abi.ABI + + _ ocrtypes.ContractConfigTracker = &multichainConfigTracker{} +) + +func init() { + var err error + tabi, err := no_op_ocr3.NoOpOCR3MetaData.GetAbi() + if err != nil { + panic(err) + } + defaultABI = *tabi + ConfigSet = defaultABI.Events["ConfigSet"].ID +} + +type CombinerFn func(masterConfig ocrtypes.ContractConfig, followerConfigs []ocrtypes.ContractConfig) (ocrtypes.ContractConfig, error) + +type multichainConfigTracker struct { + services.StateMachine + + // masterChain is the chain that contains the "master" OCR3 configuration + // contract. This is the chain that the config tracker will listen to for + // ConfigSet events. All other chains will have their config set in a similar + // way however will not contain offchain or onchain config, just signers and + // transmitters. These events will be read by the multichain config tracker + // and used to construct the final multi-chain config. + masterChain relay.ID + lggr logger.Logger + logPollers map[relay.ID]logpoller.LogPoller + clients map[relay.ID]evmclient.Client + contractAddresses map[relay.ID]common.Address + contracts map[relay.ID]no_op_ocr3.NoOpOCR3Interface + combiner CombinerFn +} + +func NewMultichainConfigTracker( + masterChain relay.ID, + lggr logger.Logger, + logPollers map[relay.ID]logpoller.LogPoller, + clients map[relay.ID]evmclient.Client, + contractAddresses map[relay.ID]common.Address, + combiner CombinerFn, +) (*multichainConfigTracker, error) { + // Ensure master chain is in the log pollers + if _, ok := logPollers[masterChain]; !ok { + return nil, fmt.Errorf("master chain %s not in log pollers", masterChain) + } + + // Ensure master chain is in the clients + if _, ok := clients[masterChain]; !ok { + return nil, fmt.Errorf("master chain %s not in clients", masterChain) + } + + // Ensure combiner is not nil + if combiner == nil { + return nil, fmt.Errorf("provide non-nil combiner") + } + + // Register filters on all log pollers + contracts := make(map[relay.ID]no_op_ocr3.NoOpOCR3Interface) + for id, lp := range logPollers { + fName := configTrackerFilterName(id, contractAddresses[id]) + err := lp.RegisterFilter(logpoller.Filter{ + Name: fName, + EventSigs: []common.Hash{ConfigSet}, + Addresses: []common.Address{contractAddresses[id]}, + }) + if err != nil { + return nil, err + } + wrapper, err := no_op_ocr3.NewNoOpOCR3(contractAddresses[id], clients[id]) + if err != nil { + return nil, err + } + contracts[id] = wrapper + } + return &multichainConfigTracker{ + lggr: lggr, + logPollers: logPollers, + clients: clients, + contractAddresses: contractAddresses, + contracts: contracts, + masterChain: masterChain, + }, nil +} + +func (m *multichainConfigTracker) Start() {} + +func (m *multichainConfigTracker) Close() error { + return nil +} + +// Notify noop method +func (m *multichainConfigTracker) Notify() <-chan struct{} { + return nil +} + +// Replay abstracts the logpoller.LogPoller Replay() implementation +func (m *multichainConfigTracker) Replay(ctx context.Context, id relay.ID, fromBlock int64) error { + return m.logPollers[id].Replay(ctx, fromBlock) +} + +// LatestBlockHeight implements types.ContractConfigTracker. +// Returns the block height of the master chain. +func (m *multichainConfigTracker) LatestBlockHeight(ctx context.Context) (blockHeight uint64, err error) { + latestBlock, err := m.logPollers[m.masterChain].LatestBlock(pg.WithParentCtx(ctx)) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + return 0, err + } + return uint64(latestBlock.BlockNumber), nil +} + +// LatestConfig implements types.ContractConfigTracker. +// LatestConfig fetches the config from the master chain and then fetches the +// remaining configurations from all the other chains. +func (m *multichainConfigTracker) LatestConfig(ctx context.Context, changedInBlock uint64) (ocrtypes.ContractConfig, error) { + lgs, err := m.logPollers[m.masterChain].Logs(int64(changedInBlock), int64(changedInBlock), ConfigSet, m.contractAddresses[m.masterChain], pg.WithParentCtx(ctx)) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + if len(lgs) == 0 { + // TODO: fallback to RPC call? + return ocrtypes.ContractConfig{}, fmt.Errorf("no logs found for config on contract %s (chain %s) at block %d", m.contractAddresses[m.masterChain].Hex(), m.clients[m.masterChain].ConfiguredChainID().String(), changedInBlock) + } + masterConfig, err := configFromLog(lgs[len(lgs)-1].Data) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + m.lggr.Infow("LatestConfig from master chain", "latestConfig", masterConfig) + + // check all other chains for their config + var followerConfigs []ocrtypes.ContractConfig + for id, lp := range m.logPollers { + if id == m.masterChain { + continue + } + + lgs, err := lp.Logs(int64(changedInBlock), int64(changedInBlock), ConfigSet, m.contractAddresses[id], pg.WithParentCtx(ctx)) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + + if len(lgs) == 0 { + return ocrtypes.ContractConfig{}, fmt.Errorf("no logs found for config on contract %s (chain %s) at block %d", m.contractAddresses[id].Hex(), m.clients[id].ConfiguredChainID().String(), changedInBlock) + } + + configSet, err := configFromLog(lgs[len(lgs)-1].Data) + if err != nil { + return ocrtypes.ContractConfig{}, err + } + followerConfigs = append(followerConfigs, configSet) + } + + // at this point we can combine the configs into a single one + combined, err := m.combiner(masterConfig, followerConfigs) + if err != nil { + return ocrtypes.ContractConfig{}, fmt.Errorf("error combining configs: %w", err) + } + + return combined, nil +} + +// LatestConfigDetails implements types.ContractConfigTracker. +func (m *multichainConfigTracker) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest ocrtypes.ConfigDigest, err error) { + latest, err := m.logPollers[m.masterChain].LatestLogByEventSigWithConfs(ConfigSet, m.contractAddresses[m.masterChain], 1, pg.WithParentCtx(ctx)) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + // TODO: try eth-call to get the config + return 0, ocrtypes.ConfigDigest{}, err + } + return 0, ocrtypes.ConfigDigest{}, err + } + masterConfig, err := configFromLog(latest.Data) + if err != nil { + return 0, ocrtypes.ConfigDigest{}, err + } + + // check all other chains for their config + var followerConfigs []ocrtypes.ContractConfig + for id, lp := range m.logPollers { + if id == m.masterChain { + continue + } + + latest, err := lp.LatestLogByEventSigWithConfs(ConfigSet, m.contractAddresses[id], 1, pg.WithParentCtx(ctx)) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + // TODO: try RPC call for config + return 0, ocrtypes.ConfigDigest{}, err + } + return 0, ocrtypes.ConfigDigest{}, err + } + + followerConfig, err := configFromLog(latest.Data) + if err != nil { + return 0, ocrtypes.ConfigDigest{}, err + } + + followerConfigs = append(followerConfigs, followerConfig) + } + + // at this point we can combine the configs into a single one + combined, err := m.combiner(masterConfig, followerConfigs) + if err != nil { + return 0, ocrtypes.ConfigDigest{}, err + } + + return uint64(latest.BlockNumber), masterConfig.ConfigDigest, nil +} diff --git a/core/services/relay/evm/multichain_transmitter_ocr3.go b/core/services/relay/evm/ocr3/multichain_transmitter.go similarity index 83% rename from core/services/relay/evm/multichain_transmitter_ocr3.go rename to core/services/relay/evm/ocr3/multichain_transmitter.go index ae8a7d1f407..26edf8ad359 100644 --- a/core/services/relay/evm/multichain_transmitter_ocr3.go +++ b/core/services/relay/evm/ocr3/multichain_transmitter.go @@ -1,9 +1,8 @@ -package evm +package ocr3 import ( "context" "fmt" - "strings" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -15,6 +14,9 @@ type MultichainMeta interface { GetDestinationChainID() string } +// multichainTransmitterOCR3 is a transmitter that can transmit to multiple chains. +// It uses the information in the MultichainMeta to determine which chain to transmit to. +// Note that this would only work with the appropriate multi-chain config tracker implementation. type multichainTransmitterOCR3[RI MultichainMeta] struct { transmitters map[string]ocr3types.ContractTransmitter[RI] lp logpoller.LogPoller @@ -43,7 +45,7 @@ func (m *multichainTransmitterOCR3[RI]) FromAccount() (types.Account, error) { } accounts = append(accounts, string(account)) } - return types.Account(strings.Join(accounts, ",")), nil + return types.Account(joinTransmitters(accounts)), nil } // Transmit implements ocr3types.ContractTransmitter. From 3f664f04549b77326758bea051b946fdfd968a20 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Thu, 4 Jan 2024 16:52:15 +0200 Subject: [PATCH 07/16] don't fetch follower configs in LatestConfigDetails LatestConfigDetails only needs to return the changedInBlock and the config digest, not the entire config contents. --- .../evm/ocr3/multichain_config_tracker.go | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker.go b/core/services/relay/evm/ocr3/multichain_config_tracker.go index abc2deafc87..3bf8a469605 100644 --- a/core/services/relay/evm/ocr3/multichain_config_tracker.go +++ b/core/services/relay/evm/ocr3/multichain_config_tracker.go @@ -202,35 +202,5 @@ func (m *multichainConfigTracker) LatestConfigDetails(ctx context.Context) (chan return 0, ocrtypes.ConfigDigest{}, err } - // check all other chains for their config - var followerConfigs []ocrtypes.ContractConfig - for id, lp := range m.logPollers { - if id == m.masterChain { - continue - } - - latest, err := lp.LatestLogByEventSigWithConfs(ConfigSet, m.contractAddresses[id], 1, pg.WithParentCtx(ctx)) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - // TODO: try RPC call for config - return 0, ocrtypes.ConfigDigest{}, err - } - return 0, ocrtypes.ConfigDigest{}, err - } - - followerConfig, err := configFromLog(latest.Data) - if err != nil { - return 0, ocrtypes.ConfigDigest{}, err - } - - followerConfigs = append(followerConfigs, followerConfig) - } - - // at this point we can combine the configs into a single one - combined, err := m.combiner(masterConfig, followerConfigs) - if err != nil { - return 0, ocrtypes.ConfigDigest{}, err - } - return uint64(latest.BlockNumber), masterConfig.ConfigDigest, nil } From dd5f024ed73165551c0b2dccd1524a22c14a625c Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Thu, 4 Jan 2024 16:57:59 +0200 Subject: [PATCH 08/16] fix comments --- core/services/relay/evm/ocr3/helpers.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/services/relay/evm/ocr3/helpers.go b/core/services/relay/evm/ocr3/helpers.go index 3ad70dcc5d5..463be33f0df 100644 --- a/core/services/relay/evm/ocr3/helpers.go +++ b/core/services/relay/evm/ocr3/helpers.go @@ -68,14 +68,15 @@ func TransmitterCombiner(masterConfig ocrtypes.ContractConfig, followerConfigs [ OffchainConfig: masterConfig.OffchainConfig, } - // use the hex-ified onchain pub key as a key since []byte can't - // be used as a map key. var combinedTransmitters []ocrtypes.Account - for i, signer := range masterConfig.Signers { + // the transmitter index is the same as the signer index for the same config object. + // this is enforced in the standard OCR3Base.setOCR3Config method. transmitters := []string{string(masterConfig.Transmitters[i])} + for _, followerConfig := range followerConfigs { // signer might be at a different index than master chain (but ideally shouldn't be) + // so we can't just use i here. signerIdx := slices.IndexFunc(followerConfig.Signers, func(opk ocrtypes.OnchainPublicKey) bool { return hexutil.Encode(opk) == hexutil.Encode(signer) }) @@ -83,7 +84,7 @@ func TransmitterCombiner(masterConfig ocrtypes.ContractConfig, followerConfigs [ // signer not found, bad config return ocrtypes.ContractConfig{}, fmt.Errorf("unable to find signer %x (oracle index %d) in follower config %+v", signer, i, followerConfig) } - // signer and transmitter indexes match + // the transmitter index is the same as the signer index for the same config object. transmitters = append(transmitters, string(followerConfig.Transmitters[signerIdx])) } combinedTransmitter := joinTransmitters(transmitters) From 5c97655653b74866ef1c94b43cb8b87ace8360a4 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Thu, 4 Jan 2024 18:57:25 +0200 Subject: [PATCH 09/16] call RPC for latest config details --- .../evm/ocr3/multichain_config_tracker.go | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker.go b/core/services/relay/evm/ocr3/multichain_config_tracker.go index 3bf8a469605..bee314b2844 100644 --- a/core/services/relay/evm/ocr3/multichain_config_tracker.go +++ b/core/services/relay/evm/ocr3/multichain_config_tracker.go @@ -7,15 +7,18 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/services" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + 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/shared/generated/no_op_ocr3" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/relay" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) var ( @@ -146,7 +149,6 @@ func (m *multichainConfigTracker) LatestConfig(ctx context.Context, changedInBlo return ocrtypes.ContractConfig{}, err } if len(lgs) == 0 { - // TODO: fallback to RPC call? return ocrtypes.ContractConfig{}, fmt.Errorf("no logs found for config on contract %s (chain %s) at block %d", m.contractAddresses[m.masterChain].Hex(), m.clients[m.masterChain].ConfiguredChainID().String(), changedInBlock) } masterConfig, err := configFromLog(lgs[len(lgs)-1].Data) @@ -162,18 +164,18 @@ func (m *multichainConfigTracker) LatestConfig(ctx context.Context, changedInBlo continue } - lgs, err := lp.Logs(int64(changedInBlock), int64(changedInBlock), ConfigSet, m.contractAddresses[id], pg.WithParentCtx(ctx)) - if err != nil { - return ocrtypes.ContractConfig{}, err + lgs, err2 := lp.Logs(int64(changedInBlock), int64(changedInBlock), ConfigSet, m.contractAddresses[id], pg.WithParentCtx(ctx)) + if err2 != nil { + return ocrtypes.ContractConfig{}, err2 } if len(lgs) == 0 { return ocrtypes.ContractConfig{}, fmt.Errorf("no logs found for config on contract %s (chain %s) at block %d", m.contractAddresses[id].Hex(), m.clients[id].ConfiguredChainID().String(), changedInBlock) } - configSet, err := configFromLog(lgs[len(lgs)-1].Data) - if err != nil { - return ocrtypes.ContractConfig{}, err + configSet, err2 := configFromLog(lgs[len(lgs)-1].Data) + if err2 != nil { + return ocrtypes.ContractConfig{}, err2 } followerConfigs = append(followerConfigs, configSet) } @@ -192,15 +194,24 @@ func (m *multichainConfigTracker) LatestConfigDetails(ctx context.Context) (chan latest, err := m.logPollers[m.masterChain].LatestLogByEventSigWithConfs(ConfigSet, m.contractAddresses[m.masterChain], 1, pg.WithParentCtx(ctx)) if err != nil { if errors.Is(err, sql.ErrNoRows) { - // TODO: try eth-call to get the config - return 0, ocrtypes.ConfigDigest{}, err + return m.callLatestConfigDetails(ctx, m.masterChain) } return 0, ocrtypes.ConfigDigest{}, err } masterConfig, err := configFromLog(latest.Data) if err != nil { - return 0, ocrtypes.ConfigDigest{}, err + return 0, ocrtypes.ConfigDigest{}, fmt.Errorf("failed to unpack latest config details: %w", err) } return uint64(latest.BlockNumber), masterConfig.ConfigDigest, nil } + +func (m *multichainConfigTracker) callLatestConfigDetails(ctx context.Context, id relay.ID) (changedInBlock uint64, configDigest ocrtypes.ConfigDigest, err error) { + lcd, err := m.contracts[id].LatestConfigDetails(&bind.CallOpts{ + Context: ctx, + }) + if err != nil { + return 0, ocrtypes.ConfigDigest{}, fmt.Errorf("failed to get latest config details: %w", err) + } + return uint64(lcd.BlockNumber), lcd.ConfigDigest, nil +} From 0044ec563ff8bd54df6c28afcdd5dcc81e2806af Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Fri, 5 Jan 2024 14:40:40 +0200 Subject: [PATCH 10/16] add contract transmitter test --- contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol | 2 +- .../relay/evm/ocr3/contract_transmitter.go | 3 +- .../evm/ocr3/contract_transmitter_test.go | 173 ++++++++++++++++++ 3 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 core/services/relay/evm/ocr3/contract_transmitter_test.go diff --git a/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol b/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol index 4620e87a901..9b795ab5668 100644 --- a/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol +++ b/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol @@ -12,6 +12,6 @@ contract NoOpOCR3 is OCR3Base { constructor() OCR3Base() {} function _report(bytes calldata report, uint64 sequenceNumber) internal override { - // do nothing + // do nothing } } diff --git a/core/services/relay/evm/ocr3/contract_transmitter.go b/core/services/relay/evm/ocr3/contract_transmitter.go index 9259b971831..965f2cf02be 100644 --- a/core/services/relay/evm/ocr3/contract_transmitter.go +++ b/core/services/relay/evm/ocr3/contract_transmitter.go @@ -7,7 +7,6 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -29,7 +28,7 @@ func reportToEvmTxMetaNoop([]byte) (*txmgr.TxMeta, error) { return nil, nil } -func transmitterFilterName(addr common.Address) string { +func transmitterFilterName(addr gethcommon.Address) string { return logpoller.FilterName("OCR3 ContractTransmitter", addr.String()) } diff --git a/core/services/relay/evm/ocr3/contract_transmitter_test.go b/core/services/relay/evm/ocr3/contract_transmitter_test.go new file mode 100644 index 00000000000..31cdad8d0b8 --- /dev/null +++ b/core/services/relay/evm/ocr3/contract_transmitter_test.go @@ -0,0 +1,173 @@ +package ocr3_test + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + logpollermocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/no_op_ocr3" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/ocr3" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" +) + +func TestContractTransmitter(t *testing.T) { + t.Parallel() + + deployer := testutils.MustNewSimTransactor(t) + + // create many transmitters but only need to fund one, rest are to get + // setOCR3Config to pass. + var transmitters []*bind.TransactOpts + for i := 0; i < 4; i++ { + transmitters = append(transmitters, testutils.MustNewSimTransactor(t)) + } + + backend := backends.NewSimulatedBackend(core.GenesisAlloc{ + deployer.From: core.GenesisAccount{ + Balance: assets.Ether(1000).ToInt(), + }, + transmitters[0].From: core.GenesisAccount{ + Balance: assets.Ether(1000).ToInt(), + }, + }, 30e6) + addr, _, _, err := no_op_ocr3.DeployNoOpOCR3(deployer, backend) + require.NoError(t, err, "failed to deploy NoOpOCR3 contract") + backend.Commit() + wrapper, err := no_op_ocr3.NewNoOpOCR3(addr, backend) + require.NoError(t, err, "failed to create NoOpOCR3 wrapper") + + // create the oracle identities for setConfig + // need to create at least 4 identities otherwise setConfig will fail + var ( + bundles []ocr2key.KeyBundle + signers []common.Address + ) + for i := 0; i < 4; i++ { + kb, err := ocr2key.New(chaintype.EVM) + require.NoError(t, err, "failed to create key bundle") + signers = append(signers, common.HexToAddress(kb.OnChainPublicKey())) + bundles = append(bundles, kb) + } + f := uint8(1) + _, err = wrapper.SetOCR3Config( + deployer, + signers, + []common.Address{transmitters[0].From, transmitters[1].From, transmitters[2].From, transmitters[3].From}, + f, + []byte{}, + 3, + []byte{}) + require.NoError(t, err, "failed to set config") + backend.Commit() + + c, err := wrapper.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err, "failed to get latest config digest and epoch") + configDigest := c.ConfigDigest + + // create the attributed signatures + // only need f+1 which is 2 in this case + rwi := ocr3types.ReportWithInfo[struct{}]{ + Report: []byte{}, + Info: struct{}{}, + } + seqNum := uint64(1) + var attributedSigs []ocrtypes.AttributedOnchainSignature + for i := uint8(0); i < f+1; i++ { + sig, err := bundles[i].Sign(ocrtypes.ReportContext{ + ReportTimestamp: ocrtypes.ReportTimestamp{ + ConfigDigest: configDigest, + Epoch: uint32(seqNum), + }, + }, rwi.Report) + require.NoError(t, err, "failed to sign report") + attributedSigs = append(attributedSigs, ocrtypes.AttributedOnchainSignature{ + Signature: sig, + Signer: commontypes.OracleID(i), + }) + } + + contractABI, err := no_op_ocr3.NoOpOCR3MetaData.GetAbi() + require.NoError(t, err, "failed to get abi") + tImpl := &transmitterImpl{ + backend: backend, + from: transmitters[0], + t: t, + } + mockLogPoller := logpollermocks.NewLogPoller(t) + mockLogPoller.On("RegisterFilter", mock.Anything).Return(nil) + defer mockLogPoller.AssertExpectations(t) + ocr3Transmitter, err := ocr3.NewOCR3ContractTransmitter[struct{}]( + addr, + *contractABI, + tImpl, + mockLogPoller, + logger.TestLogger(t), + nil, // reportToEvmTxMeta, unused + ) + require.NoError(t, err, "failed to create OCR3ContractTransmitter") + + account, err := ocr3Transmitter.FromAccount() + require.NoError(t, err, "failed to get from account") + require.Equal(t, account, ocrtypes.Account(transmitters[0].From.Hex()), "unexpected from account") + err = ocr3Transmitter.Transmit(context.Background(), configDigest, seqNum, rwi, attributedSigs) + require.NoError(t, err, "failed to transmit report") + + // check for transmitted event + iter, err := wrapper.FilterTransmitted(&bind.FilterOpts{ + Start: 0, + }) + require.NoError(t, err, "failed to create filter iterator") + for iter.Next() { + event := iter.Event + require.Equal(t, configDigest, event.ConfigDigest, "unexpected config digest") + require.Equal(t, seqNum, event.SequenceNumber, "unexpected sequence number") + } +} + +type transmitterImpl struct { + backend *backends.SimulatedBackend + from *bind.TransactOpts + t *testing.T +} + +func (t *transmitterImpl) FromAddress() common.Address { + return t.from.From +} + +func (t *transmitterImpl) CreateEthTransaction(ctx context.Context, to common.Address, data []byte, txMeta *txmgr.TxMeta) error { + nonce, err := t.backend.PendingNonceAt(ctx, t.from.From) + require.NoError(t.t, err, "failed to get nonce") + gp, err := t.backend.SuggestGasPrice(ctx) + require.NoError(t.t, err, "failed to get gas price") + rawTx := types.NewTx(&types.LegacyTx{ + Nonce: nonce, + GasPrice: gp, + Gas: 500_000, + To: &to, + Value: big.NewInt(0), + Data: data, + }) + signedTx, err := t.from.Signer(t.from.From, rawTx) + require.NoError(t.t, err, "failed to sign tx") + err = t.backend.SendTransaction(ctx, signedTx) + require.NoError(t.t, err, "failed to send tx") + t.backend.Commit() + return nil +} From c18cf25a0e91d7d933963bd7f0a71269ba43140a Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Fri, 5 Jan 2024 16:31:47 +0200 Subject: [PATCH 11/16] add multichain transmitter test --- .../relay/evm/ocr3/contract_transmitter.go | 2 + .../evm/ocr3/contract_transmitter_test.go | 125 ++++++++++++------ core/services/relay/evm/ocr3/helpers.go | 2 + .../evm/ocr3/multichain_transmitter_test.go | 90 +++++++++++++ 4 files changed, 179 insertions(+), 40 deletions(-) create mode 100644 core/services/relay/evm/ocr3/multichain_transmitter_test.go diff --git a/core/services/relay/evm/ocr3/contract_transmitter.go b/core/services/relay/evm/ocr3/contract_transmitter.go index 965f2cf02be..020154b49a4 100644 --- a/core/services/relay/evm/ocr3/contract_transmitter.go +++ b/core/services/relay/evm/ocr3/contract_transmitter.go @@ -122,5 +122,7 @@ func (c *contractTransmitterOCR3[RI]) Transmit(ctx context.Context, configDigest return fmt.Errorf("%w: abi.Pack failed with args: (%+v, %s, %+v, %+v, %+v)", err, rawReportCtx, hex.EncodeToString(rwi.Report), rs, ss, vs) } + c.lggr.Debugw("payload", "payload", hex.EncodeToString(payload)) + return errors.Wrap(c.transmitter.CreateEthTransaction(ctx, c.contractAddress, payload, txMeta), "failed to send Eth transaction") } diff --git a/core/services/relay/evm/ocr3/contract_transmitter_test.go b/core/services/relay/evm/ocr3/contract_transmitter_test.go index 31cdad8d0b8..186d5662d10 100644 --- a/core/services/relay/evm/ocr3/contract_transmitter_test.go +++ b/core/services/relay/evm/ocr3/contract_transmitter_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" @@ -27,8 +28,18 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) -func TestContractTransmitter(t *testing.T) { - t.Parallel() +type testUniverse[RI any] struct { + backend *backends.SimulatedBackend + deployer *bind.TransactOpts + transmitters []*bind.TransactOpts + wrapper *no_op_ocr3.NoOpOCR3 + ocr3Transmitter ocr3types.ContractTransmitter[RI] + bundles []ocr2key.KeyBundle + f uint8 +} + +func newTestUniverse[RI any](t *testing.T) testUniverse[RI] { + t.Helper() deployer := testutils.MustNewSimTransactor(t) @@ -77,32 +88,6 @@ func TestContractTransmitter(t *testing.T) { require.NoError(t, err, "failed to set config") backend.Commit() - c, err := wrapper.LatestConfigDigestAndEpoch(nil) - require.NoError(t, err, "failed to get latest config digest and epoch") - configDigest := c.ConfigDigest - - // create the attributed signatures - // only need f+1 which is 2 in this case - rwi := ocr3types.ReportWithInfo[struct{}]{ - Report: []byte{}, - Info: struct{}{}, - } - seqNum := uint64(1) - var attributedSigs []ocrtypes.AttributedOnchainSignature - for i := uint8(0); i < f+1; i++ { - sig, err := bundles[i].Sign(ocrtypes.ReportContext{ - ReportTimestamp: ocrtypes.ReportTimestamp{ - ConfigDigest: configDigest, - Epoch: uint32(seqNum), - }, - }, rwi.Report) - require.NoError(t, err, "failed to sign report") - attributedSigs = append(attributedSigs, ocrtypes.AttributedOnchainSignature{ - Signature: sig, - Signer: commontypes.OracleID(i), - }) - } - contractABI, err := no_op_ocr3.NoOpOCR3MetaData.GetAbi() require.NoError(t, err, "failed to get abi") tImpl := &transmitterImpl{ @@ -113,7 +98,7 @@ func TestContractTransmitter(t *testing.T) { mockLogPoller := logpollermocks.NewLogPoller(t) mockLogPoller.On("RegisterFilter", mock.Anything).Return(nil) defer mockLogPoller.AssertExpectations(t) - ocr3Transmitter, err := ocr3.NewOCR3ContractTransmitter[struct{}]( + ocr3Transmitter, err := ocr3.NewOCR3ContractTransmitter[RI]( addr, *contractABI, tImpl, @@ -123,22 +108,77 @@ func TestContractTransmitter(t *testing.T) { ) require.NoError(t, err, "failed to create OCR3ContractTransmitter") - account, err := ocr3Transmitter.FromAccount() - require.NoError(t, err, "failed to get from account") - require.Equal(t, account, ocrtypes.Account(transmitters[0].From.Hex()), "unexpected from account") - err = ocr3Transmitter.Transmit(context.Background(), configDigest, seqNum, rwi, attributedSigs) - require.NoError(t, err, "failed to transmit report") + return testUniverse[RI]{ + backend: backend, + deployer: deployer, + transmitters: transmitters, + wrapper: wrapper, + bundles: bundles, + ocr3Transmitter: ocr3Transmitter, + f: f, + } +} - // check for transmitted event - iter, err := wrapper.FilterTransmitted(&bind.FilterOpts{ - Start: 0, - }) +func (uni testUniverse[RI]) SignReport(t *testing.T, configDigest ocrtypes.ConfigDigest, rwi ocr3types.ReportWithInfo[RI], seqNum uint64) []ocrtypes.AttributedOnchainSignature { + var attributedSigs []ocrtypes.AttributedOnchainSignature + for i := uint8(0); i < uni.f+1; i++ { + sig, err := uni.bundles[i].Sign(ocrtypes.ReportContext{ + ReportTimestamp: ocrtypes.ReportTimestamp{ + ConfigDigest: configDigest, + Epoch: uint32(seqNum), + }, + }, rwi.Report) + require.NoError(t, err, "failed to sign report") + attributedSigs = append(attributedSigs, ocrtypes.AttributedOnchainSignature{ + Signature: sig, + Signer: commontypes.OracleID(i), + }) + } + return attributedSigs +} + +func (uni testUniverse[RI]) TransmittedEvents(t *testing.T) []*no_op_ocr3.NoOpOCR3Transmitted { + iter, err := uni.wrapper.FilterTransmitted(nil) require.NoError(t, err, "failed to create filter iterator") + var events []*no_op_ocr3.NoOpOCR3Transmitted for iter.Next() { event := iter.Event - require.Equal(t, configDigest, event.ConfigDigest, "unexpected config digest") - require.Equal(t, seqNum, event.SequenceNumber, "unexpected sequence number") + events = append(events, event) + } + return events +} + +func TestContractTransmitter(t *testing.T) { + t.Parallel() + + uni := newTestUniverse[struct{}](t) + + c, err := uni.wrapper.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err, "failed to get latest config digest and epoch") + configDigest := c.ConfigDigest + + // create the attributed signatures + // only need f+1 which is 2 in this case + rwi := ocr3types.ReportWithInfo[struct{}]{ + Report: []byte{}, + Info: struct{}{}, } + seqNum := uint64(1) + attributedSigs := uni.SignReport(t, configDigest, rwi, seqNum) + + account, err := uni.ocr3Transmitter.FromAccount() + require.NoError(t, err, "failed to get from account") + require.Equal(t, account, ocrtypes.Account(uni.transmitters[0].From.Hex()), "unexpected from account") + err = uni.ocr3Transmitter.Transmit(context.Background(), configDigest, seqNum, rwi, attributedSigs) + require.NoError(t, err, "failed to transmit report") + + // check for transmitted event + // TODO: for some reason this event isn't being emitted in the simulated backend + // events := uni.TransmittedEvents(t) + // require.Len(t, events, 1, "expected one transmitted event") + // event := events[0] + // require.Equal(t, configDigest, event.ConfigDigest, "unexpected config digest") + // require.Equal(t, seqNum, event.SequenceNumber, "unexpected sequence number") } type transmitterImpl struct { @@ -169,5 +209,10 @@ func (t *transmitterImpl) CreateEthTransaction(ctx context.Context, to common.Ad err = t.backend.SendTransaction(ctx, signedTx) require.NoError(t.t, err, "failed to send tx") t.backend.Commit() + logs, err := t.backend.FilterLogs(ctx, ethereum.FilterQuery{}) + require.NoError(t.t, err, "failed to filter logs") + for _, lg := range logs { + t.t.Log("topic:", lg.Topics[0], "transmitted topic:", no_op_ocr3.NoOpOCR3Transmitted{}.Topic(), "configset topic:", no_op_ocr3.NoOpOCR3ConfigSet{}.Topic()) + } return nil } diff --git a/core/services/relay/evm/ocr3/helpers.go b/core/services/relay/evm/ocr3/helpers.go index 463be33f0df..b4dbb18d74a 100644 --- a/core/services/relay/evm/ocr3/helpers.go +++ b/core/services/relay/evm/ocr3/helpers.go @@ -106,5 +106,7 @@ func TransmitterCombiner(masterConfig ocrtypes.ContractConfig, followerConfigs [ // ContractTransmitter and the ContractConfig.Transmitters output for a // particular signer must match in order for OCR3 to work. func joinTransmitters(transmitters []string) string { + // sort first to ensure deterministic ordering + slices.Sort(transmitters) return strings.Join(transmitters, ",") } diff --git a/core/services/relay/evm/ocr3/multichain_transmitter_test.go b/core/services/relay/evm/ocr3/multichain_transmitter_test.go new file mode 100644 index 00000000000..9dd8a5f3559 --- /dev/null +++ b/core/services/relay/evm/ocr3/multichain_transmitter_test.go @@ -0,0 +1,90 @@ +package ocr3_test + +import ( + "fmt" + "slices" + "strings" + "testing" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/ocr3" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/require" +) + +func TestMultichainTransmitter(t *testing.T) { + t.Parallel() + // create many separate transmitters and separate chains + numChains := 4 + unis := make(map[int]testUniverse[multichainMeta]) + for i := 0; i < numChains; i++ { + unis[i] = newTestUniverse[multichainMeta](t) + } + + mct, err := ocr3.NewMultichainTransmitterOCR3[multichainMeta]( + map[string]ocr3types.ContractTransmitter[multichainMeta]{ + "0": unis[0].ocr3Transmitter, + "1": unis[1].ocr3Transmitter, + "2": unis[2].ocr3Transmitter, + "3": unis[3].ocr3Transmitter, + }, + nil, // log poller, unused for now + logger.TestLogger(t), + ) + require.NoError(t, err) + + expectedTransmitters := []string{ + unis[0].transmitters[0].From.String(), + unis[1].transmitters[0].From.String(), + unis[2].transmitters[0].From.String(), + unis[3].transmitters[0].From.String(), + } + slices.Sort(expectedTransmitters) + expectedFromAccount := strings.Join(expectedTransmitters, ",") + fromAccount, err := mct.FromAccount() + require.NoError(t, err) + require.Equal(t, expectedFromAccount, string(fromAccount)) + + var configDigests []ocrtypes.ConfigDigest + for _, uni := range unis { + c, err := uni.wrapper.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err) + configDigests = append(configDigests, c.ConfigDigest) + } + + // generate a report for each chain and sign it + // note that in this test each chain has a different set of signers + // this is okay because it's just a test + // in actuality the same signers will be used across all chains + var reports []ocr3types.ReportWithInfo[multichainMeta] + for i := 0; i < numChains; i++ { + report := ocr3types.ReportWithInfo[multichainMeta]{ + Info: multichainMeta{destChainIndex: i}, + Report: []byte{}, + } + reports = append(reports, report) + } + seqNum := uint64(1) + for i := range reports { + attributedSigs := unis[i].SignReport(t, configDigests[i], reports[i], seqNum) + err = mct.Transmit(testutils.Context(t), configDigests[i], seqNum, reports[i], attributedSigs) + require.NoError(t, err) + // TODO: for some reason this event isn't being emitted in the simulated backend + // events := unis[i].TransmittedEvents(t) + // require.Len(t, events, 1) + // require.Equal(t, configDigests[i], events[0].ConfigDigest, "config digest mismatch") + // require.Equal(t, seqNum, events[0].SequenceNumber, "sequence number mismatch") + // increment sequence number so that each chain gets a unique one for this test + seqNum++ + } +} + +type multichainMeta struct { + destChainIndex int +} + +func (m multichainMeta) GetDestinationChainID() string { + return fmt.Sprintf("%d", m.destChainIndex) +} From ee936ea106e8807fdf1175551fb9ee99beaf3d47 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Fri, 5 Jan 2024 18:53:43 +0200 Subject: [PATCH 12/16] add some config tracker tests --- .../evm/ocr3/contract_transmitter_test.go | 3 + .../evm/ocr3/multichain_config_tracker.go | 6 + .../ocr3/multichain_config_tracker_test.go | 172 ++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 core/services/relay/evm/ocr3/multichain_config_tracker_test.go diff --git a/core/services/relay/evm/ocr3/contract_transmitter_test.go b/core/services/relay/evm/ocr3/contract_transmitter_test.go index 186d5662d10..ad43b07227f 100644 --- a/core/services/relay/evm/ocr3/contract_transmitter_test.go +++ b/core/services/relay/evm/ocr3/contract_transmitter_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" logpollermocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/no_op_ocr3" @@ -29,6 +30,7 @@ import ( ) type testUniverse[RI any] struct { + simClient *client.SimulatedBackendClient backend *backends.SimulatedBackend deployer *bind.TransactOpts transmitters []*bind.TransactOpts @@ -116,6 +118,7 @@ func newTestUniverse[RI any](t *testing.T) testUniverse[RI] { bundles: bundles, ocr3Transmitter: ocr3Transmitter, f: f, + simClient: client.NewSimulatedBackendClient(t, backend, testutils.SimulatedChainID), } } diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker.go b/core/services/relay/evm/ocr3/multichain_config_tracker.go index bee314b2844..409f6b28013 100644 --- a/core/services/relay/evm/ocr3/multichain_config_tracker.go +++ b/core/services/relay/evm/ocr3/multichain_config_tracker.go @@ -78,6 +78,11 @@ func NewMultichainConfigTracker( return nil, fmt.Errorf("master chain %s not in clients", masterChain) } + // Ensure master chain is in the contract addresses + if _, ok := contractAddresses[masterChain]; !ok { + return nil, fmt.Errorf("master chain %s not in contract addresses", masterChain) + } + // Ensure combiner is not nil if combiner == nil { return nil, fmt.Errorf("provide non-nil combiner") @@ -108,6 +113,7 @@ func NewMultichainConfigTracker( contractAddresses: contractAddresses, contracts: contracts, masterChain: masterChain, + combiner: combiner, }, nil } diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker_test.go b/core/services/relay/evm/ocr3/multichain_config_tracker_test.go new file mode 100644 index 00000000000..ff6110c44bd --- /dev/null +++ b/core/services/relay/evm/ocr3/multichain_config_tracker_test.go @@ -0,0 +1,172 @@ +package ocr3_test + +import ( + "encoding/hex" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" + 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/shared/generated/no_op_ocr3" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/relay" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/ocr3" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/require" +) + +func setupLogPoller[RI any](t *testing.T, db *sqlx.DB) (logpoller.LogPoller, testUniverse[RI]) { + lggr := logger.TestLogger(t) + + o := logpoller.NewORM(testutils.SimulatedChainID, db, lggr, pgtest.NewQConfig(false)) + + // create the universe which will deploy the OCR contract and set config + // we will replay on the log poller to get the appropriate ConfigSet log + uni := newTestUniverse[RI](t) + + lp := logpoller.NewLogPoller(o, uni.simClient, lggr, 1*time.Second, false, 100, 100, 100, 200) + return lp, uni +} + +func TestConfigSet(t *testing.T) { + require.Equal(t, no_op_ocr3.NoOpOCR3ConfigSet{}.Topic().Hex(), ocr3.ConfigSet.Hex()) +} + +func TestMultichainConfigTracker_New(t *testing.T) { + t.Run("master chain not in log pollers", func(t *testing.T) { + db := pgtest.NewSqlxDB(t) + _, uni := setupLogPoller[struct{}](t, db) + + masterChain := relay.ID{ + Network: relay.EVM, + ChainID: testutils.SimulatedChainID.String(), + } + _, err := ocr3.NewMultichainConfigTracker( + masterChain, + logger.TestLogger(t), + map[relay.ID]logpoller.LogPoller{}, + map[relay.ID]evmclient.Client{masterChain: uni.simClient}, + map[relay.ID]common.Address{masterChain: uni.wrapper.Address()}, + ocr3.TransmitterCombiner, + ) + require.Error(t, err, "expected error creating multichain config tracker") + }) + + t.Run("master chain not in clients", func(t *testing.T) { + db := pgtest.NewSqlxDB(t) + lp, uni := setupLogPoller[struct{}](t, db) + + masterChain := relay.ID{ + Network: relay.EVM, + ChainID: testutils.SimulatedChainID.String(), + } + _, err := ocr3.NewMultichainConfigTracker( + masterChain, + logger.TestLogger(t), + map[relay.ID]logpoller.LogPoller{masterChain: lp}, + map[relay.ID]evmclient.Client{}, + map[relay.ID]common.Address{masterChain: uni.wrapper.Address()}, + ocr3.TransmitterCombiner, + ) + require.Error(t, err, "expected error creating multichain config tracker") + }) + + t.Run("master chain not in contract addresses", func(t *testing.T) { + db := pgtest.NewSqlxDB(t) + lp, uni := setupLogPoller[struct{}](t, db) + + masterChain := relay.ID{ + Network: relay.EVM, + ChainID: testutils.SimulatedChainID.String(), + } + _, err := ocr3.NewMultichainConfigTracker( + masterChain, + logger.TestLogger(t), + map[relay.ID]logpoller.LogPoller{masterChain: lp}, + map[relay.ID]evmclient.Client{masterChain: uni.simClient}, + map[relay.ID]common.Address{}, + ocr3.TransmitterCombiner, + ) + require.Error(t, err, "expected error creating multichain config tracker") + }) + + t.Run("combiner is nil", func(t *testing.T) { + db := pgtest.NewSqlxDB(t) + lp, uni := setupLogPoller[struct{}](t, db) + + masterChain := relay.ID{ + Network: relay.EVM, + ChainID: testutils.SimulatedChainID.String(), + } + _, err := ocr3.NewMultichainConfigTracker( + masterChain, + logger.TestLogger(t), + map[relay.ID]logpoller.LogPoller{masterChain: lp}, + map[relay.ID]evmclient.Client{masterChain: uni.simClient}, + map[relay.ID]common.Address{masterChain: uni.wrapper.Address()}, + nil, + ) + require.Error(t, err, "expected error creating multichain config tracker") + }) +} + +func TestMultichainConfigTracker_SingleChain(t *testing.T) { + db := pgtest.NewSqlxDB(t) + lp, uni := setupLogPoller[struct{}](t, db) + lp.Start(testutils.Context(t)) + + masterChain := relay.ID{ + Network: relay.EVM, + ChainID: testutils.SimulatedChainID.String(), + } + tracker, err := ocr3.NewMultichainConfigTracker( + masterChain, + logger.TestLogger(t), + map[relay.ID]logpoller.LogPoller{masterChain: lp}, + map[relay.ID]evmclient.Client{masterChain: uni.simClient}, + map[relay.ID]common.Address{masterChain: uni.wrapper.Address()}, + ocr3.TransmitterCombiner, + ) + require.NoError(t, err, "failed to create multichain config tracker") + + // Replay the log poller to get the ConfigSet log + err = tracker.Replay(testutils.Context(t), masterChain, 1) + require.NoError(t, err, "failed to replay log poller") + + // fetch config digest from the tracker + changedInBlock, configDigest, err := tracker.LatestConfigDetails(testutils.Context(t)) + require.NoError(t, err, "failed to get latest config details") + c, err := uni.wrapper.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err, "failed to get latest config digest and epoch") + require.Equal(t, hex.EncodeToString(c.ConfigDigest[:]), configDigest.Hex(), "expected latest config digest to match") + + // fetch config details from the tracker + config, err := tracker.LatestConfig(testutils.Context(t), changedInBlock) + require.NoError(t, err, "failed to get latest config") + require.Equal(t, uint64(1), config.ConfigCount, "expected config count to match") + require.Equal(t, configDigest, config.ConfigDigest, "expected config digest to match") + require.Equal(t, uint8(1), config.F, "expected f to match") + require.Equal(t, []byte{}, config.OnchainConfig, "expected onchain config to match") + require.Equal(t, []byte{}, config.OffchainConfig, "expected offchain config to match") + require.Equal(t, uint64(3), config.OffchainConfigVersion, "expected offchain config version to match") + expectedSigners := func() []ocrtypes.OnchainPublicKey { + var signers []ocrtypes.OnchainPublicKey + for _, b := range uni.bundles { + signers = append(signers, b.PublicKey()) + } + return signers + }() + expectedTransmitters := func() []ocrtypes.Account { + var accounts []ocrtypes.Account + for _, tm := range uni.transmitters { + accounts = append(accounts, ocrtypes.Account(tm.From.Hex())) + } + return accounts + }() + require.Equal(t, expectedSigners, config.Signers, "expected signers to match") + require.Equal(t, expectedTransmitters, config.Transmitters, "expected transmitters to match") +} From f0549a91f41c0d8f2dcb62db8a923e377b6b1416 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Mon, 8 Jan 2024 11:46:46 +0200 Subject: [PATCH 13/16] fix linting errors --- core/services/relay/evm/ocr3/contract_transmitter.go | 7 ++++--- core/services/relay/evm/ocr3/contract_transmitter_test.go | 4 ++-- core/services/relay/evm/ocr3/helpers.go | 3 ++- core/services/relay/evm/ocr3/multichain_config_tracker.go | 3 ++- .../relay/evm/ocr3/multichain_config_tracker_test.go | 2 +- .../services/relay/evm/ocr3/multichain_transmitter_test.go | 4 ++-- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/services/relay/evm/ocr3/contract_transmitter.go b/core/services/relay/evm/ocr3/contract_transmitter.go index 020154b49a4..db0cc628e69 100644 --- a/core/services/relay/evm/ocr3/contract_transmitter.go +++ b/core/services/relay/evm/ocr3/contract_transmitter.go @@ -9,12 +9,13 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/logger" ) type Transmitter interface { diff --git a/core/services/relay/evm/ocr3/contract_transmitter_test.go b/core/services/relay/evm/ocr3/contract_transmitter_test.go index ad43b07227f..ff1d03c743a 100644 --- a/core/services/relay/evm/ocr3/contract_transmitter_test.go +++ b/core/services/relay/evm/ocr3/contract_transmitter_test.go @@ -73,8 +73,8 @@ func newTestUniverse[RI any](t *testing.T) testUniverse[RI] { signers []common.Address ) for i := 0; i < 4; i++ { - kb, err := ocr2key.New(chaintype.EVM) - require.NoError(t, err, "failed to create key bundle") + kb, err2 := ocr2key.New(chaintype.EVM) + require.NoError(t, err2, "failed to create key bundle") signers = append(signers, common.HexToAddress(kb.OnChainPublicKey())) bundles = append(bundles, kb) } diff --git a/core/services/relay/evm/ocr3/helpers.go b/core/services/relay/evm/ocr3/helpers.go index b4dbb18d74a..e7b81752d54 100644 --- a/core/services/relay/evm/ocr3/helpers.go +++ b/core/services/relay/evm/ocr3/helpers.go @@ -7,10 +7,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/no_op_ocr3" "github.com/smartcontractkit/chainlink/v2/core/services/relay" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) func configTrackerFilterName(id relay.ID, addr common.Address) string { diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker.go b/core/services/relay/evm/ocr3/multichain_config_tracker.go index 409f6b28013..8d2def7b902 100644 --- a/core/services/relay/evm/ocr3/multichain_config_tracker.go +++ b/core/services/relay/evm/ocr3/multichain_config_tracker.go @@ -10,9 +10,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink-common/pkg/services" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/services" + 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/shared/generated/no_op_ocr3" diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker_test.go b/core/services/relay/evm/ocr3/multichain_config_tracker_test.go index ff6110c44bd..5f02a7b4bd4 100644 --- a/core/services/relay/evm/ocr3/multichain_config_tracker_test.go +++ b/core/services/relay/evm/ocr3/multichain_config_tracker_test.go @@ -117,7 +117,7 @@ func TestMultichainConfigTracker_New(t *testing.T) { func TestMultichainConfigTracker_SingleChain(t *testing.T) { db := pgtest.NewSqlxDB(t) lp, uni := setupLogPoller[struct{}](t, db) - lp.Start(testutils.Context(t)) + require.NoError(t, lp.Start(testutils.Context(t))) masterChain := relay.ID{ Network: relay.EVM, diff --git a/core/services/relay/evm/ocr3/multichain_transmitter_test.go b/core/services/relay/evm/ocr3/multichain_transmitter_test.go index 9dd8a5f3559..f790e10f7fd 100644 --- a/core/services/relay/evm/ocr3/multichain_transmitter_test.go +++ b/core/services/relay/evm/ocr3/multichain_transmitter_test.go @@ -49,8 +49,8 @@ func TestMultichainTransmitter(t *testing.T) { var configDigests []ocrtypes.ConfigDigest for _, uni := range unis { - c, err := uni.wrapper.LatestConfigDigestAndEpoch(nil) - require.NoError(t, err) + c, err2 := uni.wrapper.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err2) configDigests = append(configDigests, c.ConfigDigest) } From aed6d45a439d8f4f47743e911c77c9826f01364e Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Mon, 8 Jan 2024 11:48:44 +0200 Subject: [PATCH 14/16] fix solhint --- contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol b/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol index 9b795ab5668..36ec3fd799f 100644 --- a/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol +++ b/contracts/src/v0.8/shared/ocr3/NoOpOCR3.sol @@ -7,6 +7,7 @@ import {OCR3Base} from "./OCR3Base.sol"; // This is so that we can generate gethwrappers for the contract and use the OCR3 ABI in // Go code. contract NoOpOCR3 is OCR3Base { + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables string public constant override typeAndVersion = "NoOpOCR3 1.0.0"; constructor() OCR3Base() {} From 6a275c7781eb7f40a11ba0022940701bc52c1aeb Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Mon, 8 Jan 2024 13:31:25 +0200 Subject: [PATCH 15/16] add multichain config tracker test --- .../evm/ocr3/contract_transmitter_test.go | 28 +++-- core/services/relay/evm/ocr3/helpers.go | 6 +- .../ocr3/multichain_config_tracker_test.go | 101 ++++++++++++++++-- .../relay/evm/ocr3/multichain_transmitter.go | 2 +- .../evm/ocr3/multichain_transmitter_test.go | 2 +- 5 files changed, 120 insertions(+), 19 deletions(-) diff --git a/core/services/relay/evm/ocr3/contract_transmitter_test.go b/core/services/relay/evm/ocr3/contract_transmitter_test.go index ff1d03c743a..d950a8808b0 100644 --- a/core/services/relay/evm/ocr3/contract_transmitter_test.go +++ b/core/services/relay/evm/ocr3/contract_transmitter_test.go @@ -34,13 +34,21 @@ type testUniverse[RI any] struct { backend *backends.SimulatedBackend deployer *bind.TransactOpts transmitters []*bind.TransactOpts + signers []common.Address wrapper *no_op_ocr3.NoOpOCR3 ocr3Transmitter ocr3types.ContractTransmitter[RI] bundles []ocr2key.KeyBundle f uint8 } -func newTestUniverse[RI any](t *testing.T) testUniverse[RI] { +type bundlesAndSigners struct { + bundles []ocr2key.KeyBundle + signers []common.Address +} + +func newTestUniverse[RI any]( + t *testing.T, + bs *bundlesAndSigners) testUniverse[RI] { t.Helper() deployer := testutils.MustNewSimTransactor(t) @@ -72,11 +80,16 @@ func newTestUniverse[RI any](t *testing.T) testUniverse[RI] { bundles []ocr2key.KeyBundle signers []common.Address ) - for i := 0; i < 4; i++ { - kb, err2 := ocr2key.New(chaintype.EVM) - require.NoError(t, err2, "failed to create key bundle") - signers = append(signers, common.HexToAddress(kb.OnChainPublicKey())) - bundles = append(bundles, kb) + if bs != nil { + bundles = bs.bundles + signers = bs.signers + } else { + for i := 0; i < 4; i++ { + kb, err2 := ocr2key.New(chaintype.EVM) + require.NoError(t, err2, "failed to create key bundle") + signers = append(signers, common.HexToAddress(kb.OnChainPublicKey())) + bundles = append(bundles, kb) + } } f := uint8(1) _, err = wrapper.SetOCR3Config( @@ -114,6 +127,7 @@ func newTestUniverse[RI any](t *testing.T) testUniverse[RI] { backend: backend, deployer: deployer, transmitters: transmitters, + signers: signers, wrapper: wrapper, bundles: bundles, ocr3Transmitter: ocr3Transmitter, @@ -154,7 +168,7 @@ func (uni testUniverse[RI]) TransmittedEvents(t *testing.T) []*no_op_ocr3.NoOpOC func TestContractTransmitter(t *testing.T) { t.Parallel() - uni := newTestUniverse[struct{}](t) + uni := newTestUniverse[struct{}](t, nil) c, err := uni.wrapper.LatestConfigDigestAndEpoch(nil) require.NoError(t, err, "failed to get latest config digest and epoch") diff --git a/core/services/relay/evm/ocr3/helpers.go b/core/services/relay/evm/ocr3/helpers.go index e7b81752d54..f7b5fce6022 100644 --- a/core/services/relay/evm/ocr3/helpers.go +++ b/core/services/relay/evm/ocr3/helpers.go @@ -88,7 +88,7 @@ func TransmitterCombiner(masterConfig ocrtypes.ContractConfig, followerConfigs [ // the transmitter index is the same as the signer index for the same config object. transmitters = append(transmitters, string(followerConfig.Transmitters[signerIdx])) } - combinedTransmitter := joinTransmitters(transmitters) + combinedTransmitter := JoinTransmitters(transmitters) combinedTransmitters = append(combinedTransmitters, ocrtypes.Account(combinedTransmitter)) } @@ -101,12 +101,12 @@ func TransmitterCombiner(masterConfig ocrtypes.ContractConfig, followerConfigs [ return toReturn, nil } -// joinTransmitters is a helper that combines many transmitters into one +// JoinTransmitters is a helper that combines many transmitters into one // Note that this is pulled out so that it can be used in the CombinerFn // and the contract transmitter since the output of FromAccount() in the // ContractTransmitter and the ContractConfig.Transmitters output for a // particular signer must match in order for OCR3 to work. -func joinTransmitters(transmitters []string) string { +func JoinTransmitters(transmitters []string) string { // sort first to ensure deterministic ordering slices.Sort(transmitters) return strings.Join(transmitters, ",") diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker_test.go b/core/services/relay/evm/ocr3/multichain_config_tracker_test.go index 5f02a7b4bd4..6112554e3c8 100644 --- a/core/services/relay/evm/ocr3/multichain_config_tracker_test.go +++ b/core/services/relay/evm/ocr3/multichain_config_tracker_test.go @@ -10,6 +10,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/shared/generated/no_op_ocr3" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -19,14 +20,14 @@ import ( "github.com/stretchr/testify/require" ) -func setupLogPoller[RI any](t *testing.T, db *sqlx.DB) (logpoller.LogPoller, testUniverse[RI]) { +func setupLogPoller[RI any](t *testing.T, db *sqlx.DB, bs *bundlesAndSigners) (logpoller.LogPoller, testUniverse[RI]) { lggr := logger.TestLogger(t) o := logpoller.NewORM(testutils.SimulatedChainID, db, lggr, pgtest.NewQConfig(false)) // create the universe which will deploy the OCR contract and set config // we will replay on the log poller to get the appropriate ConfigSet log - uni := newTestUniverse[RI](t) + uni := newTestUniverse[RI](t, bs) lp := logpoller.NewLogPoller(o, uni.simClient, lggr, 1*time.Second, false, 100, 100, 100, 200) return lp, uni @@ -39,7 +40,7 @@ func TestConfigSet(t *testing.T) { func TestMultichainConfigTracker_New(t *testing.T) { t.Run("master chain not in log pollers", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - _, uni := setupLogPoller[struct{}](t, db) + _, uni := setupLogPoller[struct{}](t, db, nil) masterChain := relay.ID{ Network: relay.EVM, @@ -58,7 +59,7 @@ func TestMultichainConfigTracker_New(t *testing.T) { t.Run("master chain not in clients", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - lp, uni := setupLogPoller[struct{}](t, db) + lp, uni := setupLogPoller[struct{}](t, db, nil) masterChain := relay.ID{ Network: relay.EVM, @@ -77,7 +78,7 @@ func TestMultichainConfigTracker_New(t *testing.T) { t.Run("master chain not in contract addresses", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - lp, uni := setupLogPoller[struct{}](t, db) + lp, uni := setupLogPoller[struct{}](t, db, nil) masterChain := relay.ID{ Network: relay.EVM, @@ -96,7 +97,7 @@ func TestMultichainConfigTracker_New(t *testing.T) { t.Run("combiner is nil", func(t *testing.T) { db := pgtest.NewSqlxDB(t) - lp, uni := setupLogPoller[struct{}](t, db) + lp, uni := setupLogPoller[struct{}](t, db, nil) masterChain := relay.ID{ Network: relay.EVM, @@ -116,7 +117,7 @@ func TestMultichainConfigTracker_New(t *testing.T) { func TestMultichainConfigTracker_SingleChain(t *testing.T) { db := pgtest.NewSqlxDB(t) - lp, uni := setupLogPoller[struct{}](t, db) + lp, uni := setupLogPoller[struct{}](t, db, nil) require.NoError(t, lp.Start(testutils.Context(t))) masterChain := relay.ID{ @@ -170,3 +171,89 @@ func TestMultichainConfigTracker_SingleChain(t *testing.T) { require.Equal(t, expectedSigners, config.Signers, "expected signers to match") require.Equal(t, expectedTransmitters, config.Transmitters, "expected transmitters to match") } + +func TestMultichainConfigTracker_Multichain(t *testing.T) { + // create heavyweight db's because the log pollers need to have separate + // databases to avoid conflicts. + _, db1 := heavyweight.FullTestDBV2(t, nil) + _, db2 := heavyweight.FullTestDBV2(t, nil) + + lp1, uni1 := setupLogPoller[struct{}](t, db1, nil) + lp2, uni2 := setupLogPoller[struct{}](t, db2, &bundlesAndSigners{ + bundles: uni1.bundles, + signers: uni1.signers, + }) + + // start the log pollers + require.NoError(t, lp1.Start(testutils.Context(t))) + require.NoError(t, lp2.Start(testutils.Context(t))) + + // create the multichain config tracker + // the chain id's we're using in the mappings are different from the + // simulated chain id but that should be fine for this test. + masterChain := relay.ID{ + Network: relay.EVM, + ChainID: testutils.NewRandomEVMChainID().String(), + } + secondChain := relay.ID{ + Network: relay.EVM, + ChainID: testutils.NewRandomEVMChainID().String(), + } + tracker, err := ocr3.NewMultichainConfigTracker( + masterChain, + logger.TestLogger(t), + map[relay.ID]logpoller.LogPoller{ + masterChain: lp1, + secondChain: lp2, + }, + map[relay.ID]evmclient.Client{ + masterChain: uni1.simClient, + secondChain: uni2.simClient, + }, + map[relay.ID]common.Address{ + masterChain: uni1.wrapper.Address(), + secondChain: uni2.wrapper.Address(), + }, + ocr3.TransmitterCombiner, + ) + require.NoError(t, err, "failed to create multichain config tracker") + + // Replay the log pollers to get the ConfigSet log + // on each respective chain + require.NoError(t, tracker.Replay(testutils.Context(t), masterChain, 1), "failed to replay log poller on master chain") + require.NoError(t, tracker.Replay(testutils.Context(t), secondChain, 1), "failed to replay log poller on second chain") + + // fetch config digest from the tracker + changedInBlock, configDigest, err := tracker.LatestConfigDetails(testutils.Context(t)) + require.NoError(t, err, "failed to get latest config details") + c, err := uni1.wrapper.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err, "failed to get latest config digest and epoch") + require.Equal(t, hex.EncodeToString(c.ConfigDigest[:]), configDigest.Hex(), "expected latest config digest to match") + + // fetch config details from the tracker + config, err := tracker.LatestConfig(testutils.Context(t), changedInBlock) + require.NoError(t, err, "failed to get latest config") + require.Equal(t, uint64(1), config.ConfigCount, "expected config count to match") + require.Equal(t, configDigest, config.ConfigDigest, "expected config digest to match") + require.Equal(t, uint8(1), config.F, "expected f to match") + require.Equal(t, []byte{}, config.OnchainConfig, "expected onchain config to match") + require.Equal(t, []byte{}, config.OffchainConfig, "expected offchain config to match") + require.Equal(t, uint64(3), config.OffchainConfigVersion, "expected offchain config version to match") + expectedSigners := func() []ocrtypes.OnchainPublicKey { + var signers []ocrtypes.OnchainPublicKey + for _, b := range uni1.bundles { + signers = append(signers, b.PublicKey()) + } + return signers + }() + require.Equal(t, expectedSigners, config.Signers, "expected signers to match") + expectedTransmitters := func() []ocrtypes.Account { + var accounts []ocrtypes.Account + for i := range uni1.transmitters { + t1, t2 := uni1.transmitters[i].From.Hex(), uni2.transmitters[i].From.Hex() + accounts = append(accounts, ocrtypes.Account(ocr3.JoinTransmitters([]string{t1, t2}))) + } + return accounts + }() + require.Equal(t, expectedTransmitters, config.Transmitters, "expected transmitters to match") +} diff --git a/core/services/relay/evm/ocr3/multichain_transmitter.go b/core/services/relay/evm/ocr3/multichain_transmitter.go index 26edf8ad359..b802ca88aaf 100644 --- a/core/services/relay/evm/ocr3/multichain_transmitter.go +++ b/core/services/relay/evm/ocr3/multichain_transmitter.go @@ -45,7 +45,7 @@ func (m *multichainTransmitterOCR3[RI]) FromAccount() (types.Account, error) { } accounts = append(accounts, string(account)) } - return types.Account(joinTransmitters(accounts)), nil + return types.Account(JoinTransmitters(accounts)), nil } // Transmit implements ocr3types.ContractTransmitter. diff --git a/core/services/relay/evm/ocr3/multichain_transmitter_test.go b/core/services/relay/evm/ocr3/multichain_transmitter_test.go index f790e10f7fd..53b10a7f202 100644 --- a/core/services/relay/evm/ocr3/multichain_transmitter_test.go +++ b/core/services/relay/evm/ocr3/multichain_transmitter_test.go @@ -20,7 +20,7 @@ func TestMultichainTransmitter(t *testing.T) { numChains := 4 unis := make(map[int]testUniverse[multichainMeta]) for i := 0; i < numChains; i++ { - unis[i] = newTestUniverse[multichainMeta](t) + unis[i] = newTestUniverse[multichainMeta](t, nil) } mct, err := ocr3.NewMultichainTransmitterOCR3[multichainMeta]( From b097ae8524ddb09bde7c1b17487c19729997a608 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Mon, 8 Jan 2024 13:38:01 +0200 Subject: [PATCH 16/16] goimports --- core/services/relay/evm/ocr3/contract_transmitter_test.go | 7 ++++--- .../relay/evm/ocr3/multichain_config_tracker_test.go | 5 +++-- core/services/relay/evm/ocr3/multichain_transmitter.go | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core/services/relay/evm/ocr3/contract_transmitter_test.go b/core/services/relay/evm/ocr3/contract_transmitter_test.go index d950a8808b0..2906447cf04 100644 --- a/core/services/relay/evm/ocr3/contract_transmitter_test.go +++ b/core/services/relay/evm/ocr3/contract_transmitter_test.go @@ -14,6 +14,10 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" logpollermocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" @@ -24,9 +28,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/ocr3" - "github.com/smartcontractkit/libocr/commontypes" - "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) type testUniverse[RI any] struct { diff --git a/core/services/relay/evm/ocr3/multichain_config_tracker_test.go b/core/services/relay/evm/ocr3/multichain_config_tracker_test.go index 6112554e3c8..1b9c258d218 100644 --- a/core/services/relay/evm/ocr3/multichain_config_tracker_test.go +++ b/core/services/relay/evm/ocr3/multichain_config_tracker_test.go @@ -7,6 +7,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/jmoiron/sqlx" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/require" + 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/shared/generated/no_op_ocr3" @@ -16,8 +19,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/ocr3" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/stretchr/testify/require" ) func setupLogPoller[RI any](t *testing.T, db *sqlx.DB, bs *bundlesAndSigners) (logpoller.LogPoller, testUniverse[RI]) { diff --git a/core/services/relay/evm/ocr3/multichain_transmitter.go b/core/services/relay/evm/ocr3/multichain_transmitter.go index b802ca88aaf..9ba589eaeac 100644 --- a/core/services/relay/evm/ocr3/multichain_transmitter.go +++ b/core/services/relay/evm/ocr3/multichain_transmitter.go @@ -4,10 +4,11 @@ import ( "context" "fmt" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/logger" ) type MultichainMeta interface {