From 62d31d0a6ea724e7ac00d5860d60cc93d88a3f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 13 May 2024 17:33:24 +0200 Subject: [PATCH] KeystoneForwarder updates (#12962) Co-authored-by: DeividasK --- .changeset/clever-islands-draw.md | 5 + contracts/.changeset/quiet-donkeys-hang.md | 5 + .../src/v0.8/keystone/KeystoneForwarder.sol | 241 +++++++++++++++--- .../v0.8/keystone/interfaces/IReceiver.sol | 7 + .../src/v0.8/keystone/libraries/Utils.sol | 42 --- .../keystone/test/KeystoneForwarder.t.sol | 66 ----- .../test/KeystoneForwarderBaseTest.t.sol | 53 ++++ .../test/KeystoneForwarder_ReportTest.t.sol | 132 ++++++++++ .../KeystoneForwarder_SetConfigTest.t.sol | 44 ++++ ...KeystoneForwarder_TypeAndVersionTest.t.sol | 10 + .../src/v0.8/keystone/test/mocks/Receiver.sol | 16 ++ core/capabilities/targets/write_target.go | 107 +------- .../capabilities/targets/write_target_test.go | 16 +- .../keystone/generated/forwarder/forwarder.go | 204 +++++++++++++-- ...rapper-dependency-versions-do-not-edit.txt | 2 +- core/services/relay/evm/cap_encoder.go | 22 +- core/services/relay/evm/cap_encoder_test.go | 8 +- flake.lock | 38 ++- flake.nix | 6 +- shell.nix | 6 +- 20 files changed, 738 insertions(+), 292 deletions(-) create mode 100644 .changeset/clever-islands-draw.md create mode 100644 contracts/.changeset/quiet-donkeys-hang.md create mode 100644 contracts/src/v0.8/keystone/interfaces/IReceiver.sol delete mode 100644 contracts/src/v0.8/keystone/libraries/Utils.sol delete mode 100644 contracts/src/v0.8/keystone/test/KeystoneForwarder.t.sol create mode 100644 contracts/src/v0.8/keystone/test/KeystoneForwarderBaseTest.t.sol create mode 100644 contracts/src/v0.8/keystone/test/KeystoneForwarder_ReportTest.t.sol create mode 100644 contracts/src/v0.8/keystone/test/KeystoneForwarder_SetConfigTest.t.sol create mode 100644 contracts/src/v0.8/keystone/test/KeystoneForwarder_TypeAndVersionTest.t.sol create mode 100644 contracts/src/v0.8/keystone/test/mocks/Receiver.sol diff --git a/.changeset/clever-islands-draw.md b/.changeset/clever-islands-draw.md new file mode 100644 index 00000000000..0408383bd03 --- /dev/null +++ b/.changeset/clever-islands-draw.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#internal diff --git a/contracts/.changeset/quiet-donkeys-hang.md b/contracts/.changeset/quiet-donkeys-hang.md new file mode 100644 index 00000000000..9d376e40c05 --- /dev/null +++ b/contracts/.changeset/quiet-donkeys-hang.md @@ -0,0 +1,5 @@ +--- +"@chainlink/contracts": patch +--- + +#internal diff --git a/contracts/src/v0.8/keystone/KeystoneForwarder.sol b/contracts/src/v0.8/keystone/KeystoneForwarder.sol index e6e2675fa2d..e8e574cc0bc 100644 --- a/contracts/src/v0.8/keystone/KeystoneForwarder.sol +++ b/contracts/src/v0.8/keystone/KeystoneForwarder.sol @@ -2,75 +2,232 @@ pragma solidity ^0.8.19; import {IForwarder} from "./interfaces/IForwarder.sol"; +import {IReceiver} from "./interfaces/IReceiver.sol"; import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; -import {Utils} from "./libraries/Utils.sol"; -// solhint-disable gas-custom-errors, no-unused-vars +/// @notice This is an entry point for `write_${chain}` Target capability. It +/// allows nodes to determine if reports have been processed (successfully or +/// not) in a decentralized and product-agnostic way by recording processed +/// reports. contract KeystoneForwarder is IForwarder, ConfirmedOwner, TypeAndVersionInterface { error ReentrantCall(); - /// @notice This error is returned when the data with report is invalid. - /// This can happen if the data is shorter than SELECTOR_LENGTH + REPORT_LENGTH. - /// @param data the data that was received - error InvalidData(bytes data); + /// @notice This error is returned when the report is shorter than + /// REPORT_METADATA_LENGTH, which is the minimum length of a report. + error InvalidReport(); + + /// @notice This error is thrown whenever trying to set a config with a fault + /// tolerance of 0. + error FaultToleranceMustBePositive(); + + /// @notice This error is thrown whenever configuration provides more signers + /// than the maximum allowed number. + /// @param numSigners The number of signers who have signed the report + /// @param maxSigners The maximum number of signers that can sign a report + error ExcessSigners(uint256 numSigners, uint256 maxSigners); + + /// @notice This error is thrown whenever a configuration is provided with + /// less than the minimum number of signers. + /// @param numSigners The number of signers provided + /// @param minSigners The minimum number of signers expected + error InsufficientSigners(uint256 numSigners, uint256 minSigners); + + /// @notice This error is thrown whenever a duplicate signer address is + /// provided in the configuration. + /// @param signer The signer address that was duplicated. + error DuplicateSigner(address signer); + + /// @notice This error is thrown whenever a report has an incorrect number of + /// signatures. + /// @param expected The number of signatures expected, F + 1 + /// @param received The number of signatures received + error InvalidSignatureCount(uint256 expected, uint256 received); + + /// @notice This error is thrown whenever a report specifies a DON ID that + /// does not have a configuration. + /// @param donId The DON ID that was provided in the report + error InvalidDonId(uint32 donId); + + /// @notice This error is thrown whenever a signer address is not in the + /// configuration. + /// @param signer The signer address that was not in the configuration + error InvalidSigner(address signer); + + /// @notice This error is thrown whenever a signature is invalid. + /// @param signature The signature that was invalid + error InvalidSignature(bytes signature); + + /// @notice This error is thrown whenever a report has already been processed. + /// @param reportId The ID of the report that was already processed + error ReportAlreadyProcessed(bytes32 reportId); + + bool internal s_reentrancyGuard; // guard against reentrancy + + /// @notice Contains the signing address of each oracle + struct OracleSet { + uint8 f; // Number of faulty nodes allowed + address[] signers; + mapping(address => uint256) _positions; // 1-indexed to detect unset values + } - uint256 private constant SELECTOR_LENGTH = 4; - uint256 private constant REPORT_LENGTH = 64; + /// @notice Contains the configuration for each DON ID + mapping(uint32 donId => OracleSet) internal s_configs; - struct HotVars { - bool reentrancyGuard; // guard against reentrancy + struct DeliveryStatus { + address transmitter; + bool success; } - HotVars internal s_hotVars; // Mixture of config and state, commonly accessed + mapping(bytes32 reportId => DeliveryStatus status) internal s_reports; - mapping(bytes32 => address) internal s_reports; + /// @notice Emitted when a report is processed + /// @param receiver The address of the receiver contract + /// @param workflowOwner The address of the workflow owner + /// @param workflowExecutionId The ID of the workflow execution + /// @param result The result of the attempted delivery. True if successful. + event ReportProcessed( + address indexed receiver, + address indexed workflowOwner, + bytes32 indexed workflowExecutionId, + bool result + ); constructor() ConfirmedOwner(msg.sender) {} - // send a report to targetAddress + uint256 internal constant MAX_ORACLES = 31; + // 32 bytes for workflowId, 4 bytes for donId, 32 bytes for + // workflowExecutionId, 20 bytes for workflowOwner + uint256 internal constant REPORT_METADATA_LENGTH = 88; + uint256 internal constant SIGNATURE_LENGTH = 65; + + function setConfig(uint32 donId, uint8 f, address[] calldata signers) external nonReentrant { + if (f == 0) revert FaultToleranceMustBePositive(); + if (signers.length > MAX_ORACLES) revert ExcessSigners(signers.length, MAX_ORACLES); + if (signers.length <= 3 * f) revert InsufficientSigners(signers.length, 3 * f + 1); + + // TODO: how does setConfig handle expiration? e.g. if the signer set changes + + // remove any old signer addresses + for (uint256 i; i < s_configs[donId].signers.length; ++i) { + address signer = s_configs[donId].signers[i]; + delete s_configs[donId]._positions[signer]; + } + + // add new signer addresses + s_configs[donId].signers = signers; + for (uint256 i; i < signers.length; ++i) { + // assign indices, detect duplicates + address signer = signers[i]; + if (s_configs[donId]._positions[signer] != 0) revert DuplicateSigner(signer); + s_configs[donId]._positions[signer] = uint8(i) + 1; + s_configs[donId].signers.push(signer); + } + s_configs[donId].f = f; + } + + // send a report to receiver function report( - address targetAddress, - bytes calldata data, + address receiverAddress, + bytes calldata rawReport, bytes[] calldata signatures - ) external nonReentrant returns (bool) { - if (data.length < SELECTOR_LENGTH + REPORT_LENGTH) { - revert InvalidData(data); + ) external nonReentrant { + if (rawReport.length < REPORT_METADATA_LENGTH) { + revert InvalidReport(); } - // data is an encoded call with the selector prefixed: (bytes4 selector, bytes report, ...) - // we are able to partially decode just the first param, since we don't know the rest - bytes memory rawReport = abi.decode(data[4:], (bytes)); + (bytes32 workflowId, uint32 donId, bytes32 workflowExecutionId, address workflowOwner) = _getMetadata(rawReport); - // TODO: we probably need some type of f value config? + // f can never be 0, so this means the config doesn't actually exist + if (s_configs[donId].f == 0) revert InvalidDonId(donId); - bytes32 hash = keccak256(rawReport); + bytes32 reportId = _reportId(receiverAddress, workflowExecutionId); + if (s_reports[reportId].transmitter != address(0)) revert ReportAlreadyProcessed(reportId); + + if (s_configs[donId].f + 1 != signatures.length) + revert InvalidSignatureCount(s_configs[donId].f + 1, signatures.length); // validate signatures - for (uint256 i = 0; i < signatures.length; i++) { - // TODO: is libocr-style multiple bytes32 arrays more optimal? - (bytes32 r, bytes32 s, uint8 v) = Utils._splitSignature(signatures[i]); - address signer = ecrecover(hash, v, r, s); - // TODO: we need to store oracle cluster similar to aggregator then, to validate valid signer list + { + bytes32 hash = keccak256(rawReport); + + address[MAX_ORACLES] memory signed; + uint8 index; + for (uint256 i; i < signatures.length; ++i) { + // TODO: is libocr-style multiple bytes32 arrays more optimal, gas-wise? + (bytes32 r, bytes32 s, uint8 v) = _splitSignature(signatures[i]); + address signer = ecrecover(hash, v, r, s); + + // validate signer is trusted and signature is unique + index = uint8(s_configs[donId]._positions[signer]); + if (index == 0) revert InvalidSigner(signer); // index is 1-indexed so we can detect unset signers + index -= 1; + if (signed[index] != address(0)) revert DuplicateSigner(signer); + signed[index] = signer; + } } - (bytes32 workflowId, bytes32 workflowExecutionId) = Utils._splitReport(rawReport); - - // report was already processed - if (s_reports[workflowExecutionId] != address(0)) { - return false; + IReceiver receiver = IReceiver(receiverAddress); + bool success; + try receiver.onReport(workflowId, workflowOwner, rawReport[REPORT_METADATA_LENGTH:]) { + success = true; + } catch { + // Do nothing, success is already false } - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory result) = targetAddress.call(data); + s_reports[reportId] = DeliveryStatus(msg.sender, success); - s_reports[workflowExecutionId] = msg.sender; - return true; + emit ReportProcessed(receiverAddress, workflowOwner, workflowExecutionId, success); + } + + function _reportId(address receiver, bytes32 workflowExecutionId) internal pure returns (bytes32) { + // TODO: gas savings: could we just use a bytes key and avoid another keccak256 call + return keccak256(bytes.concat(bytes20(uint160(receiver)), workflowExecutionId)); } // get transmitter of a given report or 0x0 if it wasn't transmitted yet - function getTransmitter(bytes32 workflowExecutionId) external view returns (address) { - return s_reports[workflowExecutionId]; + function getTransmitter(address receiver, bytes32 workflowExecutionId) external view returns (address) { + bytes32 reportId = _reportId(receiver, workflowExecutionId); + return s_reports[reportId].transmitter; + } + + function _splitSignature(bytes memory sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) { + if (sig.length != SIGNATURE_LENGTH) revert InvalidSignature(sig); + + assembly { + /* + First 32 bytes stores the length of the signature + + add(sig, 32) = pointer of sig + 32 + effectively, skips first 32 bytes of signature + + mload(p) loads next 32 bytes starting at the memory address p into memory + */ + + // first 32 bytes, after the length prefix + r := mload(add(sig, 32)) + // second 32 bytes + s := mload(add(sig, 64)) + // final byte (first byte of the next 32 bytes) + v := byte(0, mload(add(sig, 96))) + } + } + + function _getMetadata( + bytes memory rawReport + ) internal pure returns (bytes32 workflowId, uint32 donId, bytes32 workflowExecutionId, address workflowOwner) { + assembly { + // skip first 32 bytes, contains length of the report + // first 32 bytes is the workflowId + workflowId := mload(add(rawReport, 32)) + // next 4 bytes is donId. We shift right by 28 bytes to get the actual value + donId := shr(mul(28, 8), mload(add(rawReport, 64))) + // next 32 bytes is the workflowExecutionId + workflowExecutionId := mload(add(rawReport, 68)) + // next 20 bytes is the workflowOwner. We shift right by 12 bytes to get + // the actual value + workflowOwner := shr(mul(12, 8), mload(add(rawReport, 100))) + } } /// @inheritdoc TypeAndVersionInterface @@ -82,9 +239,9 @@ contract KeystoneForwarder is IForwarder, ConfirmedOwner, TypeAndVersionInterfac * @dev replicates Open Zeppelin's ReentrancyGuard but optimized to fit our storage */ modifier nonReentrant() { - if (s_hotVars.reentrancyGuard) revert ReentrantCall(); - s_hotVars.reentrancyGuard = true; + if (s_reentrancyGuard) revert ReentrantCall(); + s_reentrancyGuard = true; _; - s_hotVars.reentrancyGuard = false; + s_reentrancyGuard = false; } } diff --git a/contracts/src/v0.8/keystone/interfaces/IReceiver.sol b/contracts/src/v0.8/keystone/interfaces/IReceiver.sol new file mode 100644 index 00000000000..96a68ec97f2 --- /dev/null +++ b/contracts/src/v0.8/keystone/interfaces/IReceiver.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/// @title IReceiver - receives keystone reports +interface IReceiver { + function onReport(bytes32 workflowId, address workflowOwner, bytes calldata report) external; +} diff --git a/contracts/src/v0.8/keystone/libraries/Utils.sol b/contracts/src/v0.8/keystone/libraries/Utils.sol deleted file mode 100644 index 66e2635b908..00000000000 --- a/contracts/src/v0.8/keystone/libraries/Utils.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -// solhint-disable gas-custom-errors -library Utils { - // solhint-disable avoid-low-level-calls, chainlink-solidity/explicit-returns - function _splitSignature(bytes memory sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) { - require(sig.length == 65, "invalid signature length"); - - assembly { - /* - First 32 bytes stores the length of the signature - - add(sig, 32) = pointer of sig + 32 - effectively, skips first 32 bytes of signature - - mload(p) loads next 32 bytes starting at the memory address p into memory - */ - - // first 32 bytes, after the length prefix - r := mload(add(sig, 32)) - // second 32 bytes - s := mload(add(sig, 64)) - // final byte (first byte of the next 32 bytes) - v := byte(0, mload(add(sig, 96))) - } - - // implicitly return (r, s, v) - } - - // solhint-disable avoid-low-level-calls, chainlink-solidity/explicit-returns - function _splitReport( - bytes memory rawReport - ) internal pure returns (bytes32 workflowId, bytes32 workflowExecutionId) { - require(rawReport.length > 64, "invalid report length"); - assembly { - // skip first 32 bytes, contains length of the report - workflowId := mload(add(rawReport, 32)) - workflowExecutionId := mload(add(rawReport, 64)) - } - } -} diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarder.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarder.t.sol deleted file mode 100644 index 10d4cb3f9c2..00000000000 --- a/contracts/src/v0.8/keystone/test/KeystoneForwarder.t.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import "forge-std/Test.sol"; - -import "../KeystoneForwarder.sol"; -import {Utils} from "../libraries/Utils.sol"; - -contract Receiver { - event MessageReceived(bytes32 indexed workflowId, bytes32 indexed workflowExecutionId, bytes[] mercuryReports); - - constructor() {} - - function foo(bytes calldata rawReport) external { - // decode metadata - (bytes32 workflowId, bytes32 workflowExecutionId) = Utils._splitReport(rawReport); - // parse actual report - bytes[] memory mercuryReports = abi.decode(rawReport[64:], (bytes[])); - emit MessageReceived(workflowId, workflowExecutionId, mercuryReports); - } -} - -contract KeystoneForwarderTest is Test { - function setUp() public virtual {} - - function test_abi_partial_decoding_works() public { - bytes memory report = hex"0102"; - uint256 amount = 1; - bytes memory payload = abi.encode(report, amount); - bytes memory decodedReport = abi.decode(payload, (bytes)); - assertEq(decodedReport, report, "not equal"); - } - - function test_it_works() public { - KeystoneForwarder forwarder = new KeystoneForwarder(); - Receiver receiver = new Receiver(); - - // taken from https://github.com/smartcontractkit/chainlink/blob/2390ec7f3c56de783ef4e15477e99729f188c524/core/services/relay/evm/cap_encoder_test.go#L42-L55 - bytes - memory report = hex"6d795f69640000000000000000000000000000000000000000000000000000006d795f657865637574696f6e5f696400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000301020300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004aabbccdd00000000000000000000000000000000000000000000000000000000"; - bytes memory data = abi.encodeWithSignature("foo(bytes)", report); - bytes[] memory signatures = new bytes[](0); - - vm.expectCall(address(receiver), data); - vm.recordLogs(); - - bool delivered1 = forwarder.report(address(receiver), data, signatures); - assertTrue(delivered1, "report not delivered"); - - Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries[0].emitter, address(receiver)); - // validate workflow id and workflow execution id - bytes32 workflowId = hex"6d795f6964000000000000000000000000000000000000000000000000000000"; - bytes32 executionId = hex"6d795f657865637574696f6e5f69640000000000000000000000000000000000"; - assertEq(entries[0].topics[1], workflowId); - assertEq(entries[0].topics[2], executionId); - bytes[] memory mercuryReports = abi.decode(entries[0].data, (bytes[])); - assertEq(mercuryReports.length, 2); - assertEq(mercuryReports[0], hex"010203"); - assertEq(mercuryReports[1], hex"aabbccdd"); - - // doesn't deliver the same report more than once - bool delivered2 = forwarder.report(address(receiver), data, signatures); - assertFalse(delivered2, "report redelivered"); - } -} diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarderBaseTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarderBaseTest.t.sol new file mode 100644 index 00000000000..d2801abb5af --- /dev/null +++ b/contracts/src/v0.8/keystone/test/KeystoneForwarderBaseTest.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {Receiver} from "./mocks/Receiver.sol"; +import {KeystoneForwarder} from "../KeystoneForwarder.sol"; + +contract BaseTest is Test { + address internal ADMIN = address(1); + address internal constant TRANSMITTER = address(50); + uint256 internal constant MAX_ORACLES = 31; + uint32 internal DON_ID = 0x01020304; + uint8 internal F = 1; + + struct Signer { + uint256 mockPrivateKey; + address signerAddress; + } + + Signer[MAX_ORACLES] internal s_signers; + KeystoneForwarder internal s_forwarder; + Receiver internal s_receiver; + + function setUp() public virtual { + vm.startPrank(ADMIN); + s_forwarder = new KeystoneForwarder(); + s_receiver = new Receiver(); + + uint256 seed = 0; + + for (uint256 i; i < MAX_ORACLES; i++) { + uint256 mockPK = seed + i + 1; + s_signers[i].mockPrivateKey = mockPK; + s_signers[i].signerAddress = vm.addr(mockPK); + } + } + + function _getSignerAddresses() internal view returns (address[] memory) { + address[] memory signerAddrs = new address[](s_signers.length); + for (uint256 i = 0; i < signerAddrs.length; i++) { + signerAddrs[i] = s_signers[i].signerAddress; + } + return signerAddrs; + } + + function _getSignerAddresses(uint256 limit) internal view returns (address[] memory) { + address[] memory signerAddrs = new address[](limit); + for (uint256 i = 0; i < limit; i++) { + signerAddrs[i] = s_signers[i].signerAddress; + } + return signerAddrs; + } +} diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarder_ReportTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarder_ReportTest.t.sol new file mode 100644 index 00000000000..eecaf4fb241 --- /dev/null +++ b/contracts/src/v0.8/keystone/test/KeystoneForwarder_ReportTest.t.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseTest} from "./KeystoneForwarderBaseTest.t.sol"; +import {KeystoneForwarder} from "../KeystoneForwarder.sol"; + +contract KeystoneForwarder_ReportTest is BaseTest { + event MessageReceived(bytes32 indexed workflowId, address indexed workflowOwner, bytes[] mercuryReports); + event ReportProcessed( + address indexed receiver, + address indexed workflowOwner, + bytes32 indexed workflowExecutionId, + bool result + ); + + bytes32 internal workflowId = hex"6d795f6964000000000000000000000000000000000000000000000000000000"; + address internal workflowOwner = address(51); + bytes32 internal executionId = hex"6d795f657865637574696f6e5f69640000000000000000000000000000000000"; + bytes[] internal mercuryReports = new bytes[](2); + bytes internal rawReports; + bytes internal report; + uint256 internal requiredSignaturesNum = F + 1; + bytes[] internal signatures = new bytes[](2); + + function setUp() public override { + BaseTest.setUp(); + + s_forwarder.setConfig(DON_ID, F, _getSignerAddresses()); + + mercuryReports[0] = hex"010203"; + mercuryReports[1] = hex"aabbccdd"; + + rawReports = abi.encode(mercuryReports); + report = abi.encodePacked(workflowId, DON_ID, executionId, workflowOwner, rawReports); + + for (uint256 i = 0; i < requiredSignaturesNum; i++) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(s_signers[i].mockPrivateKey, keccak256(report)); + signatures[i] = bytes.concat(r, s, bytes1(v)); + } + + vm.startPrank(TRANSMITTER); + } + + function test_RevertWhen_ReportHasIncorrectDON() public { + uint32 invalidDONId = 111; + bytes memory reportWithInvalidDONId = abi.encodePacked( + workflowId, + invalidDONId, + executionId, + workflowOwner, + rawReports + ); + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.InvalidDonId.selector, invalidDONId)); + s_forwarder.report(address(s_receiver), reportWithInvalidDONId, signatures); + } + + function test_RevertWhen_ReportIsMalformed() public { + bytes memory shortenedReport = abi.encode(bytes32(report)); + + vm.expectRevert(KeystoneForwarder.InvalidReport.selector); + s_forwarder.report(address(s_receiver), shortenedReport, signatures); + } + + function test_RevertWhen_TooFewSignatures() public { + bytes[] memory fewerSignatures = new bytes[](F); + + vm.expectRevert( + abi.encodeWithSelector(KeystoneForwarder.InvalidSignatureCount.selector, F + 1, fewerSignatures.length) + ); + s_forwarder.report(address(s_receiver), report, fewerSignatures); + } + + function test_RevertWhen_TooManySignatures() public { + bytes[] memory moreSignatures = new bytes[](F + 2); + + vm.expectRevert( + abi.encodeWithSelector(KeystoneForwarder.InvalidSignatureCount.selector, F + 1, moreSignatures.length) + ); + s_forwarder.report(address(s_receiver), report, moreSignatures); + } + + function test_RevertWhen_AnySignatureIsInvalid() public { + signatures[1] = abi.encode(1234); // invalid signature + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.InvalidSignature.selector, signatures[1])); + s_forwarder.report(address(s_receiver), report, signatures); + } + + function test_RevertWhen_AnySignerIsInvalid() public { + uint256 mockPK = 999; + + Signer memory maliciousSigner = Signer({mockPrivateKey: mockPK, signerAddress: vm.addr(mockPK)}); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(maliciousSigner.mockPrivateKey, keccak256(report)); + signatures[1] = bytes.concat(r, s, bytes1(v)); + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.InvalidSigner.selector, maliciousSigner.signerAddress)); + s_forwarder.report(address(s_receiver), report, signatures); + } + + function test_RevertWhen_ReportHasDuplicateSignatures() public { + signatures[1] = signatures[0]; // repeat a signature + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.DuplicateSigner.selector, s_signers[0].signerAddress)); + s_forwarder.report(address(s_receiver), report, signatures); + } + + function test_RevertWhen_ReportAlreadyProcessed() public { + s_forwarder.report(address(s_receiver), report, signatures); + bytes32 reportId = keccak256(bytes.concat(bytes20(uint160(address(s_receiver))), executionId)); + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.ReportAlreadyProcessed.selector, reportId)); + s_forwarder.report(address(s_receiver), report, signatures); + } + + function test_Report_SuccessfulDelivery() public { + // taken from https://github.com/smartcontractkit/chainlink/blob/2390ec7f3c56de783ef4e15477e99729f188c524/core/services/relay/evm/cap_encoder_test.go#L42-L55 + // bytes memory report = hex"6d795f6964000000000000000000000000000000000000000000000000000000010203046d795f657865637574696f6e5f696400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000301020300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004aabbccdd00000000000000000000000000000000000000000000000000000000"; + + vm.expectEmit(address(s_receiver)); + emit MessageReceived(workflowId, workflowOwner, mercuryReports); + + vm.expectEmit(address(s_forwarder)); + emit ReportProcessed(address(s_receiver), workflowOwner, executionId, true); + + s_forwarder.report(address(s_receiver), report, signatures); + + // validate transmitter was recorded + address transmitter = s_forwarder.getTransmitter(address(s_receiver), executionId); + assertEq(transmitter, TRANSMITTER, "transmitter mismatch"); + } +} diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarder_SetConfigTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarder_SetConfigTest.t.sol new file mode 100644 index 00000000000..0211b3e4027 --- /dev/null +++ b/contracts/src/v0.8/keystone/test/KeystoneForwarder_SetConfigTest.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseTest} from "./KeystoneForwarderBaseTest.t.sol"; +import {KeystoneForwarder} from "../KeystoneForwarder.sol"; + +contract KeystoneForwarder_SetConfigTest is BaseTest { + function test_RevertWhen_FaultToleranceIsZero() public { + vm.expectRevert(KeystoneForwarder.FaultToleranceMustBePositive.selector); + s_forwarder.setConfig(DON_ID, 0, _getSignerAddresses()); + } + + function test_RevertWhen_InsufficientSigners() public { + address[] memory signers = new address[](1); + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.InsufficientSigners.selector, 1, 4)); + s_forwarder.setConfig(DON_ID, F, signers); + } + + function test_RevertWhen_ExcessSigners() public { + address[] memory signers = new address[](64); + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.ExcessSigners.selector, 64, 31)); + s_forwarder.setConfig(DON_ID, F, signers); + } + + function test_RevertWhen_ProvidingDuplicateSigners() public { + address[] memory signers = _getSignerAddresses(); + signers[1] = signers[0]; + + vm.expectRevert(abi.encodeWithSelector(KeystoneForwarder.DuplicateSigner.selector, signers[0])); + s_forwarder.setConfig(DON_ID, F, signers); + } + + function test_SetConfig_FirstTime() public { + s_forwarder.setConfig(DON_ID, F, _getSignerAddresses()); + } + + function test_SetConfig_WhenSignersAreRemoved() public { + s_forwarder.setConfig(DON_ID, F, _getSignerAddresses()); + + s_forwarder.setConfig(DON_ID, F, _getSignerAddresses(16)); + } +} diff --git a/contracts/src/v0.8/keystone/test/KeystoneForwarder_TypeAndVersionTest.t.sol b/contracts/src/v0.8/keystone/test/KeystoneForwarder_TypeAndVersionTest.t.sol new file mode 100644 index 00000000000..1d8d7572e0c --- /dev/null +++ b/contracts/src/v0.8/keystone/test/KeystoneForwarder_TypeAndVersionTest.t.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseTest} from "./KeystoneForwarderBaseTest.t.sol"; + +contract KeystoneForwarder_TypeAndVersionTest is BaseTest { + function test_TypeAndVersion() public view { + assertEq(s_forwarder.typeAndVersion(), "KeystoneForwarder 1.0.0"); + } +} diff --git a/contracts/src/v0.8/keystone/test/mocks/Receiver.sol b/contracts/src/v0.8/keystone/test/mocks/Receiver.sol new file mode 100644 index 00000000000..5d2f646445b --- /dev/null +++ b/contracts/src/v0.8/keystone/test/mocks/Receiver.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IReceiver} from "../../interfaces/IReceiver.sol"; + +contract Receiver is IReceiver { + event MessageReceived(bytes32 indexed workflowId, address indexed workflowOwner, bytes[] mercuryReports); + + constructor() {} + + function onReport(bytes32 workflowId, address workflowOwner, bytes calldata rawReport) external { + // parse actual report + bytes[] memory mercuryReports = abi.decode(rawReport, (bytes[])); + emit MessageReceived(workflowId, workflowOwner, mercuryReports); + } +} diff --git a/core/capabilities/targets/write_target.go b/core/capabilities/targets/write_target.go index 3349e05003e..7dd8a3cca89 100644 --- a/core/capabilities/targets/write_target.go +++ b/core/capabilities/targets/write_target.go @@ -2,14 +2,10 @@ package targets import ( "context" - "encoding/json" "fmt" - "strings" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" chainselectors "github.com/smartcontractkit/chain-selectors" @@ -17,13 +13,11 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/core" "github.com/smartcontractkit/chainlink-common/pkg/values" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - abiutil "github.com/smartcontractkit/chainlink/v2/core/chains/evm/abi" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" "github.com/smartcontractkit/chainlink/v2/core/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" ) var forwardABI = evmtypes.MustGetABI(forwarder.KeystoneForwarderMetaData.ABI) @@ -76,8 +70,6 @@ func NewEvmWrite(chain legacyevm.Chain, lggr logger.Logger) *EvmWrite { type EvmConfig struct { ChainID uint Address string - Params []any - ABI string } // TODO: enforce required key presence @@ -92,77 +84,10 @@ func parseConfig(rawConfig *values.Map) (EvmConfig, error) { return config, err } -func evaluateParams(params []any, inputs map[string]any) ([]any, error) { - vars := pipeline.NewVarsFrom(inputs) - var args []any - for _, param := range params { - switch v := param.(type) { - case string: - val, err := pipeline.VarExpr(v, vars)() - if err == nil { - args = append(args, val) - } else if errors.Is(errors.Cause(err), pipeline.ErrParameterEmpty) { - args = append(args, param) - } else { - return args, err - } - default: - args = append(args, param) - } - } - - return args, nil -} - -func encodePayload(args []any, rawSelector string) ([]byte, error) { - // TODO: do spec parsing as part of parseConfig() - - // Based on https://github.com/ethereum/go-ethereum/blob/f1c27c286ea2d0e110a507e5749e92d0a6144f08/signer/fourbyte/abi.go#L77-L102 - - // NOTE: without having full ABI it's actually impossible to support function overloading - selector, err := abiutil.ParseSignature(rawSelector) - if err != nil { - return nil, err - } - - abidata, err := json.Marshal([]abi.SelectorMarshaling{selector}) - if err != nil { - return nil, err - } - - spec, err := abi.JSON(strings.NewReader(string(abidata))) - if err != nil { - return nil, err - } - - return spec.Pack(selector.Name, args...) - - // NOTE: could avoid JSON encoding/decoding the selector - // var args abi.Arguments - // for _, arg := range selector.Inputs { - // ty, err := abi.NewType(arg.Type, arg.InternalType, arg.Components) - // if err != nil { - // return nil, err - // } - // args = append(args, abi.Argument{Name: arg.Name, Type: ty}) - // } - // // we only care about the name + inputs so we can compute the method ID - // method := abi.NewMethod(selector.Name, selector.Name, abi.Function, "nonpayable", false, false, args, nil) - // - // https://github.com/ethereum/go-ethereum/blob/f1c27c286ea2d0e110a507e5749e92d0a6144f08/accounts/abi/abi.go#L77-L82 - // arguments, err := method.Inputs.Pack(args...) - // if err != nil { - // return nil, err - // } - // // Pack up the method ID too if not a constructor and return - // return append(method.ID, arguments...), nil -} - func (cap *EvmWrite) Execute(ctx context.Context, request capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { cap.lggr.Debugw("Execute", "request", request) // TODO: idempotency - // TODO: extract into ChainWriter? txm := cap.chain.TxManager() config := cap.chain.Config().EVM().ChainWriter() @@ -172,17 +97,15 @@ func (cap *EvmWrite) Execute(ctx context.Context, request capabilities.Capabilit return nil, err } - inputsAny, err := request.Inputs.Unwrap() - if err != nil { - return nil, err + var inputs struct { + Report []byte + Signatures [][]byte } - inputs := inputsAny.(map[string]any) - rep, ok := inputs["report"] - if !ok { - return nil, errors.New("malformed data: inputs doesn't contain a report key") + if err = request.Inputs.UnwrapTo(&inputs); err != nil { + return nil, err } - if rep == nil { + if inputs.Report == nil { // We received any empty report -- this means we should skip transmission. cap.lggr.Debugw("Skipping empty report", "request", request) callback := make(chan capabilities.CapabilityResponse) @@ -197,24 +120,10 @@ func (cap *EvmWrite) Execute(ctx context.Context, request capabilities.Capabilit return callback, nil } - // evaluate any variables in reqConfig.Params - args, err := evaluateParams(reqConfig.Params, inputs) - if err != nil { - return nil, err - } - - data, err := encodePayload(args, reqConfig.ABI) - if err != nil { - return nil, err - } - // TODO: validate encoded report is prefixed with workflowID and executionID that match the request meta - // No signature validation in the MVP demo - signatures := [][]byte{} - - // construct forwarding payload - calldata, err := forwardABI.Pack("report", common.HexToAddress(reqConfig.Address), data, signatures) + // construct forwarder payload + calldata, err := forwardABI.Pack("report", common.HexToAddress(reqConfig.Address), inputs.Report, inputs.Signatures) if err != nil { return nil, err } diff --git a/core/capabilities/targets/write_target_test.go b/core/capabilities/targets/write_target_test.go index 96da502f10d..f994623ede3 100644 --- a/core/capabilities/targets/write_target_test.go +++ b/core/capabilities/targets/write_target_test.go @@ -48,14 +48,12 @@ func TestEvmWrite(t *testing.T) { capability := targets.NewEvmWrite(chain, logger.TestLogger(t)) ctx := testutils.Context(t) - config, err := values.NewMap(map[string]any{ - "abi": "receive(report bytes)", - "params": []any{"$(report)"}, - }) + config, err := values.NewMap(map[string]any{}) require.NoError(t, err) inputs, err := values.NewMap(map[string]any{ - "report": []byte{1, 2, 3}, + "report": []byte{1, 2, 3}, + "signatures": [][]byte{}, }) require.NoError(t, err) @@ -73,12 +71,8 @@ func TestEvmWrite(t *testing.T) { method := forwardABI.Methods["report"] err = method.Inputs.UnpackIntoMap(payload, req.EncodedPayload[4:]) require.NoError(t, err) - require.Equal(t, []byte{ - 0xa6, 0x9b, 0x6e, 0xd0, // selector = keccak(signature)[:4] - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, // type = bytes - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, // len = 3 - 0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // elements [1, 2, 3] zero padded - }, payload["data"]) + require.Equal(t, []byte{0x1, 0x2, 0x3}, payload["rawReport"]) + require.Equal(t, [][]byte{}, payload["signatures"]) }) ch, err := capability.Execute(ctx, req) diff --git a/core/gethwrappers/keystone/generated/forwarder/forwarder.go b/core/gethwrappers/keystone/generated/forwarder/forwarder.go index c8cf31ae869..fbdd024c64f 100644 --- a/core/gethwrappers/keystone/generated/forwarder/forwarder.go +++ b/core/gethwrappers/keystone/generated/forwarder/forwarder.go @@ -31,8 +31,8 @@ var ( ) var KeystoneForwarderMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"InvalidData\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReentrantCall\",\"type\":\"error\"},{\"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\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"}],\"name\":\"getTransmitter\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"targetAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"}],\"name\":\"report\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b610c5f806101576000396000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c8063c0965dc311610050578063c0965dc314610108578063e6b714581461012b578063f2fde38b1461016157600080fd5b8063181f5a771461007757806379ba5097146100bf5780638da5cb5b146100c9575b600080fd5b604080518082018252601781527f4b657973746f6e65466f7277617264657220312e302e30000000000000000000602082015290516100b69190610806565b60405180910390f35b6100c7610174565b005b60005473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b6565b61011b61011636600461089b565b610276565b60405190151581526020016100b6565b6100e3610139366004610977565b60009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b6100c761016f366004610990565b61056d565b60015473ffffffffffffffffffffffffffffffffffffffff1633146101fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60025460009060ff16156102b6576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790556102ed604060046109e1565b84101561032a5784846040517f2a62609b0000000000000000000000000000000000000000000000000000000081526004016101f19291906109fa565b60006103398560048189610a47565b8101906103469190610aa0565b8051602082012090915060005b848110156104445760008060006103c189898681811061037557610375610b6f565b90506020028101906103879190610b9e565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061058192505050565b925092509250600060018683868660405160008152602001604052604051610405949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015610427573d6000803e3d6000fd5b5086955061043c9450859350610c0a92505050565b915050610353565b506000806104518461060f565b600081815260036020526040902054919350915073ffffffffffffffffffffffffffffffffffffffff161561048d57600094505050505061053c565b6000808b73ffffffffffffffffffffffffffffffffffffffff168b8b6040516104b7929190610c42565b6000604051808303816000865af19150503d80600081146104f4576040519150601f19603f3d011682016040523d82523d6000602084013e6104f9565b606091505b5050506000928352505060036020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001633179055506001925050505b600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905595945050505050565b61057561068e565b61057e81610711565b50565b600080600083516041146105f1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f696e76616c6964207369676e6174757265206c656e677468000000000000000060448201526064016101f1565b50505060208101516040820151606090920151909260009190911a90565b600080604083511161067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f696e76616c6964207265706f7274206c656e677468000000000000000000000060448201526064016101f1565b505060208101516040909101519091565b60005473ffffffffffffffffffffffffffffffffffffffff16331461070f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016101f1565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610790576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016101f1565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208083528351808285015260005b8181101561083357858101830151858201604001528201610817565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461089657600080fd5b919050565b6000806000806000606086880312156108b357600080fd5b6108bc86610872565b9450602086013567ffffffffffffffff808211156108d957600080fd5b818801915088601f8301126108ed57600080fd5b8135818111156108fc57600080fd5b89602082850101111561090e57600080fd5b60208301965080955050604088013591508082111561092c57600080fd5b818801915088601f83011261094057600080fd5b81358181111561094f57600080fd5b8960208260051b850101111561096457600080fd5b9699959850939650602001949392505050565b60006020828403121561098957600080fd5b5035919050565b6000602082840312156109a257600080fd5b6109ab82610872565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156109f4576109f46109b2565b92915050565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b60008085851115610a5757600080fd5b83861115610a6457600080fd5b5050820193919092039150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600060208284031215610ab257600080fd5b813567ffffffffffffffff80821115610aca57600080fd5b818401915084601f830112610ade57600080fd5b813581811115610af057610af0610a71565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715610b3657610b36610a71565b81604052828152876020848701011115610b4f57600080fd5b826020860160208301376000928101602001929092525095945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610bd357600080fd5b83018035915067ffffffffffffffff821115610bee57600080fd5b602001915036819003821315610c0357600080fd5b9250929050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610c3b57610c3b6109b2565b5060010190565b818382376000910190815291905056fea164736f6c6343000813000a", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"}],\"name\":\"DuplicateSigner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxSigners\",\"type\":\"uint256\"}],\"name\":\"ExcessSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FaultToleranceMustBePositive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"numSigners\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSigners\",\"type\":\"uint256\"}],\"name\":\"InsufficientSigners\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"}],\"name\":\"InvalidDonId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidReport\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"received\",\"type\":\"uint256\"}],\"name\":\"InvalidSignatureCount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"}],\"name\":\"InvalidSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReentrantCall\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"reportId\",\"type\":\"bytes32\"}],\"name\":\"ReportAlreadyProcessed\",\"type\":\"error\"},{\"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\":true,\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"result\",\"type\":\"bool\"}],\"name\":\"ReportProcessed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"workflowExecutionId\",\"type\":\"bytes32\"}],\"name\":\"getTransmitter\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiverAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"rawReport\",\"type\":\"bytes\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"}],\"name\":\"report\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"donId\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b61151d806101576000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c806379ba50971161005b57806379ba5097146101175780638da5cb5b1461011f578063c0965dc31461013d578063f2fde38b1461015057600080fd5b8063134a46f014610082578063181f5a7714610097578063390d0b15146100df575b600080fd5b610095610090366004611106565b610163565b005b604080518082018252601781527f4b657973746f6e65466f7277617264657220312e302e30000000000000000000602082015290516100d691906111de565b60405180910390f35b6100f26100ed366004611221565b61057d565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100d6565b61009561060e565b60005473ffffffffffffffffffffffffffffffffffffffff166100f2565b61009561014b36600461124b565b61070b565b61009561015e3660046112fa565b610ded565b60015474010000000000000000000000000000000000000000900460ff16156101b8576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000017905560ff8316600003610234576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f81111561027e576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101829052601f60248201526044015b60405180910390fd5b610289836003611344565b60ff1681116102e7578061029e846003611344565b6102a9906001611367565b6040517f9dd9e6d8000000000000000000000000000000000000000000000000000000008152600481019290925260ff166024820152604401610275565b60005b63ffffffff85166000908152600260205260409020600101548110156103885763ffffffff8516600090815260026020526040812060010180548390811061033457610334611380565b600091825260208083209091015463ffffffff891683526002808352604080852073ffffffffffffffffffffffffffffffffffffffff9093168552910190915281205550610381816113af565b90506102ea565b5063ffffffff841660009081526002602052604090206103ac906001018383610ffe565b5060005b8181101561050a5760008383838181106103cc576103cc611380565b90506020020160208101906103e191906112fa565b63ffffffff8716600090815260026020818152604080842073ffffffffffffffffffffffffffffffffffffffff8616855290920190529020549091501561046c576040517fe021c4f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610275565b610477826001611367565b63ffffffff8716600090815260026020818152604080842073ffffffffffffffffffffffffffffffffffffffff909616808552868401835290842060ff959095169094559081526001938401805494850181558252902090910180547fffffffffffffffffffffffff0000000000000000000000000000000000000000169091179055610503816113af565b90506103b0565b50505063ffffffff91909116600090815260026020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff169055565b6000806105df84846040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606084901b1660208201526034810182905260009060540160405160208183030381529060405280519060200120905092915050565b60009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff169150505b92915050565b60015473ffffffffffffffffffffffffffffffffffffffff16331461068f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610275565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60015474010000000000000000000000000000000000000000900460ff1615610760576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000017905560588310156107da576040517fb55ac75400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060008061081f88888080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610e0192505050565b63ffffffff8316600090815260026020526040812054949850929650909450925060ff9091169003610885576040517fea1b312900000000000000000000000000000000000000000000000000000000815263ffffffff84166004820152602401610275565b604080517fffffffffffffffffffffffffffffffffffffffff00000000000000000000000060608c901b1660208083019190915260348083018690528351808403909101815260549092018352815191810191909120600081815260039092529190205473ffffffffffffffffffffffffffffffffffffffff1615610939576040517f1aac3d2900000000000000000000000000000000000000000000000000000000815260048101829052602401610275565b63ffffffff8416600090815260026020526040902054869061095f9060ff166001611367565b60ff16146109ca5763ffffffff841660009081526002602052604090205461098b9060ff166001611367565b6040517fd6022e8e00000000000000000000000000000000000000000000000000000000815260ff909116600482015260248101879052604401610275565b600089896040516109dc9291906113e7565b604051809103902090506109ee611086565b6000805b89811015610c5c576000806000610a608e8e86818110610a1457610a14611380565b9050602002810190610a2691906113f7565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610e2692505050565b925092509250600060018883868660405160008152602001604052604051610aa4949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015610ac6573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015163ffffffff8f1660009081526002602081815284832073ffffffffffffffffffffffffffffffffffffffff851684529091019052918220549850925060ff881690039050610b81576040517fbf18af4300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610275565b610b8c60018761145c565b955060008760ff8816601f8110610ba557610ba5611380565b602002015173ffffffffffffffffffffffffffffffffffffffff1614610c0f576040517fe021c4f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610275565b80878760ff16601f8110610c2557610c25611380565b73ffffffffffffffffffffffffffffffffffffffff909216602092909202015250610c5592508391506113af9050565b90506109f2565b5050505060008a905060008173ffffffffffffffffffffffffffffffffffffffff1663ff5a027088868e8e6058908092610c9893929190611475565b6040518563ffffffff1660e01b8152600401610cb7949392919061149f565b600060405180830381600087803b158015610cd157600080fd5b505af1925050508015610ce2575060015b15610ceb575060015b604080518082018252338152821515602080830191825260008781526003909152839020915182549151151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00000000000000000000000000000000000000000090921673ffffffffffffffffffffffffffffffffffffffff91821617919091179091559051869186811691908f16907fdae8e752043eb5fc7e4a6eced57ceaf159548b630125ece9ffc41cfc952c208190610daf90861515815260200190565b60405180910390a45050600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16905550505050505050505050565b610df5610e86565b610dfe81610f09565b50565b602081015160408201516044830151606490930151919360e09190911c929160601c90565b60008060006041845114610e6857836040517f2adfdc3000000000000000000000000000000000000000000000000000000000815260040161027591906111de565b50505060208101516040820151606090920151909260009190911a90565b60005473ffffffffffffffffffffffffffffffffffffffff163314610f07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610275565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610f88576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610275565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b828054828255906000526020600020908101928215611076579160200282015b828111156110765781547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84351617825560209092019160019091019061101e565b506110829291506110a5565b5090565b604051806103e00160405280601f906020820280368337509192915050565b5b8082111561108257600081556001016110a6565b60008083601f8401126110cc57600080fd5b50813567ffffffffffffffff8111156110e457600080fd5b6020830191508360208260051b85010111156110ff57600080fd5b9250929050565b6000806000806060858703121561111c57600080fd5b843563ffffffff8116811461113057600080fd5b9350602085013560ff8116811461114657600080fd5b9250604085013567ffffffffffffffff81111561116257600080fd5b61116e878288016110ba565b95989497509550505050565b6000815180845260005b818110156111a057602081850181015186830182015201611184565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815260006111f1602083018461117a565b9392505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461121c57600080fd5b919050565b6000806040838503121561123457600080fd5b61123d836111f8565b946020939093013593505050565b60008060008060006060868803121561126357600080fd5b61126c866111f8565b9450602086013567ffffffffffffffff8082111561128957600080fd5b818801915088601f83011261129d57600080fd5b8135818111156112ac57600080fd5b8960208285010111156112be57600080fd5b6020830196508095505060408801359150808211156112dc57600080fd5b506112e9888289016110ba565b969995985093965092949392505050565b60006020828403121561130c57600080fd5b6111f1826111f8565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60ff818116838216029081169081811461136057611360611315565b5092915050565b60ff818116838216019081111561060857610608611315565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036113e0576113e0611315565b5060010190565b8183823760009101908152919050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261142c57600080fd5b83018035915067ffffffffffffffff82111561144757600080fd5b6020019150368190038213156110ff57600080fd5b60ff828116828216039081111561060857610608611315565b6000808585111561148557600080fd5b8386111561149257600080fd5b5050820193919092039150565b84815273ffffffffffffffffffffffffffffffffffffffff8416602082015260606040820152816060820152818360808301376000818301608090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101939250505056fea164736f6c6343000813000a", } var KeystoneForwarderABI = KeystoneForwarderMetaData.ABI @@ -171,9 +171,9 @@ func (_KeystoneForwarder *KeystoneForwarderTransactorRaw) Transact(opts *bind.Tr return _KeystoneForwarder.Contract.contract.Transact(opts, method, params...) } -func (_KeystoneForwarder *KeystoneForwarderCaller) GetTransmitter(opts *bind.CallOpts, workflowExecutionId [32]byte) (common.Address, error) { +func (_KeystoneForwarder *KeystoneForwarderCaller) GetTransmitter(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte) (common.Address, error) { var out []interface{} - err := _KeystoneForwarder.contract.Call(opts, &out, "getTransmitter", workflowExecutionId) + err := _KeystoneForwarder.contract.Call(opts, &out, "getTransmitter", receiver, workflowExecutionId) if err != nil { return *new(common.Address), err @@ -185,12 +185,12 @@ func (_KeystoneForwarder *KeystoneForwarderCaller) GetTransmitter(opts *bind.Cal } -func (_KeystoneForwarder *KeystoneForwarderSession) GetTransmitter(workflowExecutionId [32]byte) (common.Address, error) { - return _KeystoneForwarder.Contract.GetTransmitter(&_KeystoneForwarder.CallOpts, workflowExecutionId) +func (_KeystoneForwarder *KeystoneForwarderSession) GetTransmitter(receiver common.Address, workflowExecutionId [32]byte) (common.Address, error) { + return _KeystoneForwarder.Contract.GetTransmitter(&_KeystoneForwarder.CallOpts, receiver, workflowExecutionId) } -func (_KeystoneForwarder *KeystoneForwarderCallerSession) GetTransmitter(workflowExecutionId [32]byte) (common.Address, error) { - return _KeystoneForwarder.Contract.GetTransmitter(&_KeystoneForwarder.CallOpts, workflowExecutionId) +func (_KeystoneForwarder *KeystoneForwarderCallerSession) GetTransmitter(receiver common.Address, workflowExecutionId [32]byte) (common.Address, error) { + return _KeystoneForwarder.Contract.GetTransmitter(&_KeystoneForwarder.CallOpts, receiver, workflowExecutionId) } func (_KeystoneForwarder *KeystoneForwarderCaller) Owner(opts *bind.CallOpts) (common.Address, error) { @@ -249,16 +249,28 @@ func (_KeystoneForwarder *KeystoneForwarderTransactorSession) AcceptOwnership() return _KeystoneForwarder.Contract.AcceptOwnership(&_KeystoneForwarder.TransactOpts) } -func (_KeystoneForwarder *KeystoneForwarderTransactor) Report(opts *bind.TransactOpts, targetAddress common.Address, data []byte, signatures [][]byte) (*types.Transaction, error) { - return _KeystoneForwarder.contract.Transact(opts, "report", targetAddress, data, signatures) +func (_KeystoneForwarder *KeystoneForwarderTransactor) Report(opts *bind.TransactOpts, receiverAddress common.Address, rawReport []byte, signatures [][]byte) (*types.Transaction, error) { + return _KeystoneForwarder.contract.Transact(opts, "report", receiverAddress, rawReport, signatures) } -func (_KeystoneForwarder *KeystoneForwarderSession) Report(targetAddress common.Address, data []byte, signatures [][]byte) (*types.Transaction, error) { - return _KeystoneForwarder.Contract.Report(&_KeystoneForwarder.TransactOpts, targetAddress, data, signatures) +func (_KeystoneForwarder *KeystoneForwarderSession) Report(receiverAddress common.Address, rawReport []byte, signatures [][]byte) (*types.Transaction, error) { + return _KeystoneForwarder.Contract.Report(&_KeystoneForwarder.TransactOpts, receiverAddress, rawReport, signatures) } -func (_KeystoneForwarder *KeystoneForwarderTransactorSession) Report(targetAddress common.Address, data []byte, signatures [][]byte) (*types.Transaction, error) { - return _KeystoneForwarder.Contract.Report(&_KeystoneForwarder.TransactOpts, targetAddress, data, signatures) +func (_KeystoneForwarder *KeystoneForwarderTransactorSession) Report(receiverAddress common.Address, rawReport []byte, signatures [][]byte) (*types.Transaction, error) { + return _KeystoneForwarder.Contract.Report(&_KeystoneForwarder.TransactOpts, receiverAddress, rawReport, signatures) +} + +func (_KeystoneForwarder *KeystoneForwarderTransactor) SetConfig(opts *bind.TransactOpts, donId uint32, f uint8, signers []common.Address) (*types.Transaction, error) { + return _KeystoneForwarder.contract.Transact(opts, "setConfig", donId, f, signers) +} + +func (_KeystoneForwarder *KeystoneForwarderSession) SetConfig(donId uint32, f uint8, signers []common.Address) (*types.Transaction, error) { + return _KeystoneForwarder.Contract.SetConfig(&_KeystoneForwarder.TransactOpts, donId, f, signers) +} + +func (_KeystoneForwarder *KeystoneForwarderTransactorSession) SetConfig(donId uint32, f uint8, signers []common.Address) (*types.Transaction, error) { + return _KeystoneForwarder.Contract.SetConfig(&_KeystoneForwarder.TransactOpts, donId, f, signers) } func (_KeystoneForwarder *KeystoneForwarderTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { @@ -545,12 +557,160 @@ func (_KeystoneForwarder *KeystoneForwarderFilterer) ParseOwnershipTransferred(l return event, nil } +type KeystoneForwarderReportProcessedIterator struct { + Event *KeystoneForwarderReportProcessed + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeystoneForwarderReportProcessedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeystoneForwarderReportProcessed) + 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(KeystoneForwarderReportProcessed) + 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 *KeystoneForwarderReportProcessedIterator) Error() error { + return it.fail +} + +func (it *KeystoneForwarderReportProcessedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeystoneForwarderReportProcessed struct { + Receiver common.Address + WorkflowOwner common.Address + WorkflowExecutionId [32]byte + Result bool + Raw types.Log +} + +func (_KeystoneForwarder *KeystoneForwarderFilterer) FilterReportProcessed(opts *bind.FilterOpts, receiver []common.Address, workflowOwner []common.Address, workflowExecutionId [][32]byte) (*KeystoneForwarderReportProcessedIterator, error) { + + var receiverRule []interface{} + for _, receiverItem := range receiver { + receiverRule = append(receiverRule, receiverItem) + } + var workflowOwnerRule []interface{} + for _, workflowOwnerItem := range workflowOwner { + workflowOwnerRule = append(workflowOwnerRule, workflowOwnerItem) + } + var workflowExecutionIdRule []interface{} + for _, workflowExecutionIdItem := range workflowExecutionId { + workflowExecutionIdRule = append(workflowExecutionIdRule, workflowExecutionIdItem) + } + + logs, sub, err := _KeystoneForwarder.contract.FilterLogs(opts, "ReportProcessed", receiverRule, workflowOwnerRule, workflowExecutionIdRule) + if err != nil { + return nil, err + } + return &KeystoneForwarderReportProcessedIterator{contract: _KeystoneForwarder.contract, event: "ReportProcessed", logs: logs, sub: sub}, nil +} + +func (_KeystoneForwarder *KeystoneForwarderFilterer) WatchReportProcessed(opts *bind.WatchOpts, sink chan<- *KeystoneForwarderReportProcessed, receiver []common.Address, workflowOwner []common.Address, workflowExecutionId [][32]byte) (event.Subscription, error) { + + var receiverRule []interface{} + for _, receiverItem := range receiver { + receiverRule = append(receiverRule, receiverItem) + } + var workflowOwnerRule []interface{} + for _, workflowOwnerItem := range workflowOwner { + workflowOwnerRule = append(workflowOwnerRule, workflowOwnerItem) + } + var workflowExecutionIdRule []interface{} + for _, workflowExecutionIdItem := range workflowExecutionId { + workflowExecutionIdRule = append(workflowExecutionIdRule, workflowExecutionIdItem) + } + + logs, sub, err := _KeystoneForwarder.contract.WatchLogs(opts, "ReportProcessed", receiverRule, workflowOwnerRule, workflowExecutionIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeystoneForwarderReportProcessed) + if err := _KeystoneForwarder.contract.UnpackLog(event, "ReportProcessed", 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 (_KeystoneForwarder *KeystoneForwarderFilterer) ParseReportProcessed(log types.Log) (*KeystoneForwarderReportProcessed, error) { + event := new(KeystoneForwarderReportProcessed) + if err := _KeystoneForwarder.contract.UnpackLog(event, "ReportProcessed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + func (_KeystoneForwarder *KeystoneForwarder) ParseLog(log types.Log) (generated.AbigenLog, error) { switch log.Topics[0] { case _KeystoneForwarder.abi.Events["OwnershipTransferRequested"].ID: return _KeystoneForwarder.ParseOwnershipTransferRequested(log) case _KeystoneForwarder.abi.Events["OwnershipTransferred"].ID: return _KeystoneForwarder.ParseOwnershipTransferred(log) + case _KeystoneForwarder.abi.Events["ReportProcessed"].ID: + return _KeystoneForwarder.ParseReportProcessed(log) default: return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) @@ -565,12 +725,16 @@ func (KeystoneForwarderOwnershipTransferred) Topic() common.Hash { return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") } +func (KeystoneForwarderReportProcessed) Topic() common.Hash { + return common.HexToHash("0xdae8e752043eb5fc7e4a6eced57ceaf159548b630125ece9ffc41cfc952c2081") +} + func (_KeystoneForwarder *KeystoneForwarder) Address() common.Address { return _KeystoneForwarder.address } type KeystoneForwarderInterface interface { - GetTransmitter(opts *bind.CallOpts, workflowExecutionId [32]byte) (common.Address, error) + GetTransmitter(opts *bind.CallOpts, receiver common.Address, workflowExecutionId [32]byte) (common.Address, error) Owner(opts *bind.CallOpts) (common.Address, error) @@ -578,7 +742,9 @@ type KeystoneForwarderInterface interface { AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) - Report(opts *bind.TransactOpts, targetAddress common.Address, data []byte, signatures [][]byte) (*types.Transaction, error) + Report(opts *bind.TransactOpts, receiverAddress common.Address, rawReport []byte, signatures [][]byte) (*types.Transaction, error) + + SetConfig(opts *bind.TransactOpts, donId uint32, f uint8, signers []common.Address) (*types.Transaction, error) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) @@ -594,6 +760,12 @@ type KeystoneForwarderInterface interface { ParseOwnershipTransferred(log types.Log) (*KeystoneForwarderOwnershipTransferred, error) + FilterReportProcessed(opts *bind.FilterOpts, receiver []common.Address, workflowOwner []common.Address, workflowExecutionId [][32]byte) (*KeystoneForwarderReportProcessedIterator, error) + + WatchReportProcessed(opts *bind.WatchOpts, sink chan<- *KeystoneForwarderReportProcessed, receiver []common.Address, workflowOwner []common.Address, workflowExecutionId [][32]byte) (event.Subscription, error) + + ParseReportProcessed(log types.Log) (*KeystoneForwarderReportProcessed, error) + ParseLog(log types.Log) (generated.AbigenLog, error) Address() common.Address diff --git a/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 9b49710b546..54d1355c62f 100644 --- a/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,4 +1,4 @@ GETH_VERSION: 1.13.8 -forwarder: ../../../contracts/solc/v0.8.19/KeystoneForwarder/KeystoneForwarder.abi ../../../contracts/solc/v0.8.19/KeystoneForwarder/KeystoneForwarder.bin b4c900aae9e022f01abbac7993d41f93912247613ac6270b0c4da4ef6f2016e3 +forwarder: ../../../contracts/solc/v0.8.19/KeystoneForwarder/KeystoneForwarder.abi ../../../contracts/solc/v0.8.19/KeystoneForwarder/KeystoneForwarder.bin ed9164cfe4619dff824b11df46b66f4c6834b2ca072923f10d9ebc57ce508ed8 keystone_capability_registry: ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.abi ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.bin d4e0661491c2adc7f0d7553287c938fb32664b9f07770f6d57ae6511ce9884cd ocr3_capability: ../../../contracts/solc/v0.8.19/OCR3Capability/OCR3Capability.abi ../../../contracts/solc/v0.8.19/OCR3Capability/OCR3Capability.bin 9dcbdf55bd5729ba266148da3f17733eb592c871c2108ccca546618628fd9ad2 diff --git a/core/services/relay/evm/cap_encoder.go b/core/services/relay/evm/cap_encoder.go index b52cbb71c66..00fa3bc8773 100644 --- a/core/services/relay/evm/cap_encoder.go +++ b/core/services/relay/evm/cap_encoder.go @@ -69,14 +69,14 @@ func (c *capEncoder) Encode(ctx context.Context, input values.Map) ([]byte, erro return nil, err } // prepend workflowID and workflowExecutionID to the encoded user data - workflowIDbytes, executionIDBytes, err := extractIDs(unwrappedMap) + workflowIDbytes, donIDBytes, executionIDBytes, workflowOwnerBytes, err := extractIDs(unwrappedMap) if err != nil { return nil, err } - return append(append(workflowIDbytes, executionIDBytes...), userPayload...), nil + return append(append(append(append(workflowIDbytes, donIDBytes...), executionIDBytes...), workflowOwnerBytes...), userPayload...), nil } -func decodeID(input map[string]any, key string) ([]byte, error) { +func decodeID(input map[string]any, key string, idLen int) ([]byte, error) { id, ok := input[key].(string) if !ok { return nil, fmt.Errorf("expected %s to be a string", key) @@ -96,16 +96,20 @@ func decodeID(input map[string]any, key string) ([]byte, error) { // extract workflowID and executionID from the input map, validate and align to 32 bytes // NOTE: consider requiring them to be exactly 32 bytes to avoid issues with padding -func extractIDs(input map[string]any) ([]byte, []byte, error) { - workflowID, err := decodeID(input, consensustypes.WorkflowIDFieldName) +func extractIDs(input map[string]any) ([]byte, []byte, []byte, []byte, error) { + workflowID, err := decodeID(input, consensustypes.WorkflowIDFieldName, idLen) if err != nil { - return nil, nil, err + return nil, nil, nil, nil, err } - executionID, err := decodeID(input, consensustypes.ExecutionIDFieldName) + // TODO: source donID and workflowOwner from somewhere + donID := []byte{0, 1, 2, 3} + workflowOwner := make([]byte, 32) + + executionID, err := decodeID(input, consensustypes.ExecutionIDFieldName, idLen) if err != nil { - return nil, nil, err + return nil, nil, nil, nil, err } - return workflowID, executionID, nil + return workflowID, donID, executionID, workflowOwner, nil } diff --git a/core/services/relay/evm/cap_encoder_test.go b/core/services/relay/evm/cap_encoder_test.go index 186968df9b2..10a19fd962b 100644 --- a/core/services/relay/evm/cap_encoder_test.go +++ b/core/services/relay/evm/cap_encoder_test.go @@ -18,8 +18,10 @@ var ( reportB = []byte{0xaa, 0xbb, 0xcc, 0xdd} // hex encoded 32 byte strings - workflowID = "15c631d295ef5e32deb99a10ee6804bc4af1385568f9b3363f6552ac6dbb2cef" - executionID = "8d4e66421db647dd916d3ec28d56188c8d7dae5f808e03d03339ed2562f13bb0" + workflowID = "15c631d295ef5e32deb99a10ee6804bc4af1385568f9b3363f6552ac6dbb2cef" + donID = "00010203" + executionID = "8d4e66421db647dd916d3ec28d56188c8d7dae5f808e03d03339ed2562f13bb0" + workflowOwnerID = "0000000000000000000000000000000000000000000000000000000000000000" invalidID = "not_valid" wrongLength = "8d4e66" @@ -48,7 +50,9 @@ func TestEVMEncoder(t *testing.T) { expected := // start of the outer tuple ((user_fields), workflow_id, workflow_execution_id) workflowID + + donID + executionID + + workflowOwnerID + // start of the inner tuple (user_fields) "0000000000000000000000000000000000000000000000000000000000000020" + // offset of mercury_reports array "0000000000000000000000000000000000000000000000000000000000000002" + // length of mercury_reports array diff --git a/flake.lock b/flake.lock index 94ed8931096..260506ff892 100644 --- a/flake.lock +++ b/flake.lock @@ -18,7 +18,42 @@ "type": "github" } }, + "foundry": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1715073011, + "narHash": "sha256-fwTWvaOgAUrQwaCcGfeRn1D+n0G4ltr+I+FPb05RPeY=", + "owner": "shazow", + "repo": "foundry.nix", + "rev": "5d2761d546b8712e3faaa416bacc6567007d757a", + "type": "github" + }, + "original": { + "owner": "shazow", + "repo": "foundry.nix", + "type": "github" + } + }, "nixpkgs": { + "locked": { + "lastModified": 1666753130, + "narHash": "sha256-Wff1dGPFSneXJLI2c0kkdWTgxnQ416KE6X4KnFkgPYQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f540aeda6f677354f1e7144ab04352f61aaa0118", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1712439257, "narHash": "sha256-aSpiNepFOMk9932HOax0XwNxbA38GOUVOiXfUVPOrck=", @@ -37,7 +72,8 @@ "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "foundry": "foundry", + "nixpkgs": "nixpkgs_2" } }, "systems": { diff --git a/flake.nix b/flake.nix index b4fd137da79..7ae9a5435bd 100644 --- a/flake.nix +++ b/flake.nix @@ -3,13 +3,15 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + foundry.url = "github:shazow/foundry.nix"; flake-utils.url = "github:numtide/flake-utils"; + foundry.inputs.flake-utils.follows = "flake-utils"; }; - outputs = inputs@{ self, nixpkgs, flake-utils, ... }: + outputs = inputs@{ self, nixpkgs, flake-utils, foundry, ... }: flake-utils.lib.eachDefaultSystem (system: let - pkgs = import nixpkgs { inherit system; overlays = [ ]; }; + pkgs = import nixpkgs { inherit system; overlays = [ foundry.overlay ]; }; in rec { devShell = pkgs.callPackage ./shell.nix { diff --git a/shell.nix b/shell.nix index 1fde34fcfd8..1a3a08ffe09 100644 --- a/shell.nix +++ b/shell.nix @@ -19,13 +19,17 @@ mkShell' { python3 python3Packages.pip + protobuf + protoc-gen-go + protoc-gen-go-grpc + + foundry-bin curl nodejs nodePackages.pnpm # TODO: compiler / gcc for secp compilation go-ethereum # geth - # parity # openethereum go-mockery # tooling