Skip to content

Commit

Permalink
[KS-331] Single contract serving as router and forwarder (#13528)
Browse files Browse the repository at this point in the history
* Remove redundant IForwarder interface

* Move Router error and events to the interface

* Remove unused import

* More refactors

* Tests pass

* Merge Forwarder + Router; wrappers + gas snapshot

* Use the same mechanism for the forwarder check

* Remove router deployment steps

* Add changesets
  • Loading branch information
DeividasK authored Jun 14, 2024
1 parent a830fe0 commit 95502ad
Show file tree
Hide file tree
Showing 17 changed files with 515 additions and 1,167 deletions.
5 changes: 5 additions & 0 deletions .changeset/ninety-zebras-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

#internal
5 changes: 5 additions & 0 deletions contracts/.changeset/odd-gorillas-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/contracts': patch
---

#internal
48 changes: 24 additions & 24 deletions contracts/gas-snapshots/keystone.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -78,28 +78,28 @@ CapabilityRegistry_UpdateNodesTest:test_RevertWhen_P2PIDEmpty() (gas: 27291)
CapabilityRegistry_UpdateNodesTest:test_RevertWhen_SignerAddressEmpty() (gas: 26974)
CapabilityRegistry_UpdateNodesTest:test_RevertWhen_UpdatingNodeWithoutCapabilities() (gas: 25538)
CapabilityRegistry_UpdateNodesTest:test_UpdatesNodeInfo() (gas: 158803)
KeystoneForwarder_ReportTest:test_Report_ConfigVersion() (gas: 1807115)
KeystoneForwarder_ReportTest:test_Report_FailedDeliveryWhenReceiverInterfaceNotSupported() (gas: 132145)
KeystoneForwarder_ReportTest:test_Report_FailedDeliveryWhenReceiverNotContract() (gas: 133638)
KeystoneForwarder_ReportTest:test_Report_SuccessfulDelivery() (gas: 162153)
KeystoneForwarder_ReportTest:test_RevertWhen_AlreadyAttempted() (gas: 157192)
KeystoneForwarder_ReportTest:test_RevertWhen_AnySignatureIsInvalid() (gas: 86392)
KeystoneForwarder_ReportTest:test_RevertWhen_AnySignerIsInvalid() (gas: 118530)
KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasDuplicateSignatures() (gas: 94560)
KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasIncorrectDON() (gas: 75914)
KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasInexistentConfigVersion() (gas: 76282)
KeystoneForwarder_ReportTest:test_RevertWhen_ReportIsMalformed() (gas: 45569)
KeystoneForwarder_ReportTest:test_RevertWhen_TooFewSignatures() (gas: 55282)
KeystoneForwarder_ReportTest:test_RevertWhen_TooManySignatures() (gas: 56028)
KeystoneForwarder_SetConfigTest:test_RevertWhen_ExcessSigners() (gas: 20162)
KeystoneForwarder_SetConfigTest:test_RevertWhen_FaultToleranceIsZero() (gas: 90143)
KeystoneForwarder_SetConfigTest:test_RevertWhen_InsufficientSigners() (gas: 14511)
KeystoneForwarder_SetConfigTest:test_RevertWhen_NotOwner() (gas: 90874)
KeystoneForwarder_SetConfigTest:test_RevertWhen_ProvidingDuplicateSigners() (gas: 116644)
KeystoneForwarder_SetConfigTest:test_SetConfig_FirstTime() (gas: 1543588)
KeystoneForwarder_SetConfigTest:test_SetConfig_WhenSignersAreRemoved() (gas: 1540255)
KeystoneForwarder_ReportTest:test_Report_ConfigVersion() (gas: 1802140)
KeystoneForwarder_ReportTest:test_Report_FailedDeliveryWhenReceiverInterfaceNotSupported() (gas: 126020)
KeystoneForwarder_ReportTest:test_Report_FailedDeliveryWhenReceiverNotContract() (gas: 127513)
KeystoneForwarder_ReportTest:test_Report_SuccessfulDelivery() (gas: 156028)
KeystoneForwarder_ReportTest:test_RevertWhen_AlreadyAttempted() (gas: 152578)
KeystoneForwarder_ReportTest:test_RevertWhen_AnySignatureIsInvalid() (gas: 86398)
KeystoneForwarder_ReportTest:test_RevertWhen_AnySignerIsInvalid() (gas: 118536)
KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasDuplicateSignatures() (gas: 94566)
KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasIncorrectDON() (gas: 75920)
KeystoneForwarder_ReportTest:test_RevertWhen_ReportHasInexistentConfigVersion() (gas: 76288)
KeystoneForwarder_ReportTest:test_RevertWhen_ReportIsMalformed() (gas: 45575)
KeystoneForwarder_ReportTest:test_RevertWhen_TooFewSignatures() (gas: 55288)
KeystoneForwarder_ReportTest:test_RevertWhen_TooManySignatures() (gas: 56034)
KeystoneForwarder_SetConfigTest:test_RevertWhen_ExcessSigners() (gas: 20184)
KeystoneForwarder_SetConfigTest:test_RevertWhen_FaultToleranceIsZero() (gas: 90165)
KeystoneForwarder_SetConfigTest:test_RevertWhen_InsufficientSigners() (gas: 14533)
KeystoneForwarder_SetConfigTest:test_RevertWhen_NotOwner() (gas: 90896)
KeystoneForwarder_SetConfigTest:test_RevertWhen_ProvidingDuplicateSigners() (gas: 116678)
KeystoneForwarder_SetConfigTest:test_SetConfig_FirstTime() (gas: 1543982)
KeystoneForwarder_SetConfigTest:test_SetConfig_WhenSignersAreRemoved() (gas: 1540964)
KeystoneForwarder_TypeAndVersionTest:test_TypeAndVersion() (gas: 9641)
KeystoneRouter_SetConfigTest:test_AddForwarder_RevertWhen_NotOwner() (gas: 10956)
KeystoneRouter_SetConfigTest:test_RemoveForwarder_RevertWhen_NotOwner() (gas: 10879)
KeystoneRouter_SetConfigTest:test_Route_RevertWhen_Unauthorized() (gas: 18509)
KeystoneRouter_SetConfigTest:test_Route_Success() (gas: 75585)
KeystoneRouter_SetConfigTest:test_AddForwarder_RevertWhen_NotOwner() (gas: 10978)
KeystoneRouter_SetConfigTest:test_RemoveForwarder_RevertWhen_NotOwner() (gas: 10923)
KeystoneRouter_SetConfigTest:test_Route_RevertWhen_UnauthorizedForwarder() (gas: 18553)
KeystoneRouter_SetConfigTest:test_Route_Success() (gas: 75629)
1 change: 0 additions & 1 deletion contracts/scripts/native_solc_compile_all_keystone
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,4 @@ compileContract () {

compileContract keystone/CapabilityRegistry.sol
compileContract keystone/KeystoneForwarder.sol
compileContract keystone/KeystoneRouter.sol
compileContract keystone/OCR3Capability.sol
127 changes: 88 additions & 39 deletions contracts/src/v0.8/keystone/KeystoneForwarder.sol
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
import {IReceiver} from "./interfaces/IReceiver.sol";
import {IRouter} from "./interfaces/IRouter.sol";
import {IForwarder} from "./interfaces/IForwarder.sol";
import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";

import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol";

/// @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, OwnerIsCreator, ITypeAndVersion {
contract KeystoneForwarder is OwnerIsCreator, ITypeAndVersion, IRouter {
/// @notice This error is returned when the report is shorter than
/// REPORT_METADATA_LENGTH, which is the minimum length of a report.
error InvalidReport();
Expand Down Expand Up @@ -64,8 +64,6 @@ contract KeystoneForwarder is IForwarder, OwnerIsCreator, ITypeAndVersion {
mapping(address signer => uint256 position) _positions; // 1-indexed to detect unset values
}

address internal s_router;

/// @notice Contains the configuration for each DON ID
// @param configId (uint64(donId) << 32) | configVersion
mapping(uint64 configId => OracleSet) internal s_configs;
Expand All @@ -81,17 +79,98 @@ contract KeystoneForwarder is IForwarder, OwnerIsCreator, ITypeAndVersion {
bool result
);

string public constant override typeAndVersion = "KeystoneForwarder 1.0.0";
string public constant override typeAndVersion = "Forwarder and Router 1.0.0";

constructor(address router) OwnerIsCreator() {
s_router = router;
constructor() OwnerIsCreator() {
s_forwarders[address(this)] = true;
}

uint256 internal constant MAX_ORACLES = 31;
uint256 internal constant METADATA_LENGTH = 109;
uint256 internal constant FORWARDER_METADATA_LENGTH = 45;
uint256 internal constant SIGNATURE_LENGTH = 65;

// ================================================================
// │ IRouter │
// ================================================================

mapping(address forwarder => bool) internal s_forwarders;
mapping(bytes32 transmissionId => TransmissionInfo) internal s_transmissions;

function addForwarder(address forwarder) external onlyOwner {
s_forwarders[forwarder] = true;
emit ForwarderAdded(forwarder);
}

function removeForwarder(address forwarder) external onlyOwner {
s_forwarders[forwarder] = false;
emit ForwarderRemoved(forwarder);
}

function route(
bytes32 transmissionId,
address transmitter,
address receiver,
bytes calldata metadata,
bytes calldata validatedReport
) public returns (bool) {
if (!s_forwarders[msg.sender]) {
revert UnauthorizedForwarder();
}

if (s_transmissions[transmissionId].transmitter != address(0)) revert AlreadyAttempted(transmissionId);
s_transmissions[transmissionId].transmitter = transmitter;

if (receiver.code.length == 0) return false;

try IReceiver(receiver).onReport(metadata, validatedReport) {
s_transmissions[transmissionId].success = true;
return true;
} catch {
return false;
}
}

function getTransmissionId(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) public pure returns (bytes32) {
// This is slightly cheaper compared to
// keccak256(abi.encode(receiver, workflowExecutionId, reportId));
return keccak256(bytes.concat(bytes20(uint160(receiver)), workflowExecutionId, reportId));
}

/// @notice Get transmitter of a given report or 0x0 if it wasn't transmitted yet
function getTransmitter(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) external view returns (address) {
return s_transmissions[getTransmissionId(receiver, workflowExecutionId, reportId)].transmitter;
}

/// @notice Get delivery status of a given report
function getTransmissionState(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) external view returns (IRouter.TransmissionState) {
bytes32 transmissionId = getTransmissionId(receiver, workflowExecutionId, reportId);

if (s_transmissions[transmissionId].transmitter == address(0)) return IRouter.TransmissionState.NOT_ATTEMPTED;
return
s_transmissions[transmissionId].success ? IRouter.TransmissionState.SUCCEEDED : IRouter.TransmissionState.FAILED;
}

function isForwarder(address forwarder) external view returns (bool) {
return s_forwarders[forwarder];
}

// ================================================================
// │ Forwarder │
// ================================================================

function setConfig(uint32 donId, uint32 configVersion, uint8 f, address[] calldata signers) external onlyOwner {
if (f == 0) revert FaultToleranceMustBePositive();
if (signers.length > MAX_ORACLES) revert ExcessSigners(signers.length, MAX_ORACLES);
Expand Down Expand Up @@ -170,7 +249,7 @@ contract KeystoneForwarder is IForwarder, OwnerIsCreator, ITypeAndVersion {
}
}

bool success = IRouter(s_router).route(
bool success = this.route(
getTransmissionId(receiver, workflowExecutionId, reportId),
msg.sender,
receiver,
Expand All @@ -181,36 +260,6 @@ contract KeystoneForwarder is IForwarder, OwnerIsCreator, ITypeAndVersion {
emit ReportProcessed(receiver, workflowExecutionId, reportId, success);
}

function getTransmitter(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) external view returns (address) {
return IRouter(s_router).getTransmitter(getTransmissionId(receiver, workflowExecutionId, reportId));
}

function getTransmissionState(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) external view returns (IRouter.TransmissionState) {
return IRouter(s_router).getTransmissionState(getTransmissionId(receiver, workflowExecutionId, reportId));
}

function getTransmissionId(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) public pure returns (bytes32) {
// This is slightly cheaper compared to
// keccak256(abi.encode(receiver, workflowExecutionId, reportId));
return keccak256(bytes.concat(bytes20(uint160(receiver)), workflowExecutionId, reportId));
}

function getRouter() external view returns (address) {
return s_router;
}

// solhint-disable-next-line chainlink-solidity/explicit-returns
function _getMetadata(
bytes memory rawReport
Expand Down
76 changes: 0 additions & 76 deletions contracts/src/v0.8/keystone/KeystoneRouter.sol

This file was deleted.

5 changes: 0 additions & 5 deletions contracts/src/v0.8/keystone/interfaces/IForwarder.sol

This file was deleted.

32 changes: 30 additions & 2 deletions contracts/src/v0.8/keystone/interfaces/IRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,26 @@ pragma solidity ^0.8.19;

/// @title IRouter - delivers keystone reports to receiver
interface IRouter {
error UnauthorizedForwarder();
error AlreadyAttempted(bytes32 transmissionId);

event ForwarderAdded(address indexed forwarder);
event ForwarderRemoved(address indexed forwarder);

enum TransmissionState {
NOT_ATTEMPTED,
SUCCEEDED,
FAILED
}

struct TransmissionInfo {
address transmitter;
bool success;
}

function addForwarder(address forwarder) external;
function removeForwarder(address forwarder) external;

function route(
bytes32 transmissionId,
address transmitter,
Expand All @@ -17,6 +31,20 @@ interface IRouter {
bytes calldata report
) external returns (bool);

function getTransmitter(bytes32 transmissionId) external view returns (address);
function getTransmissionState(bytes32 transmissionId) external view returns (TransmissionState);
function getTransmissionId(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) external pure returns (bytes32);
function getTransmitter(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) external view returns (address);
function getTransmissionState(
address receiver,
bytes32 workflowExecutionId,
bytes2 reportId
) external view returns (TransmissionState);
function isForwarder(address forwarder) external view returns (bool);
}
Loading

0 comments on commit 95502ad

Please sign in to comment.