Transaction Hash:
Block:
22854029 at Jul-05-2025 03:52:11 PM +UTC
Transaction Fee:
0.000172974977476645 ETH
$0.44
Gas Used:
117,071 Gas / 1.477521995 Gwei
Emitted Events:
126 |
DataFeedsCache.BundleReportUpdated( dataId=System.Byte[], timestamp=1751730720, bundle=0x000000000000000000000000000000000000000000000000000B929071E5EC22 )
|
127 |
KeystoneForwarder.ReportProcessed( receiver=DataFeedsCache, workflowExecutionId=7962E1DE7C3094DD70E688260EB5EB548F1214A7E6BBCB7730FF3DCDF8681810, reportId=System.Byte[], result=True )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x844FDF42...04b0a7C7e | |||||
0xa68A99C6...AE51c8d96 | |||||
0xdadB0d80...24f783711
Miner
| (BuilderNet) | 29.339476078166635915 Eth | 29.339593149166635915 Eth | 0.000117071 | |
0xef9CD5Ab...02Ee24B5D |
2.30509911634948137 Eth
Nonce: 2238
|
2.304926141372004725 Eth
Nonce: 2239
| 0.000172974977476645 |
Execution Trace
KeystoneForwarder.report( receiver=0x844FDF4275F59ED011feF86857Db88404b0a7C7e, rawReport=0x017962E1DE7C3094DD70E688260EB5EB548F1214A7E6BBCB7730FF3DCDF868181068694A21000000040000000100FC71EEF9469F7C5D32CBE918349E8C69A3941DA9EC429A0C7F3A7B15CD998962366365336663613863FBB30BD8E9D779044C3C30DD82E52A5FA1573388000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002002CE8BFC980000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068694A2000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000B929071E5EC22, reportContext=0x000EC71B4C7561FCCDC37D6922E71EDD1E52F8C27D942D802A89FBCF060C504900000000000000000000000000000000000000000000000000000000467427000000000000000000000000000000000000000000000000000000000000000000, signatures=[lqRzISxuVkS+ZH2Co0J6JJWhxb9y1YhFuIedBiU632N30fN4FNV2aw9alRyMk4qTEztqVcWK/qVjkTlCpcRCxQA=, p4ueXkb/nJ6ED8anrEBBgy78OPQphDUDa6UttWPoRnppC/NPdAF5r0T4Y6kxKTzubnYv/luM9nBo0ADpTmuouAA=, iE7JdFPNAnWeajcwsMV1f2rzgPmhGum+Dx78uq84gBBNpOSSPWzObuhlIEC9EQ5Whqz/CYSX7nHhgybONrpM9AA=, Kcn+wpvCeG9YHaq6DhgfZkqRjqcOXqU1S8S/f/lQWL8SY4Oxw6H/HaVSWA23EAo/tRo7GkS2O0TTanHRyt4FIgA=] )
-
Null: 0x000...001.54a47cc5( )
-
Null: 0x000...001.54a47cc5( )
-
Null: 0x000...001.54a47cc5( )
-
Null: 0x000...001.54a47cc5( )
KeystoneForwarder.route( transmissionId=C84991F221D6290E3037B6EFA369527BCF4E021F364D63EE49A7E06AE52C198B, transmitter=0xef9CD5Ab3149e07f0EF928735Bf42c902Ee24B5D, receiver=0x844FDF4275F59ED011feF86857Db88404b0a7C7e, metadata=0x00FC71EEF9469F7C5D32CBE918349E8C69A3941DA9EC429A0C7F3A7B15CD998962366365336663613863FBB30BD8E9D779044C3C30DD82E52A5FA15733880001, validatedReport=0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002002CE8BFC980000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068694A2000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000B929071E5EC22 ) => ( True )
-
DataFeedsCache.supportsInterface( interfaceId=System.Byte[] ) => ( True )
-
DataFeedsCache.supportsInterface( interfaceId=System.Byte[] ) => ( False )
-
DataFeedsCache.supportsInterface( interfaceId=System.Byte[] ) => ( True )
-
DataFeedsCache.onReport( metadata=0x00FC71EEF9469F7C5D32CBE918349E8C69A3941DA9EC429A0C7F3A7B15CD998962366365336663613863FBB30BD8E9D779044C3C30DD82E52A5FA15733880001, report=0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002002CE8BFC980000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068694A2000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000B929071E5EC22 )
-
File 1 of 2: KeystoneForwarder
File 2 of 2: DataFeedsCache
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {IReceiver} from "./interfaces/IReceiver.sol"; import {IRouter} from "./interfaces/IRouter.sol"; import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol"; import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol"; import {ERC165Checker} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/ERC165Checker.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 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(); /// @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 configuration that does not exist. /// @param configId (uint64(donId) << 32) | configVersion error InvalidConfig(uint64 configId); /// @notice This error is thrown whenever a signer address is not in the configuration or /// when trying to set a zero address as a signer. /// @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 Contains the signing address of each oracle struct OracleSet { uint8 f; // Number of faulty nodes allowed address[] signers; mapping(address signer => uint256 position) _positions; // 1-indexed to detect unset values } struct Transmission { address transmitter; // This is true if the receiver is not a contract or does not implement the `IReceiver` interface. bool invalidReceiver; // Whether the transmission attempt was successful. If `false`, the transmission can be retried // with an increased gas limit. bool success; // The amount of gas allocated for the `IReceiver.onReport` call. uint80 allows storing gas for known EVM block // gas limits. Ensures that the minimum gas requested by the user is available during the transmission attempt. // If the transmission fails (indicated by a `false` success state), it can be retried with an increased gas limit. uint80 gasLimit; } /// @notice Emitted when a report is processed /// @param result The result of the attempted delivery. True if successful. event ReportProcessed( address indexed receiver, bytes32 indexed workflowExecutionId, bytes2 indexed reportId, bool result ); /// @notice Contains the configuration for each DON ID /// configId (uint64(donId) << 32) | configVersion mapping(uint64 configId => OracleSet oracleSet) internal s_configs; event ConfigSet(uint32 indexed donId, uint32 indexed configVersion, uint8 f, address[] signers); string public constant override typeAndVersion = "KeystoneForwarder 1.0.0"; 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; /// @dev This is the gas required to store `success` after the report is processed. /// It is a warm storage write because of the packed struct. In practice it will cost less. uint256 internal constant INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT = 5_000; /// @dev This is the gas required to store the transmission struct and perform other checks. uint256 internal constant INTERNAL_GAS_REQUIREMENTS = 25_000 + INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT; /// @dev This is the minimum gas required to route a report. This includes internal gas requirements /// as well as the minimum gas that the user contract will receive. 30k * 3 gas is to account for /// cases where consumers need close to the 30k limit provided in the supportsInterface check. uint256 internal constant MINIMUM_GAS_LIMIT = INTERNAL_GAS_REQUIREMENTS + 30_000 * 3 + 10_000; // ================================================================ // │ Router │ // ================================================================ mapping(address forwarder => bool isForwarder) internal s_forwarders; mapping(bytes32 transmissionId => Transmission transmission) 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(); uint256 gasLimit = gasleft() - INTERNAL_GAS_REQUIREMENTS; if (gasLimit < MINIMUM_GAS_LIMIT) revert InsufficientGasForRouting(transmissionId); Transmission memory transmission = s_transmissions[transmissionId]; if (transmission.success || transmission.invalidReceiver) revert AlreadyAttempted(transmissionId); s_transmissions[transmissionId].transmitter = transmitter; s_transmissions[transmissionId].gasLimit = uint80(gasLimit); // This call can consume up to 90k gas. if (!ERC165Checker.supportsInterface(receiver, type(IReceiver).interfaceId)) { s_transmissions[transmissionId].invalidReceiver = true; return false; } bool success; bytes memory payload = abi.encodeCall(IReceiver.onReport, (metadata, validatedReport)); uint256 remainingGas = gasleft() - INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT; assembly { // call and return whether we succeeded. ignore return data // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength) success := call(remainingGas, receiver, 0, add(payload, 0x20), mload(payload), 0x0, 0x0) } if (success) { s_transmissions[transmissionId].success = true; } return success; } 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 getTransmissionInfo( address receiver, bytes32 workflowExecutionId, bytes2 reportId ) external view returns (TransmissionInfo memory) { bytes32 transmissionId = getTransmissionId(receiver, workflowExecutionId, reportId); Transmission memory transmission = s_transmissions[transmissionId]; TransmissionState state; if (transmission.transmitter == address(0)) { state = IRouter.TransmissionState.NOT_ATTEMPTED; } else if (transmission.invalidReceiver) { state = IRouter.TransmissionState.INVALID_RECEIVER; } else { state = transmission.success ? IRouter.TransmissionState.SUCCEEDED : IRouter.TransmissionState.FAILED; } return TransmissionInfo({ gasLimit: transmission.gasLimit, invalidReceiver: transmission.invalidReceiver, state: state, success: transmission.success, transmissionId: transmissionId, transmitter: transmission.transmitter }); } /// @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; } 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); if (signers.length <= 3 * f) revert InsufficientSigners(signers.length, 3 * f + 1); uint64 configId = (uint64(donId) << 32) | configVersion; // remove any old signer addresses for (uint256 i = 0; i < s_configs[configId].signers.length; ++i) { delete s_configs[configId]._positions[s_configs[configId].signers[i]]; } // add new signer addresses for (uint256 i = 0; i < signers.length; ++i) { // assign indices, detect duplicates address signer = signers[i]; if (signer == address(0)) revert InvalidSigner(signer); if (s_configs[configId]._positions[signer] != 0) revert DuplicateSigner(signer); s_configs[configId]._positions[signer] = i + 1; } s_configs[configId].signers = signers; s_configs[configId].f = f; emit ConfigSet(donId, configVersion, f, signers); } function clearConfig(uint32 donId, uint32 configVersion) external onlyOwner { // We are not removing old signer positions, because it is sufficient to // clear the f value for `report` function. If we decide to restore // the configId in the future, the setConfig function clears the positions. s_configs[(uint64(donId) << 32) | configVersion].f = 0; emit ConfigSet(donId, configVersion, 0, new address[](0)); } // send a report to receiver function report( address receiver, bytes calldata rawReport, bytes calldata reportContext, bytes[] calldata signatures ) external { if (rawReport.length < METADATA_LENGTH) { revert InvalidReport(); } bytes32 workflowExecutionId; bytes2 reportId; { uint64 configId; (workflowExecutionId, configId, reportId) = _getMetadata(rawReport); OracleSet storage config = s_configs[configId]; uint8 f = config.f; // f can never be 0, so this means the config doesn't actually exist if (f == 0) revert InvalidConfig(configId); if (f + 1 != signatures.length) revert InvalidSignatureCount(f + 1, signatures.length); // validate signatures bytes32 completeHash = keccak256(abi.encodePacked(keccak256(rawReport), reportContext)); address[MAX_ORACLES + 1] memory signed; for (uint256 i = 0; i < signatures.length; ++i) { bytes calldata signature = signatures[i]; if (signature.length != SIGNATURE_LENGTH) revert InvalidSignature(signature); address signer = ecrecover( completeHash, uint8(signature[64]) + 27, bytes32(signature[0:32]), bytes32(signature[32:64]) ); // validate signer is trusted and signature is unique uint256 index = config._positions[signer]; if (index == 0) revert InvalidSigner(signer); // index is 1-indexed so we can detect unset signers if (signed[index] != address(0)) revert DuplicateSigner(signer); signed[index] = signer; } } bool success = this.route( getTransmissionId(receiver, workflowExecutionId, reportId), msg.sender, receiver, rawReport[FORWARDER_METADATA_LENGTH:METADATA_LENGTH], rawReport[METADATA_LENGTH:] ); emit ReportProcessed(receiver, workflowExecutionId, reportId, success); } // solhint-disable-next-line chainlink-solidity/explicit-returns function _getMetadata( bytes memory rawReport ) internal pure returns (bytes32 workflowExecutionId, uint64 configId, bytes2 reportId) { // (first 32 bytes of memory contain length of the report) // version offset 32, size 1 // workflow_execution_id offset 33, size 32 // timestamp offset 65, size 4 // don_id offset 69, size 4 // don_config_version, offset 73, size 4 // workflow_cid offset 77, size 32 // workflow_name offset 109, size 10 // workflow_owner offset 119, size 20 // report_id offset 139, size 2 assembly { workflowExecutionId := mload(add(rawReport, 33)) // shift right by 24 bytes to get the combined don_id and don_config_version configId := shr(mul(24, 8), mload(add(rawReport, 69))) reportId := mload(add(rawReport, 139)) } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IERC165} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol"; /// @title IReceiver - receives keystone reports /// @notice Implementations must support the IReceiver interface through ERC165. interface IReceiver is IERC165 { /// @notice Handles incoming keystone reports. /// @dev If this function call reverts, it can be retried with a higher gas /// limit. The receiver is responsible for discarding stale reports. /// @param metadata Report's metadata. /// @param report Workflow report. function onReport(bytes calldata metadata, bytes calldata report) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @title IRouter - delivers keystone reports to receiver interface IRouter { error UnauthorizedForwarder(); /// @dev Thrown when the gas limit is insufficient for handling state after /// calling the receiver function. error InsufficientGasForRouting(bytes32 transmissionId); error AlreadyAttempted(bytes32 transmissionId); event ForwarderAdded(address indexed forwarder); event ForwarderRemoved(address indexed forwarder); enum TransmissionState { NOT_ATTEMPTED, SUCCEEDED, INVALID_RECEIVER, FAILED } struct TransmissionInfo { bytes32 transmissionId; TransmissionState state; address transmitter; // This is true if the receiver is not a contract or does not implement the // `IReceiver` interface. bool invalidReceiver; // Whether the transmission attempt was successful. If `false`, the // transmission can be retried with an increased gas limit. bool success; // The amount of gas allocated for the `IReceiver.onReport` call. uint80 // allows storing gas for known EVM block gas limits. // Ensures that the minimum gas requested by the user is available during // the transmission attempt. If the transmission fails (indicated by a // `false` success state), it can be retried with an increased gas limit. uint80 gasLimit; } function addForwarder(address forwarder) external; function removeForwarder(address forwarder) external; function route( bytes32 transmissionId, address transmitter, address receiver, bytes calldata metadata, bytes calldata report ) external returns (bool); function getTransmissionId( address receiver, bytes32 workflowExecutionId, bytes2 reportId ) external pure returns (bytes32); function getTransmissionInfo( address receiver, bytes32 workflowExecutionId, bytes2 reportId ) external view returns (TransmissionInfo memory); function getTransmitter( address receiver, bytes32 workflowExecutionId, bytes2 reportId ) external view returns (address); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ITypeAndVersion { function typeAndVersion() external pure returns (string memory); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ConfirmedOwner} from "./ConfirmedOwner.sol"; /// @title The OwnerIsCreator contract /// @notice A contract with helpers for basic contract ownership. contract OwnerIsCreator is ConfirmedOwner { constructor() ConfirmedOwner(msg.sender) {} } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.2) (utils/introspection/ERC165Checker.sol) pragma solidity ^0.8.0; import "./IERC165.sol"; /** * @dev Library used to query support of an interface declared via {IERC165}. * * Note that these functions return the actual result of the query: they do not * `revert` if an interface is not supported. It is up to the caller to decide * what to do in these cases. */ library ERC165Checker { // As per the EIP-165 spec, no interface should ever match 0xffffffff bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff; /** * @dev Returns true if `account` supports the {IERC165} interface. */ function supportsERC165(address account) internal view returns (bool) { // Any contract that implements ERC165 must explicitly indicate support of // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid return supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) && !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID); } /** * @dev Returns true if `account` supports the interface defined by * `interfaceId`. Support for {IERC165} itself is queried automatically. * * See {IERC165-supportsInterface}. */ function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { // query support of both ERC165 as per the spec and support of _interfaceId return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId); } /** * @dev Returns a boolean array where each value corresponds to the * interfaces passed in and whether they're supported or not. This allows * you to batch check interfaces for a contract where your expectation * is that some interfaces may not be supported. * * See {IERC165-supportsInterface}. * * _Available since v3.4._ */ function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool[] memory) { // an array of booleans corresponding to interfaceIds and whether they're supported or not bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); // query support of ERC165 itself if (supportsERC165(account)) { // query support of each interface in interfaceIds for (uint256 i = 0; i < interfaceIds.length; i++) { interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]); } } return interfaceIdsSupported; } /** * @dev Returns true if `account` supports all the interfaces defined in * `interfaceIds`. Support for {IERC165} itself is queried automatically. * * Batch-querying can lead to gas savings by skipping repeated checks for * {IERC165} support. * * See {IERC165-supportsInterface}. */ function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { // query support of ERC165 itself if (!supportsERC165(account)) { return false; } // query support of each interface in interfaceIds for (uint256 i = 0; i < interfaceIds.length; i++) { if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) { return false; } } // all interfaces supported return true; } /** * @notice Query if a contract implements an interface, does not check ERC165 support * @param account The address of the contract to query for support of an interface * @param interfaceId The interface identifier, as specified in ERC-165 * @return true if the contract at account indicates support of the interface with * identifier interfaceId, false otherwise * @dev Assumes that account contains a contract that supports ERC165, otherwise * the behavior of this method is undefined. This precondition can be checked * with {supportsERC165}. * * Some precompiled contracts will falsely indicate support for a given interface, so caution * should be exercised when using this function. * * Interface identification is specified in ERC-165. */ function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { // prepare call bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId); // perform static call bool success; uint256 returnSize; uint256 returnValue; assembly { success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20) returnSize := returndatasize() returnValue := mload(0x00) } return success && returnSize >= 0x20 && returnValue > 0; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol"; /// @title The ConfirmedOwner contract /// @notice A contract with helpers for basic contract ownership. contract ConfirmedOwner is ConfirmedOwnerWithProposal { constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {} } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IOwnable} from "../interfaces/IOwnable.sol"; /// @title The ConfirmedOwner contract /// @notice A contract with helpers for basic contract ownership. contract ConfirmedOwnerWithProposal is IOwnable { address private s_owner; address private s_pendingOwner; event OwnershipTransferRequested(address indexed from, address indexed to); event OwnershipTransferred(address indexed from, address indexed to); constructor(address newOwner, address pendingOwner) { // solhint-disable-next-line gas-custom-errors require(newOwner != address(0), "Cannot set owner to zero"); s_owner = newOwner; if (pendingOwner != address(0)) { _transferOwnership(pendingOwner); } } /// @notice Allows an owner to begin transferring ownership to a new address. function transferOwnership(address to) public override onlyOwner { _transferOwnership(to); } /// @notice Allows an ownership transfer to be completed by the recipient. function acceptOwnership() external override { // solhint-disable-next-line gas-custom-errors require(msg.sender == s_pendingOwner, "Must be proposed owner"); address oldOwner = s_owner; s_owner = msg.sender; s_pendingOwner = address(0); emit OwnershipTransferred(oldOwner, msg.sender); } /// @notice Get the current owner function owner() public view override returns (address) { return s_owner; } /// @notice validate, transfer ownership, and emit relevant events function _transferOwnership(address to) private { // solhint-disable-next-line gas-custom-errors require(to != msg.sender, "Cannot transfer to self"); s_pendingOwner = to; emit OwnershipTransferRequested(s_owner, to); } /// @notice validate access function _validateOwnership() internal view { // solhint-disable-next-line gas-custom-errors require(msg.sender == s_owner, "Only callable by owner"); } /// @notice Reverts if called by anyone other than the contract owner. modifier onlyOwner() { _validateOwnership(); _; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IOwnable { function owner() external returns (address); function transferOwnership(address recipient) external; function acceptOwnership() external; }
File 2 of 2: DataFeedsCache
// SPDX-License-Identifier: MIT pragma solidity 0.8.26; import {IReceiver} from "../keystone/interfaces/IReceiver.sol"; import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol"; import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol"; import {IDataFeedsCache} from "./interfaces/IDataFeedsCache.sol"; import {ITokenRecover} from "./interfaces/ITokenRecover.sol"; import {IERC165} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC165.sol"; import {IERC20} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/utils/SafeERC20.sol"; contract DataFeedsCache is IDataFeedsCache, IReceiver, ITokenRecover, ITypeAndVersion, OwnerIsCreator { using SafeERC20 for IERC20; string public constant override typeAndVersion = "DataFeedsCache 1.0.0"; // solhint-disable-next-line uint256 public constant override version = 7; /// Cache State struct WorkflowMetadata { address allowedSender; // Address of the sender allowed to send new reports address allowedWorkflowOwner; // ─╮ Address of the workflow owner bytes10 allowedWorkflowName; // ──╯ Name of the workflow } struct FeedConfig { uint8[] bundleDecimals; // Only appliciable to Bundle reports - Decimal reports have decimals encoded into the DataId. string description; // Description of the feed (e.g. "LINK / USD") WorkflowMetadata[] workflowMetadata; // Metadata for the feed } struct ReceivedBundleReport { bytes32 dataId; // Data ID of the feed from the received report uint32 timestamp; // Timestamp of the feed from the received report bytes bundle; // Report data in raw bytes } struct ReceivedDecimalReport { bytes32 dataId; // Data ID of the feed from the received report uint32 timestamp; // ─╮ Timestamp of the feed from the received report uint224 answer; // ───╯ Report data in uint224 } struct StoredBundleReport { bytes bundle; // The latest bundle report stored for a feed uint32 timestamp; // The timestamp of the latest bundle report } struct StoredDecimalReport { uint224 answer; // ───╮ The latest decimal report stored for a feed uint32 timestamp; // ─╯ The timestamp of the latest decimal report } /// The message sender determines which feed is being requested, as each proxy has a single associated feed mapping(address aggProxy => bytes16 dataId) private s_aggregatorProxyToDataId; /// The latest decimal reports for each decimal feed. This will always equal s_decimalReports[s_dataIdToRoundId[dataId]][dataId] mapping(bytes16 dataId => StoredDecimalReport) private s_latestDecimalReports; /// Decimal reports for each feed, per round mapping(uint256 roundId => mapping(bytes16 dataId => StoredDecimalReport)) private s_decimalReports; /// The latest bundle reports for each bundle feed mapping(bytes16 dataId => StoredBundleReport) private s_latestBundleReports; /// The latest round id for each feed mapping(bytes16 dataId => uint256 roundId) private s_dataIdToRoundId; /// Addresses that are permitted to configure all feeds mapping(address feedAdmin => bool isFeedAdmin) private s_feedAdmins; mapping(bytes16 dataId => FeedConfig) private s_feedConfigs; /// Whether a given Sender and Workflow have permission to write feed updates. /// reportHash is the keccak256 hash of the abi.encoded(dataId, sender, workflowOwner and workflowName) mapping(bytes32 reportHash => bool) private s_writePermissions; event BundleReportUpdated(bytes16 indexed dataId, uint256 indexed timestamp, bytes bundle); event DecimalReportUpdated( bytes16 indexed dataId, uint256 indexed roundId, uint256 indexed timestamp, uint224 answer ); event DecimalFeedConfigSet( bytes16 indexed dataId, uint8 decimals, string description, WorkflowMetadata[] workflowMetadata ); event BundleFeedConfigSet( bytes16 indexed dataId, uint8[] decimals, string description, WorkflowMetadata[] workflowMetadata ); event FeedConfigRemoved(bytes16 indexed dataId); event TokenRecovered(address indexed token, address indexed to, uint256 amount); event FeedAdminSet(address indexed feedAdmin, bool indexed isAdmin); event ProxyDataIdRemoved(address indexed proxy, bytes16 indexed dataId); event ProxyDataIdUpdated(address indexed proxy, bytes16 indexed dataId); event InvalidUpdatePermission(bytes16 indexed dataId, address sender, address workflowOwner, bytes10 workflowName); event StaleDecimalReport(bytes16 indexed dataId, uint256 reportTimestamp, uint256 latestTimestamp); event StaleBundleReport(bytes16 indexed dataId, uint256 reportTimestamp, uint256 latestTimestamp); error ArrayLengthMismatch(); error EmptyConfig(); error ErrorSendingNative(address to, uint256 amount, bytes data); error FeedNotConfigured(bytes16 dataId); error InsufficientBalance(uint256 balance, uint256 requiredBalance); error InvalidAddress(address addr); error InvalidDataId(); error InvalidWorkflowName(bytes10 workflowName); error UnauthorizedCaller(address caller); error NoMappingForSender(address proxy); modifier onlyFeedAdmin() { if (!s_feedAdmins[msg.sender]) revert UnauthorizedCaller(msg.sender); _; } /// @inheritdoc IERC165 function supportsInterface( bytes4 interfaceId ) public pure returns (bool) { return ( interfaceId == type(IDataFeedsCache).interfaceId || interfaceId == type(IERC165).interfaceId || interfaceId == type(IReceiver).interfaceId || interfaceId == type(ITokenRecover).interfaceId || interfaceId == type(ITypeAndVersion).interfaceId ); } /// @notice Get the workflow metadata of a feed /// @param dataId data ID of the feed /// @param startIndex The cursor to start fetching the metadata from /// @param maxCount The number of metadata to fetch /// @return workflowMetadata The metadata of the feed function getFeedMetadata( bytes16 dataId, uint256 startIndex, uint256 maxCount ) external view returns (WorkflowMetadata[] memory workflowMetadata) { FeedConfig storage feedConfig = s_feedConfigs[dataId]; uint256 workflowMetadataLength = feedConfig.workflowMetadata.length; if (workflowMetadataLength == 0) { revert FeedNotConfigured(dataId); } if (startIndex >= workflowMetadataLength) return new WorkflowMetadata[](0); uint256 endIndex = startIndex + maxCount; endIndex = endIndex > workflowMetadataLength || maxCount == 0 ? workflowMetadataLength : endIndex; workflowMetadata = new WorkflowMetadata[](endIndex - startIndex); for (uint256 idx; idx < workflowMetadata.length; idx++) { workflowMetadata[idx] = feedConfig.workflowMetadata[idx + startIndex]; } return workflowMetadata; } /// @notice Checks to see if this data ID, msg.sender, workflow owner, and workflow name are permissioned /// @param dataId The data ID for the feed /// @param workflowMetadata workflow metadata function checkFeedPermission( bytes16 dataId, WorkflowMetadata memory workflowMetadata ) external view returns (bool hasPermission) { bytes32 permission = _createReportHash( dataId, workflowMetadata.allowedSender, workflowMetadata.allowedWorkflowOwner, workflowMetadata.allowedWorkflowName ); return s_writePermissions[permission]; } // ================================================================ // │ Contract Config Interface │ // ================================================================ /// @notice Initializes the config for a decimal feed /// @param dataIds The data IDs of the feeds to configure /// @param descriptions The descriptions of the feeds /// @param workflowMetadata List of workflow metadata (owners, senders, and names) for every feed function setDecimalFeedConfigs( bytes16[] calldata dataIds, string[] calldata descriptions, WorkflowMetadata[] calldata workflowMetadata ) external onlyFeedAdmin { if (workflowMetadata.length == 0 || dataIds.length == 0) { revert EmptyConfig(); } if (dataIds.length != descriptions.length) { revert ArrayLengthMismatch(); } for (uint256 i; i < dataIds.length; ++i) { bytes16 dataId = dataIds[i]; if (dataId == bytes16(0)) revert InvalidDataId(); FeedConfig storage feedConfig = s_feedConfigs[dataId]; if (feedConfig.workflowMetadata.length > 0) { // Feed is already configured, remove the previous config for (uint256 j; j < feedConfig.workflowMetadata.length; ++j) { WorkflowMetadata memory feedCurrentWorkflowMetadata = feedConfig.workflowMetadata[j]; bytes32 reportHash = _createReportHash( dataId, feedCurrentWorkflowMetadata.allowedSender, feedCurrentWorkflowMetadata.allowedWorkflowOwner, feedCurrentWorkflowMetadata.allowedWorkflowName ); delete s_writePermissions[reportHash]; } delete s_feedConfigs[dataId]; emit FeedConfigRemoved(dataId); } for (uint256 j; j < workflowMetadata.length; ++j) { WorkflowMetadata memory feedWorkflowMetadata = workflowMetadata[j]; // Do those checks only once for the first data id if (i == 0) { if (feedWorkflowMetadata.allowedSender == address(0)) { revert InvalidAddress(feedWorkflowMetadata.allowedSender); } if (feedWorkflowMetadata.allowedWorkflowOwner == address(0)) { revert InvalidAddress(feedWorkflowMetadata.allowedWorkflowOwner); } if (feedWorkflowMetadata.allowedWorkflowName == bytes10(0)) { revert InvalidWorkflowName(feedWorkflowMetadata.allowedWorkflowName); } } bytes32 reportHash = _createReportHash( dataId, feedWorkflowMetadata.allowedSender, feedWorkflowMetadata.allowedWorkflowOwner, feedWorkflowMetadata.allowedWorkflowName ); s_writePermissions[reportHash] = true; feedConfig.workflowMetadata.push(feedWorkflowMetadata); } feedConfig.description = descriptions[i]; emit DecimalFeedConfigSet({ dataId: dataId, decimals: _getDecimals(dataId), description: descriptions[i], workflowMetadata: workflowMetadata }); } } /// @notice Initializes the config for a bundle feed /// @param dataIds The data IDs of the feeds to configure /// @param descriptions The descriptions of the feeds /// @param decimalsMatrix The number of decimals for each data point in the bundle for the feed /// @param workflowMetadata List of workflow metadata (owners, senders, and names) for every feed function setBundleFeedConfigs( bytes16[] calldata dataIds, string[] calldata descriptions, uint8[][] calldata decimalsMatrix, WorkflowMetadata[] calldata workflowMetadata ) external onlyFeedAdmin { if (workflowMetadata.length == 0 || dataIds.length == 0) { revert EmptyConfig(); } if (dataIds.length != descriptions.length || dataIds.length != decimalsMatrix.length) { revert ArrayLengthMismatch(); } for (uint256 i; i < dataIds.length; ++i) { bytes16 dataId = dataIds[i]; if (dataId == bytes16(0)) revert InvalidDataId(); FeedConfig storage feedConfig = s_feedConfigs[dataId]; if (feedConfig.workflowMetadata.length > 0) { // Feed is already configured, remove the previous config for (uint256 j; j < feedConfig.workflowMetadata.length; ++j) { WorkflowMetadata memory feedCurrentWorkflowMetadata = feedConfig.workflowMetadata[j]; bytes32 reportHash = _createReportHash( dataId, feedCurrentWorkflowMetadata.allowedSender, feedCurrentWorkflowMetadata.allowedWorkflowOwner, feedCurrentWorkflowMetadata.allowedWorkflowName ); delete s_writePermissions[reportHash]; } delete s_feedConfigs[dataId]; emit FeedConfigRemoved(dataId); } for (uint256 j; j < workflowMetadata.length; ++j) { WorkflowMetadata memory feedWorkflowMetadata = workflowMetadata[j]; // Do those checks only once for the first data id if (i == 0) { if (feedWorkflowMetadata.allowedSender == address(0)) { revert InvalidAddress(feedWorkflowMetadata.allowedSender); } if (feedWorkflowMetadata.allowedWorkflowOwner == address(0)) { revert InvalidAddress(feedWorkflowMetadata.allowedWorkflowOwner); } if (feedWorkflowMetadata.allowedWorkflowName == bytes10(0)) { revert InvalidWorkflowName(feedWorkflowMetadata.allowedWorkflowName); } } bytes32 reportHash = _createReportHash( dataId, feedWorkflowMetadata.allowedSender, feedWorkflowMetadata.allowedWorkflowOwner, feedWorkflowMetadata.allowedWorkflowName ); s_writePermissions[reportHash] = true; feedConfig.workflowMetadata.push(feedWorkflowMetadata); } feedConfig.bundleDecimals = decimalsMatrix[i]; feedConfig.description = descriptions[i]; emit BundleFeedConfigSet({ dataId: dataId, decimals: decimalsMatrix[i], description: descriptions[i], workflowMetadata: workflowMetadata }); } } /// @notice Removes feeds and all associated data, for a set of feeds /// @param dataIds And array of data IDs to delete the data and configs of function removeFeedConfigs( bytes16[] calldata dataIds ) external onlyFeedAdmin { for (uint256 i; i < dataIds.length; ++i) { bytes16 dataId = dataIds[i]; if (s_feedConfigs[dataId].workflowMetadata.length == 0) revert FeedNotConfigured(dataId); for (uint256 j; j < s_feedConfigs[dataId].workflowMetadata.length; ++j) { WorkflowMetadata memory feedWorkflowMetadata = s_feedConfigs[dataId].workflowMetadata[j]; bytes32 reportHash = _createReportHash( dataId, feedWorkflowMetadata.allowedSender, feedWorkflowMetadata.allowedWorkflowOwner, feedWorkflowMetadata.allowedWorkflowName ); delete s_writePermissions[reportHash]; } delete s_feedConfigs[dataId]; emit FeedConfigRemoved(dataId); } } /// @notice Sets a feed admin for all feeds, only callable by the Owner /// @param feedAdmin The feed admin function setFeedAdmin(address feedAdmin, bool isAdmin) external onlyOwner { if (feedAdmin == address(0)) revert InvalidAddress(feedAdmin); s_feedAdmins[feedAdmin] = isAdmin; emit FeedAdminSet(feedAdmin, isAdmin); } /// @notice Returns a bool is an address has feed admin permission for all feeds /// @param feedAdmin The feed admin /// @return isFeedAdmin bool if the address is the feed admin for all feeds function isFeedAdmin( address feedAdmin ) external view returns (bool) { return s_feedAdmins[feedAdmin]; } /// @inheritdoc IDataFeedsCache function updateDataIdMappingsForProxies( address[] calldata proxies, bytes16[] calldata dataIds ) external onlyFeedAdmin { uint256 numberOfProxies = proxies.length; if (numberOfProxies != dataIds.length) revert ArrayLengthMismatch(); for (uint256 i; i < numberOfProxies; i++) { s_aggregatorProxyToDataId[proxies[i]] = dataIds[i]; emit ProxyDataIdUpdated(proxies[i], dataIds[i]); } } /// @inheritdoc IDataFeedsCache function getDataIdForProxy( address proxy ) external view returns (bytes16 dataId) { return s_aggregatorProxyToDataId[proxy]; } /// @inheritdoc IDataFeedsCache function removeDataIdMappingsForProxies( address[] calldata proxies ) external onlyFeedAdmin { uint256 numberOfProxies = proxies.length; for (uint256 i; i < numberOfProxies; i++) { address proxy = proxies[i]; bytes16 dataId = s_aggregatorProxyToDataId[proxy]; delete s_aggregatorProxyToDataId[proxy]; emit ProxyDataIdRemoved(proxy, dataId); } } // ================================================================ // │ Token Transfer Interface │ // ================================================================ /// @inheritdoc ITokenRecover function recoverTokens(IERC20 token, address to, uint256 amount) external onlyOwner { if (address(token) == address(0)) { if (amount > address(this).balance) { revert InsufficientBalance(address(this).balance, amount); } (bool success, bytes memory data) = to.call{value: amount}(""); if (!success) revert ErrorSendingNative(to, amount, data); } else { if (amount > token.balanceOf(address(this))) { revert InsufficientBalance(token.balanceOf(address(this)), amount); } token.safeTransfer(to, amount); } emit TokenRecovered(address(token), to, amount); } // ================================================================ // │ Cache Update Interface │ // ================================================================ /// @inheritdoc IReceiver function onReport(bytes calldata metadata, bytes calldata report) external { (address workflowOwner, bytes10 workflowName) = _getWorkflowMetaData(metadata); // The first 32 bytes is the offset to the array // The second 32 bytes is the length of the array uint256 numReports = uint256(bytes32(report[32:64])); // Decimal reports contain 96 bytes per report // The total length should equal to the sum of: // 32 bytes for the offset // 32 bytes for the number of reports // the number of reports times 96 if (report.length == numReports * 96 + 64) { ReceivedDecimalReport[] memory decodedDecimalReports = abi.decode(report, (ReceivedDecimalReport[])); for (uint256 i; i < numReports; ++i) { ReceivedDecimalReport memory decodedDecimalReport = decodedDecimalReports[i]; // single dataId can have multiple permissions, to be updated by multiple Workflows bytes16 dataId = bytes16(decodedDecimalReport.dataId); bytes32 permission = _createReportHash(dataId, msg.sender, workflowOwner, workflowName); if (!s_writePermissions[permission]) { emit InvalidUpdatePermission(dataId, msg.sender, workflowOwner, workflowName); continue; } if (decodedDecimalReport.timestamp <= s_latestDecimalReports[dataId].timestamp) { emit StaleDecimalReport(dataId, decodedDecimalReport.timestamp, s_latestDecimalReports[dataId].timestamp); continue; } StoredDecimalReport memory decimalReport = StoredDecimalReport({answer: decodedDecimalReport.answer, timestamp: decodedDecimalReport.timestamp}); uint256 roundId = ++s_dataIdToRoundId[dataId]; s_latestDecimalReports[dataId] = decimalReport; s_decimalReports[roundId][dataId] = decimalReport; emit DecimalReportUpdated(dataId, roundId, decimalReport.timestamp, decimalReport.answer); // Needed for DF1 backward compatibility emit NewRound(roundId, address(0), decodedDecimalReport.timestamp); emit AnswerUpdated(int256(uint256(decodedDecimalReport.answer)), roundId, block.timestamp); } } // Bundle reports contain more bytes for the offsets // The total length should equal to the sum of: // 32 bytes for the offset // 32 bytes for the number of reports // the number of reports times 224 else { //For byte reports decode using ReceivedFeedReportBundle struct ReceivedBundleReport[] memory decodedBundleReports = abi.decode(report, (ReceivedBundleReport[])); for (uint256 i; i < decodedBundleReports.length; ++i) { ReceivedBundleReport memory decodedBundleReport = decodedBundleReports[i]; bytes16 dataId = bytes16(decodedBundleReport.dataId); // same dataId can have multiple permissions bytes32 permission = _createReportHash(dataId, msg.sender, workflowOwner, workflowName); if (!s_writePermissions[permission]) { emit InvalidUpdatePermission(dataId, msg.sender, workflowOwner, workflowName); continue; } if (decodedBundleReport.timestamp <= s_latestBundleReports[dataId].timestamp) { emit StaleBundleReport(dataId, decodedBundleReport.timestamp, s_latestBundleReports[dataId].timestamp); continue; } StoredBundleReport memory bundleReport = StoredBundleReport({bundle: decodedBundleReport.bundle, timestamp: decodedBundleReport.timestamp}); s_latestBundleReports[dataId] = bundleReport; emit BundleReportUpdated(dataId, bundleReport.timestamp, bundleReport.bundle); } } } // ================================================================ // │ Helper Methods │ // ================================================================ /// @notice Gets the Decimals of the feed from the data Id /// @param dataId The data ID for the feed /// @return feedDecimals The number of decimals the feed has function _getDecimals( bytes16 dataId ) internal pure returns (uint8 feedDecimals) { // Get the report type from data id. Report type has index of 7 bytes1 reportType = _getDataType(dataId, 7); // For decimal reports convert to uint8, then shift if (reportType >= hex"20" && reportType <= hex"60") { return uint8(reportType) - 32; } // If not decimal type, return 0 return 0; } /// @notice Extracts the workflow name and the workflow owner from the metadata parameter of onReport /// @param metadata The metadata in bytes format /// @return workflowOwner The owner of the workflow /// @return workflowName The name of the workflow function _getWorkflowMetaData( bytes memory metadata ) internal pure returns (address, bytes10) { address workflowOwner; bytes10 workflowName; // (first 32 bytes contain length of the byte array) // workflow_cid // offset 32, size 32 // workflow_name // offset 64, size 10 // workflow_owner // offset 74, size 20 // report_name // offset 94, size 2 assembly { // no shifting needed for bytes10 type workflowName := mload(add(metadata, 64)) // shift right by 12 bytes to get the actual value workflowOwner := shr(mul(12, 8), mload(add(metadata, 74))) } return (workflowOwner, workflowName); } /// @notice Extracts a byte from the data ID, to check data types /// @param dataId The data ID for the feed /// @param index The index of the byte to extract from the data Id /// @return dataType result The keccak256 hash of the abi.encoded inputs function _getDataType(bytes16 dataId, uint256 index) internal pure returns (bytes1 dataType) { // Convert bytes16 to bytes return abi.encodePacked(dataId)[index]; } /// @notice Creates a report hash used to permission write access /// @param dataId The data ID for the feed /// @param sender The msg.sender of the transaction calling into onReport /// @param workflowOwner The owner of the workflow /// @param workflowName The name of the workflow /// @return reportHash The keccak256 hash of the abi.encoded inputs function _createReportHash( bytes16 dataId, address sender, address workflowOwner, bytes10 workflowName ) internal pure returns (bytes32) { return keccak256(abi.encode(dataId, sender, workflowOwner, workflowName)); } // ================================================================ // │ Data Access Interface │ // ================================================================ /// Bundle Feed Interface function latestBundle() external view returns (bytes memory bundle) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return (s_latestBundleReports[dataId].bundle); } function bundleDecimals() external view returns (uint8[] memory bundleFeedDecimals) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return s_feedConfigs[dataId].bundleDecimals; } function latestBundleTimestamp() external view returns (uint256 timestamp) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return s_latestBundleReports[dataId].timestamp; } /// AggregatorInterface function latestAnswer() external view returns (int256 answer) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return int256(uint256(s_latestDecimalReports[dataId].answer)); } function latestTimestamp() external view returns (uint256 timestamp) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return s_latestDecimalReports[dataId].timestamp; } function latestRound() external view returns (uint256 round) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return s_dataIdToRoundId[dataId]; } function getAnswer( uint256 roundId ) external view returns (int256 answer) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return int256(uint256(s_decimalReports[roundId][dataId].answer)); } function getTimestamp( uint256 roundId ) external view returns (uint256 timestamp) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return s_decimalReports[roundId][dataId].timestamp; } /// AggregatorV3Interface function decimals() external view returns (uint8 feedDecimals) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return _getDecimals(dataId); } function description() external view returns (string memory feedDescription) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); return s_feedConfigs[dataId].description; } function getRoundData( uint80 roundId ) external view returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); uint256 timestamp = s_decimalReports[uint256(roundId)][dataId].timestamp; return (roundId, int256(uint256(s_decimalReports[uint256(roundId)][dataId].answer)), timestamp, timestamp, roundId); } function latestRoundData() external view returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { bytes16 dataId = s_aggregatorProxyToDataId[msg.sender]; if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender); uint80 roundId = uint80(s_dataIdToRoundId[dataId]); uint256 timestamp = s_latestDecimalReports[dataId].timestamp; return (roundId, int256(uint256(s_latestDecimalReports[dataId].answer)), timestamp, timestamp, roundId); } /// Direct access function getLatestBundle( bytes16 dataId ) external view returns (bytes memory bundle) { if (dataId == bytes16(0)) revert InvalidDataId(); return (s_latestBundleReports[dataId].bundle); } function getBundleDecimals( bytes16 dataId ) external view returns (uint8[] memory bundleFeedDecimals) { if (dataId == bytes16(0)) revert InvalidDataId(); return s_feedConfigs[dataId].bundleDecimals; } function getLatestBundleTimestamp( bytes16 dataId ) external view returns (uint256 timestamp) { if (dataId == bytes16(0)) revert InvalidDataId(); return s_latestBundleReports[dataId].timestamp; } function getLatestAnswer( bytes16 dataId ) external view returns (int256 answer) { if (dataId == bytes16(0)) revert InvalidDataId(); return int256(uint256(s_latestDecimalReports[dataId].answer)); } function getLatestTimestamp( bytes16 dataId ) external view returns (uint256 timestamp) { if (dataId == bytes16(0)) revert InvalidDataId(); return s_latestDecimalReports[dataId].timestamp; } function getLatestRoundData( bytes16 dataId ) external view returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { if (dataId == bytes16(0)) revert InvalidDataId(); uint80 roundId = uint80(s_dataIdToRoundId[dataId]); uint256 timestamp = s_latestDecimalReports[dataId].timestamp; return (roundId, int256(uint256(s_latestDecimalReports[dataId].answer)), timestamp, timestamp, roundId); } function getDecimals( bytes16 dataId ) external pure returns (uint8 feedDecimals) { if (dataId == bytes16(0)) revert InvalidDataId(); return _getDecimals(dataId); } function getDescription( bytes16 dataId ) external view returns (string memory feedDescription) { if (dataId == bytes16(0)) revert InvalidDataId(); return s_feedConfigs[dataId].description; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IERC165} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol"; /// @title IReceiver - receives keystone reports /// @notice Implementations must support the IReceiver interface through ERC165. interface IReceiver is IERC165 { /// @notice Handles incoming keystone reports. /// @dev If this function call reverts, it can be retried with a higher gas /// limit. The receiver is responsible for discarding stale reports. /// @param metadata Report's metadata. /// @param report Workflow report. function onReport(bytes calldata metadata, bytes calldata report) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ConfirmedOwner} from "./ConfirmedOwner.sol"; /// @title The OwnerIsCreator contract /// @notice A contract with helpers for basic contract ownership. contract OwnerIsCreator is ConfirmedOwner { constructor() ConfirmedOwner(msg.sender) {} } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ITypeAndVersion { function typeAndVersion() external pure returns (string memory); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import {IBundleBaseAggregator} from "./IBundleBaseAggregator.sol"; import {ICommonAggregator} from "./ICommonAggregator.sol"; import {IDecimalAggregator} from "./IDecimalAggregator.sol"; /// @notice IDataFeedsCache /// Responsible for storing data associated with a given data ID and additional request data. interface IDataFeedsCache is IDecimalAggregator, IBundleBaseAggregator, ICommonAggregator { /// @notice Remove feed configs. /// @param dataIds List of data IDs function removeFeedConfigs( bytes16[] calldata dataIds ) external; /// @notice Update mappings for AggregatorProxy -> Data ID /// @param proxies AggregatorProxy addresses /// @param dataIds Data IDs function updateDataIdMappingsForProxies(address[] calldata proxies, bytes16[] calldata dataIds) external; /// @notice Remove mappings for AggregatorProxy -> Data IDs /// @param proxies AggregatorProxy addresses to remove function removeDataIdMappingsForProxies( address[] calldata proxies ) external; /// @notice Get the Data ID mapping for a AggregatorProxy /// @param proxy AggregatorProxy addresses which will be reading feed data function getDataIdForProxy( address proxy ) external view returns (bytes16 dataId); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IERC20} from "./../../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC20.sol"; /// @notice ITokenRecover /// Implements the recoverTokens method, enabling the recovery of ERC-20 or native tokens accidentally sent to a /// contract outside of normal operations. interface ITokenRecover { /// @notice Transfer any ERC-20 or native tokens accidentally sent to this contract. /// @param token Token to transfer /// @param to Address to send payment to /// @param amount Amount of token to transfer function recoverTokens(IERC20 token, address to, uint256 amount) external; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol) pragma solidity ^0.8.20; import {IERC165} from "../utils/introspection/IERC165.sol"; // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the value of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the value of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves a `value` amount of tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 value) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the * caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the * allowance mechanism. `value` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 value) external returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC20Permit} from "../extensions/IERC20Permit.sol"; import {Address} from "../../../utils/Address.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using Address for address; /** * @dev An operation with an ERC20 token failed. */ error SafeERC20FailedOperation(address token); /** * @dev Indicates a failed `decreaseAllowance` request. */ error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); forceApprove(token, spender, oldAllowance + value); } /** * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no * value, non-reverting calls are assumed to be successful. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { uint256 currentAllowance = token.allowance(address(this), spender); if (currentAllowance < requestedDecrease) { revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); } forceApprove(token, spender, currentAllowance - requestedDecrease); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); _callOptionalReturn(token, approvalCall); } } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data); if (returndata.length != 0 && !abi.decode(returndata, (bool))) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol"; /// @title The ConfirmedOwner contract /// @notice A contract with helpers for basic contract ownership. contract ConfirmedOwner is ConfirmedOwnerWithProposal { constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {} } // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; interface IBundleBaseAggregator { function latestBundle() external view returns (bytes memory bundle); function bundleDecimals() external view returns (uint8[] memory); function latestBundleTimestamp() external view returns (uint256); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; interface ICommonAggregator { function description() external view returns (string memory); function version() external view returns (uint256); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; interface IDecimalAggregator { function latestAnswer() external view returns (int256); function latestRound() external view returns (uint256); function latestTimestamp() external view returns (uint256); function getAnswer( uint256 roundId ) external view returns (int256); function getTimestamp( uint256 roundId ) external view returns (uint256); function decimals() external view returns (uint8); function getRoundData( uint80 _roundId ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. * * ==== Security Considerations * * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be * considered as an intention to spend the allowance in any specific way. The second is that because permits have * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be * generally recommended is: * * ```solidity * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} * doThing(..., value); * } * * function doThing(..., uint256 value) public { * token.safeTransferFrom(msg.sender, address(this), value); * ... * } * ``` * * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also * {SafeERC20-safeTransferFrom}). * * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so * contracts should have entry points that don't rely on permit. */ interface IERC20Permit { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. * * CAUTION: See Security Considerations above. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) pragma solidity ^0.8.20; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev The ETH balance of the account is not enough to perform the operation. */ error AddressInsufficientBalance(address account); /** * @dev There's no code at `target` (it is not a contract). */ error AddressEmptyCode(address target); /** * @dev A call to an address target failed. The target may have reverted. */ error FailedInnerCall(); /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { if (address(this).balance < amount) { revert AddressInsufficientBalance(address(this)); } (bool success, ) = recipient.call{value: amount}(""); if (!success) { revert FailedInnerCall(); } } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason or custom error, it is bubbled * up by this function (like regular Solidity function calls). However, if * the call reverted with no returned reason, this function reverts with a * {FailedInnerCall} error. * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { if (address(this).balance < value) { revert AddressInsufficientBalance(address(this)); } (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an * unsuccessful call. */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata ) internal view returns (bytes memory) { if (!success) { _revert(returndata); } else { // only check if target is a contract if the call was successful and the return data is empty // otherwise we already know that it was a contract if (returndata.length == 0 && target.code.length == 0) { revert AddressEmptyCode(target); } return returndata; } } /** * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the * revert reason or with a default {FailedInnerCall} error. */ function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { if (!success) { _revert(returndata); } else { return returndata; } } /** * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. */ function _revert(bytes memory returndata) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert FailedInnerCall(); } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IOwnable} from "../interfaces/IOwnable.sol"; /// @title The ConfirmedOwner contract /// @notice A contract with helpers for basic contract ownership. contract ConfirmedOwnerWithProposal is IOwnable { address private s_owner; address private s_pendingOwner; event OwnershipTransferRequested(address indexed from, address indexed to); event OwnershipTransferred(address indexed from, address indexed to); constructor(address newOwner, address pendingOwner) { // solhint-disable-next-line gas-custom-errors require(newOwner != address(0), "Cannot set owner to zero"); s_owner = newOwner; if (pendingOwner != address(0)) { _transferOwnership(pendingOwner); } } /// @notice Allows an owner to begin transferring ownership to a new address. function transferOwnership(address to) public override onlyOwner { _transferOwnership(to); } /// @notice Allows an ownership transfer to be completed by the recipient. function acceptOwnership() external override { // solhint-disable-next-line gas-custom-errors require(msg.sender == s_pendingOwner, "Must be proposed owner"); address oldOwner = s_owner; s_owner = msg.sender; s_pendingOwner = address(0); emit OwnershipTransferred(oldOwner, msg.sender); } /// @notice Get the current owner function owner() public view override returns (address) { return s_owner; } /// @notice validate, transfer ownership, and emit relevant events function _transferOwnership(address to) private { // solhint-disable-next-line gas-custom-errors require(to != msg.sender, "Cannot transfer to self"); s_pendingOwner = to; emit OwnershipTransferRequested(s_owner, to); } /// @notice validate access function _validateOwnership() internal view { // solhint-disable-next-line gas-custom-errors require(msg.sender == s_owner, "Only callable by owner"); } /// @notice Reverts if called by anyone other than the contract owner. modifier onlyOwner() { _validateOwnership(); _; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IOwnable { function owner() external returns (address); function transferOwnership(address recipient) external; function acceptOwnership() external; }