Transaction Hash:
Block:
23455144 at Sep-27-2025 03:10:23 PM +UTC
Transaction Fee:
0.000162861760168073 ETH
$0.50
Gas Used:
147,971 Gas / 1.100632963 Gwei
Emitted Events:
| 159 |
AccessControlledOCR2Aggregator.NewTransmission( aggregatorRoundId=10877, answer=99722632, transmitter=[Receiver] AuthorizedForwarder, observationsTimestamp=1758985801, observations=[99718276, 99718276, 99718876, 99718876, 99720915, 99720915, 99722632, 99722632, 99722632, 99722632, 99723232, 99723232, 99723832, 99723832, 99723832, 99723832, 99725271, 99725871, 99725899], observers=0x0E0B02070C1200040811030A0D0F0905061001, juelsPerFeeCoin=191762811034322171396, configDigest=0001A0B18AB1E1A67513AFF1A75B8009CEC2570E457608CC58A0618DDE6ABAE7, epochAndRound=24592385 )
|
| 160 |
AccessControlledOCR2Aggregator.NewRound( roundId=10877, startedBy=0x00000000...000000000, startedAt=1758985801 )
|
| 161 |
AccessControlledOCR2Aggregator.AnswerUpdated( current=99722632, roundId=10877, updatedAt=1758985823 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 1.406050540812734003 Eth | 1.406124526312734003 Eth | 0.0000739855 | |
| 0x8F73090a...cC4f01E8c | |||||
| 0xb976d012...9264466D0 |
8.75031777613544183 Eth
Nonce: 153796
|
8.750154914375273757 Eth
Nonce: 153797
| 0.000162861760168073 |
Execution Trace
AuthorizedForwarder.forward( to=0x8F73090a7c58B8BDcC9A93cBB6816e5cC4f01E8c, data=0xB1DC65A40001A0B18AB1E1A67513AFF1A75B8009CEC2570E457608CC58A0618DDE6ABAE7000000000000000000000000000000000000000000000000000000000177400122C641A818D674FEE8B66EDE028EC9F7E2B5B1E74CF7E3AD88E2893B606A366800000000000000000000000000000000000000000000000000000000000000E000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000500010100010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000068D7FE490E0B02070C1200040811030A0D0F090506100100000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000A653E5CA1FF21760400000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000005F194840000000000000000000000000000000000000000000000000000000005F194840000000000000000000000000000000000000000000000000000000005F196DC0000000000000000000000000000000000000000000000000000000005F196DC0000000000000000000000000000000000000000000000000000000005F19ED30000000000000000000000000000000000000000000000000000000005F19ED30000000000000000000000000000000000000000000000000000000005F1A5880000000000000000000000000000000000000000000000000000000005F1A5880000000000000000000000000000000000000000000000000000000005F1A5880000000000000000000000000000000000000000000000000000000005F1A5880000000000000000000000000000000000000000000000000000000005F1A7E00000000000000000000000000000000000000000000000000000000005F1A7E00000000000000000000000000000000000000000000000000000000005F1AA380000000000000000000000000000000000000000000000000000000005F1AA380000000000000000000000000000000000000000000000000000000005F1AA380000000000000000000000000000000000000000000000000000000005F1AA380000000000000000000000000000000000000000000000000000000005F1AFD70000000000000000000000000000000000000000000000000000000005F1B22F0000000000000000000000000000000000000000000000000000000005F1B24B00000000000000000000000000000000000000000000000000000000000000074B7ABE71A26CDD25B4207C8213E722FF1C8B8DDEF88BFCD1BF1CC06CA805E86D68521DADCF67E07BD499A588EC0EC79AD8D6AB058F34F92FB4243FE5A78BA4D0C9117876C4F03DF858C9F746628A2EF049B74A79B8D20F483FA72803583336DB38612AAA6221C91AB4064B5C32060E30553CCED3B95C0682D58691D9F76619B3E414BDF443815EC3700CC04A9F01330A6D9D707029D119B7AB73B6093294DD0D5946F694C233605CCA6C249CD210D6DD01B64DDBF9E12604838BE21849FEADA1290D441C6F93222CE5B4B038471E56BE5ACB5679F919864A39A537BC58A4CFE400000000000000000000000000000000000000000000000000000000000000071E1131D7EF6B555B7813190405B99F316D868033D446E42047F04DFBD74945EB22EA1F3F11EFCB7349B12F1A5674C4E2657007B686D9F446C0796859DF5835570ACCAAE55ADFB361AB9C863FA1EA9BE0B4AB583B8ED3423F130670A5EFCE1E3434C6283BE625C22216A954F1FA0B99AC8AA39095ABA2E2ADFAC6434EEC80FFA635B4188E908CE4247B3C721770D1DD244C1211617A73BFE0E28E73FDFDF1ED9A7690A2791131E872DCBAC04490F9FEA0732FEDA83680A06AE07E691C59D88712732CE999D6D89DAED0E4F2C0E68B6C76816F7CD5066CA60DB53DD858CAA3F315 )
AccessControlledOCR2Aggregator.transmit( )-
Null: 0x000...001.c8a2a260( ) -
Null: 0x000...001.c8a2a260( ) -
Null: 0x000...001.c8a2a260( ) -
Null: 0x000...001.c8a2a260( ) -
Null: 0x000...001.c8a2a260( ) -
Null: 0x000...001.c8a2a260( ) -
Null: 0x000...001.c8a2a260( )
-
File 1 of 2: AuthorizedForwarder
File 2 of 2: AccessControlledOCR2Aggregator
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @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.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @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, it is bubbled up by this
* function (like regular Solidity function calls).
*
* 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.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @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`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) 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(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {ConfirmedOwnerWithProposal} from "../shared/access/ConfirmedOwnerWithProposal.sol";
import {AuthorizedReceiver} from "./AuthorizedReceiver.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
// solhint-disable gas-custom-errors
contract AuthorizedForwarder is ConfirmedOwnerWithProposal, AuthorizedReceiver {
using Address for address;
// solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
address public immutable linkToken;
event OwnershipTransferRequestedWithMessage(address indexed from, address indexed to, bytes message);
constructor(
address link,
address owner,
address recipient,
bytes memory message
) ConfirmedOwnerWithProposal(owner, recipient) {
require(link != address(0), "Link token cannot be a zero address");
linkToken = link;
if (recipient != address(0)) {
emit OwnershipTransferRequestedWithMessage(owner, recipient, message);
}
}
string public constant typeAndVersion = "AuthorizedForwarder 1.1.0";
// @notice Forward a call to another contract
// @dev Only callable by an authorized sender
// @param to address
// @param data to forward
function forward(address to, bytes calldata data) external validateAuthorizedSender {
require(to != linkToken, "Cannot forward to Link token");
_forward(to, data);
}
// @notice Forward multiple calls to other contracts in a multicall style
// @dev Only callable by an authorized sender
// @param tos An array of addresses to forward the calls to
// @param datas An array of data to forward to each corresponding address
function multiForward(address[] calldata tos, bytes[] calldata datas) external validateAuthorizedSender {
require(tos.length == datas.length, "Arrays must have the same length");
for (uint256 i = 0; i < tos.length; ++i) {
address to = tos[i];
require(to != linkToken, "Cannot forward to Link token");
// Perform the forward operation
_forward(to, datas[i]);
}
}
// @notice Forward a call to another contract
// @dev Only callable by the owner
// @param to address
// @param data to forward
function ownerForward(address to, bytes calldata data) external onlyOwner {
_forward(to, data);
}
// @notice Transfer ownership with instructions for recipient
// @param to address proposed recipient of ownership
// @param message instructions for recipient upon accepting ownership
function transferOwnershipWithMessage(address to, bytes calldata message) external {
transferOwnership(to);
emit OwnershipTransferRequestedWithMessage(msg.sender, to, message);
}
// @notice concrete implementation of AuthorizedReceiver
// @return bool of whether sender is authorized
function _canSetAuthorizedSenders() internal view override returns (bool) {
return owner() == msg.sender;
}
// @notice common forwarding functionality and validation
function _forward(address to, bytes calldata data) private {
require(to.isContract(), "Must forward to a contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = to.call(data);
if (!success) {
if (result.length == 0) revert("Forwarded call reverted without reason");
assembly {
revert(add(32, result), mload(result))
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {IAuthorizedReceiver} from "./interfaces/IAuthorizedReceiver.sol";
// solhint-disable gas-custom-errors
abstract contract AuthorizedReceiver is IAuthorizedReceiver {
mapping(address sender => bool authorized) private s_authorizedSenders;
address[] private s_authorizedSenderList;
event AuthorizedSendersChanged(address[] senders, address changedBy);
// @notice Sets the fulfillment permission for a given node. Use `true` to allow, `false` to disallow.
// @param senders The addresses of the authorized Chainlink node
function setAuthorizedSenders(address[] calldata senders) external override validateAuthorizedSenderSetter {
require(senders.length > 0, "Must have at least 1 sender");
// Set previous authorized senders to false
uint256 authorizedSendersLength = s_authorizedSenderList.length;
for (uint256 i = 0; i < authorizedSendersLength; ++i) {
s_authorizedSenders[s_authorizedSenderList[i]] = false;
}
// Set new to true
for (uint256 i = 0; i < senders.length; ++i) {
require(s_authorizedSenders[senders[i]] == false, "Must not have duplicate senders");
s_authorizedSenders[senders[i]] = true;
}
// Replace list
s_authorizedSenderList = senders;
emit AuthorizedSendersChanged(senders, msg.sender);
}
// @notice Retrieve a list of authorized senders
// @return array of addresses
function getAuthorizedSenders() external view override returns (address[] memory) {
return s_authorizedSenderList;
}
// @notice Use this to check if a node is authorized for fulfilling requests
// @param sender The address of the Chainlink node
// @return The authorization status of the node
function isAuthorizedSender(address sender) public view override returns (bool) {
return s_authorizedSenders[sender];
}
// @notice customizable guard of who can update the authorized sender list
// @return bool whether sender can update authorized sender list
function _canSetAuthorizedSenders() internal virtual returns (bool);
// @notice validates the sender is an authorized sender
function _validateIsAuthorizedSender() internal view {
require(isAuthorizedSender(msg.sender), "Not authorized sender");
}
// @notice prevents non-authorized addresses from calling this method
modifier validateAuthorizedSender() {
_validateIsAuthorizedSender();
_;
}
// @notice prevents non-authorized addresses from calling this method
modifier validateAuthorizedSenderSetter() {
require(_canSetAuthorizedSenders(), "Cannot set authorized senders");
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IAuthorizedReceiver {
function isAuthorizedSender(address sender) external view returns (bool);
function getAuthorizedSenders() external returns (address[] memory);
function setAuthorizedSenders(address[] calldata senders) external;
}
// 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: AccessControlledOCR2Aggregator
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "./OCR2Aggregator.sol";
import "./SimpleReadAccessController.sol";
/**
* @notice Wrapper of OCR2Aggregator which checks read access on Aggregator-interface methods
*/
contract AccessControlledOCR2Aggregator is OCR2Aggregator, SimpleReadAccessController {
constructor(
LinkTokenInterface _link,
int192 _minAnswer,
int192 _maxAnswer,
AccessControllerInterface _billingAccessController,
AccessControllerInterface _requesterAccessController,
uint8 _decimals,
string memory description
)
OCR2Aggregator(
_link,
_minAnswer,
_maxAnswer,
_billingAccessController,
_requesterAccessController,
_decimals,
description
) {
}
/*
* Versioning
*/
function typeAndVersion()
external
override
pure
virtual
returns (string memory)
{
return "AccessControlledOCR2Aggregator 1.0.0";
}
/*
* v2 Aggregator interface
*/
/// @inheritdoc OCR2Aggregator
function latestAnswer()
public
override
view
checkAccess()
returns (int256)
{
return super.latestAnswer();
}
/// @inheritdoc OCR2Aggregator
function latestTimestamp()
public
override
view
checkAccess()
returns (uint256)
{
return super.latestTimestamp();
}
/// @inheritdoc OCR2Aggregator
function latestRound()
public
override
view
checkAccess()
returns (uint256)
{
return super.latestRound();
}
/// @inheritdoc OCR2Aggregator
function getAnswer(uint256 _roundId)
public
override
view
checkAccess()
returns (int256)
{
return super.getAnswer(_roundId);
}
/// @inheritdoc OCR2Aggregator
function getTimestamp(uint256 _roundId)
public
override
view
checkAccess()
returns (uint256)
{
return super.getTimestamp(_roundId);
}
/*
* v3 Aggregator interface
*/
/// @inheritdoc OCR2Aggregator
function description()
public
override
view
checkAccess()
returns (string memory)
{
return super.description();
}
/// @inheritdoc OCR2Aggregator
function getRoundData(uint80 _roundId)
public
override
view
checkAccess()
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return super.getRoundData(_roundId);
}
/// @inheritdoc OCR2Aggregator
function latestRoundData()
public
override
view
checkAccess()
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return super.latestRoundData();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./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.0;
import "./interfaces/OwnableInterface.sol";
/**
* @title The ConfirmedOwner contract
* @notice A contract with helpers for basic contract ownership.
*/
contract ConfirmedOwnerWithProposal is OwnableInterface {
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
) {
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,
* pending.
*/
function transferOwnership(
address to
)
public
override
onlyOwner()
{
_transferOwnership(to);
}
/**
* @notice Allows an ownership transfer to be completed by the recipient.
*/
function acceptOwnership()
external
override
{
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
{
require(to != msg.sender, "Cannot transfer to self");
s_pendingOwner = to;
emit OwnershipTransferRequested(s_owner, to);
}
/**
* @notice validate access
*/
function _validateOwnership()
internal
view
{
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;
import "./interfaces/TypeAndVersionInterface.sol";
abstract contract OCR2Abstract is TypeAndVersionInterface {
// Maximum number of oracles the offchain reporting protocol is designed for
uint256 constant internal maxNumOracles = 31;
/**
* @notice triggers a new run of the offchain reporting protocol
* @param previousConfigBlockNumber block in which the previous config was set, to simplify historic analysis
* @param configDigest configDigest of this configuration
* @param configCount ordinal number of this config setting among all config settings over the life of this contract
* @param signers ith element is address ith oracle uses to sign a report
* @param transmitters ith element is address ith oracle uses to transmit a report via the transmit method
* @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly
* @param onchainConfig serialized configuration used by the contract (and possibly oracles)
* @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter
* @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
*/
event ConfigSet(
uint32 previousConfigBlockNumber,
bytes32 configDigest,
uint64 configCount,
address[] signers,
address[] transmitters,
uint8 f,
bytes onchainConfig,
uint64 offchainConfigVersion,
bytes offchainConfig
);
/**
* @notice sets offchain reporting protocol configuration incl. participating oracles
* @param signers addresses with which oracles sign the reports
* @param transmitters addresses oracles use to transmit the reports
* @param f number of faulty oracles the system can tolerate
* @param onchainConfig serialized configuration used by the contract (and possibly oracles)
* @param offchainConfigVersion version number for offchainEncoding schema
* @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
*/
function setConfig(
address[] memory signers,
address[] memory transmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
)
external
virtual;
/**
* @notice information about current offchain reporting protocol configuration
* @return configCount ordinal number of current config, out of all configs applied to this contract so far
* @return blockNumber block at which this config was set
* @return configDigest domain-separation tag for current config (see _configDigestFromConfigData)
*/
function latestConfigDetails()
external
view
virtual
returns (
uint32 configCount,
uint32 blockNumber,
bytes32 configDigest
);
function _configDigestFromConfigData(
uint256 chainId,
address contractAddress,
uint64 configCount,
address[] memory signers,
address[] memory transmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
)
internal
pure
returns (bytes32)
{
uint256 h = uint256(keccak256(abi.encode(chainId, contractAddress, configCount,
signers, transmitters, f, onchainConfig, offchainConfigVersion, offchainConfig
)));
uint256 prefixMask = type(uint256).max << (256-16); // 0xFFFF00..00
uint256 prefix = 0x0001 << (256-16); // 0x000100..00
return bytes32((prefix & prefixMask) | (h & ~prefixMask));
}
/**
* @notice optionally emitted to indicate the latest configDigest and epoch for
which a report was successfully transmitted. Alternatively, the contract may
use latestConfigDigestAndEpoch with scanLogs set to false.
*/
event Transmitted(
bytes32 configDigest,
uint32 epoch
);
/**
* @notice optionally returns the latest configDigest and epoch for which a
report was successfully transmitted. Alternatively, the contract may return
scanLogs set to true and use Transmitted events to provide this information
to offchain watchers.
* @return scanLogs indicates whether to rely on the configDigest and epoch
returned or whether to scan logs for the Transmitted event instead.
* @return configDigest
* @return epoch
*/
function latestConfigDigestAndEpoch()
external
view
virtual
returns(
bool scanLogs,
bytes32 configDigest,
uint32 epoch
);
/**
* @notice transmit is called to post a new report to the contract
* @param reportContext serialized report context containing configDigest, epoch, round, extraHash
* @param report serialized report, which the signatures are signing
* @param rs ith element is the R components of the ith signature on report. Must have at most maxNumOracles entries
* @param ss ith element is the S components of the ith signature on report. Must have at most maxNumOracles entries
* @param rawVs ith element is the the V component of the ith signature
*/
function transmit(
// NOTE: If these parameters are changed, expectedMsgDataLength and/or
// TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
bytes32[3] calldata reportContext,
bytes calldata report,
bytes32[] calldata rs, bytes32[] calldata ss, bytes32 rawVs // signatures
)
external
virtual;
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "./interfaces/AccessControllerInterface.sol";
import "./interfaces/AggregatorV2V3Interface.sol";
import "./interfaces/AggregatorValidatorInterface.sol";
import "./interfaces/LinkTokenInterface.sol";
import "./interfaces/TypeAndVersionInterface.sol";
import "./OCR2Abstract.sol";
import "./OwnerIsCreator.sol";
/**
* @notice OCR2Aggregator for numerical data with billing support.
* @dev
* If you read or change this, be sure to read or adjust the comments. They
* track the units of the values under consideration, and are crucial to
* the readability of the operations it specifies.
* @notice
* Billing Trust Model:
* Nothing in this contract prevents a billing admin from setting insane
* values for the billing parameters in setBilling. Oracles
* participating in this contract should regularly check that the
* parameters make sense. Similarly, the outstanding obligations of this
* contract to the oracles can exceed the funds held by the contract.
* Oracles participating in this contract should regularly check that it
* holds sufficient funds and stop interacting with it if funding runs
* out.
* This still leaves oracles with some risk due to TOCTOU issues.
* However, since the sums involved are pretty small (Ethereum
* transactions aren't that expensive in the end) and an oracle would
* likely stop participating in a contract it repeatedly lost money on,
* this risk is deemed acceptable. Oracles should also regularly
* withdraw any funds in the contract to prevent issues where the
* contract becomes underfunded at a later time, and different oracles
* are competing for the left-over funds.
* Finally, note that any change to the set of oracles or to the billing
* parameters will trigger payout of all oracles first (using the old
* parameters), a billing admin cannot take away funds that are already
* marked for payment.
*/
contract OCR2Aggregator is OCR2Abstract, OwnerIsCreator, AggregatorV2V3Interface {
// This contract is divided into sections. Each section defines a set of
// variables, events, and functions that belong together.
/***************************************************************************
* Section: Variables used in multiple other sections
**************************************************************************/
struct Transmitter {
bool active;
// Index of oracle in s_signersList/s_transmittersList
uint8 index;
// juels-denominated payment for transmitters, covering gas costs incurred
// by the transmitter plus additional rewards. The entire LINK supply (1e9
// LINK = 1e27 Juels) will always fit into a uint96.
uint96 paymentJuels;
}
mapping (address /* transmitter address */ => Transmitter) internal s_transmitters;
struct Signer {
bool active;
// Index of oracle in s_signersList/s_transmittersList
uint8 index;
}
mapping (address /* signer address */ => Signer) internal s_signers;
// s_signersList contains the signing address of each oracle
address[] internal s_signersList;
// s_transmittersList contains the transmission address of each oracle,
// i.e. the address the oracle actually sends transactions to the contract from
address[] internal s_transmittersList;
// We assume that all oracles contribute observations to all rounds. this
// variable tracks (per-oracle) from what round an oracle should be rewarded,
// i.e. the oracle gets (latestAggregatorRoundId -
// rewardFromAggregatorRoundId) * reward
uint32[maxNumOracles] internal s_rewardFromAggregatorRoundId;
bytes32 s_latestConfigDigest;
// Storing these fields used on the hot path in a HotVars variable reduces the
// retrieval of all of them to a single SLOAD.
struct HotVars {
// maximum number of faulty oracles
uint8 f;
// epoch and round from OCR protocol.
// 32 most sig bits for epoch, 8 least sig bits for round
uint40 latestEpochAndRound;
// Chainlink Aggregators expose a roundId to consumers. The offchain reporting
// protocol does not use this id anywhere. We increment it whenever a new
// transmission is made to provide callers with contiguous ids for successive
// reports.
uint32 latestAggregatorRoundId;
// Highest compensated gas price, in gwei uints
uint32 maximumGasPriceGwei;
// If gas price is less (in gwei units), transmitter gets half the savings
uint32 reasonableGasPriceGwei;
// Fixed LINK reward for each observer
uint32 observationPaymentGjuels;
// Fixed reward for transmitter
uint32 transmissionPaymentGjuels;
// Overhead incurred by accounting logic
uint24 accountingGas;
}
HotVars internal s_hotVars;
// Transmission records the median answer from the transmit transaction at
// time timestamp
struct Transmission {
int192 answer; // 192 bits ought to be enough for anyone
uint32 observationsTimestamp; // when were observations made offchain
uint32 transmissionTimestamp; // when was report received onchain
}
mapping(uint32 /* aggregator round ID */ => Transmission) internal s_transmissions;
// Lowest answer the system is allowed to report in response to transmissions
int192 immutable public minAnswer;
// Highest answer the system is allowed to report in response to transmissions
int192 immutable public maxAnswer;
/***************************************************************************
* Section: Constructor
**************************************************************************/
/**
* @param link address of the LINK contract
* @param minAnswer_ lowest answer the median of a report is allowed to be
* @param maxAnswer_ highest answer the median of a report is allowed to be
* @param requesterAccessController access controller for requesting new rounds
* @param decimals_ answers are stored in fixed-point format, with this many digits of precision
* @param description_ short human-readable description of observable this contract's answers pertain to
*/
constructor(
LinkTokenInterface link,
int192 minAnswer_,
int192 maxAnswer_,
AccessControllerInterface billingAccessController,
AccessControllerInterface requesterAccessController,
uint8 decimals_,
string memory description_
) {
s_linkToken = link;
emit LinkTokenSet(LinkTokenInterface(address(0)), link);
_setBillingAccessController(billingAccessController);
decimals = decimals_;
s_description = description_;
setRequesterAccessController(requesterAccessController);
setValidatorConfig(AggregatorValidatorInterface(address(0x0)), 0);
minAnswer = minAnswer_;
maxAnswer = maxAnswer_;
}
/***************************************************************************
* Section: OCR2Abstract Configuration
**************************************************************************/
// incremented each time a new config is posted. This count is incorporated
// into the config digest to prevent replay attacks.
uint32 internal s_configCount;
// makes it easier for offchain systems to extract config from logs
uint32 internal s_latestConfigBlockNumber;
// left as a function so this check can be disabled in derived contracts
function _requirePositiveF (
uint256 f
)
internal
pure
virtual
{
require(0 < f, "f must be positive");
}
struct SetConfigArgs {
address[] signers;
address[] transmitters;
uint8 f;
bytes onchainConfig;
uint64 offchainConfigVersion;
bytes offchainConfig;
}
/// @inheritdoc OCR2Abstract
function setConfig(
address[] memory signers,
address[] memory transmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
)
external
override
onlyOwner()
{
require(signers.length <= maxNumOracles, "too many oracles");
require(signers.length == transmitters.length, "oracle length mismatch");
require(3*f < signers.length, "faulty-oracle f too high");
_requirePositiveF(f);
require(keccak256(onchainConfig) == keccak256(abi.encodePacked(uint8(1) /*version*/, minAnswer, maxAnswer)), "invalid onchainConfig");
SetConfigArgs memory args = SetConfigArgs({
signers: signers,
transmitters: transmitters,
f: f,
onchainConfig: onchainConfig,
offchainConfigVersion: offchainConfigVersion,
offchainConfig: offchainConfig
});
s_hotVars.latestEpochAndRound = 0;
_payOracles();
// remove any old signer/transmitter addresses
uint256 oldLength = s_signersList.length;
for (uint256 i = 0; i < oldLength; i++) {
address signer = s_signersList[i];
address transmitter = s_transmittersList[i];
delete s_signers[signer];
delete s_transmitters[transmitter];
}
delete s_signersList;
delete s_transmittersList;
// add new signer/transmitter addresses
for (uint i = 0; i < args.signers.length; i++) {
require(
!s_signers[args.signers[i]].active,
"repeated signer address"
);
s_signers[args.signers[i]] = Signer({
active: true,
index: uint8(i)
});
require(
!s_transmitters[args.transmitters[i]].active,
"repeated transmitter address"
);
s_transmitters[args.transmitters[i]] = Transmitter({
active: true,
index: uint8(i),
paymentJuels: 0
});
}
s_signersList = args.signers;
s_transmittersList = args.transmitters;
s_hotVars.f = args.f;
uint32 previousConfigBlockNumber = s_latestConfigBlockNumber;
s_latestConfigBlockNumber = uint32(block.number);
s_configCount += 1;
s_latestConfigDigest = _configDigestFromConfigData(
block.chainid,
address(this),
s_configCount,
args.signers,
args.transmitters,
args.f,
args.onchainConfig,
args.offchainConfigVersion,
args.offchainConfig
);
emit ConfigSet(
previousConfigBlockNumber,
s_latestConfigDigest,
s_configCount,
args.signers,
args.transmitters,
args.f,
args.onchainConfig,
args.offchainConfigVersion,
args.offchainConfig
);
uint32 latestAggregatorRoundId = s_hotVars.latestAggregatorRoundId;
for (uint256 i = 0; i < args.signers.length; i++) {
s_rewardFromAggregatorRoundId[i] = latestAggregatorRoundId;
}
}
/// @inheritdoc OCR2Abstract
function latestConfigDetails()
external
override
view
returns (
uint32 configCount,
uint32 blockNumber,
bytes32 configDigest
)
{
return (s_configCount, s_latestConfigBlockNumber, s_latestConfigDigest);
}
/**
* @return list of addresses permitted to transmit reports to this contract
* @dev The list will match the order used to specify the transmitter during setConfig
*/
function getTransmitters()
external
view
returns(address[] memory)
{
return s_transmittersList;
}
/***************************************************************************
* Section: Onchain Validation
**************************************************************************/
// Configuration for validator
struct ValidatorConfig {
AggregatorValidatorInterface validator;
uint32 gasLimit;
}
ValidatorConfig private s_validatorConfig;
/**
* @notice indicates that the validator configuration has been set
* @param previousValidator previous validator contract
* @param previousGasLimit previous gas limit for validate calls
* @param currentValidator current validator contract
* @param currentGasLimit current gas limit for validate calls
*/
event ValidatorConfigSet(
AggregatorValidatorInterface indexed previousValidator,
uint32 previousGasLimit,
AggregatorValidatorInterface indexed currentValidator,
uint32 currentGasLimit
);
/**
* @notice validator configuration
* @return validator validator contract
* @return gasLimit gas limit for validate calls
*/
function getValidatorConfig()
external
view
returns (AggregatorValidatorInterface validator, uint32 gasLimit)
{
ValidatorConfig memory vc = s_validatorConfig;
return (vc.validator, vc.gasLimit);
}
/**
* @notice sets validator configuration
* @dev set newValidator to 0x0 to disable validate calls
* @param newValidator address of the new validator contract
* @param newGasLimit new gas limit for validate calls
*/
function setValidatorConfig(
AggregatorValidatorInterface newValidator,
uint32 newGasLimit
)
public
onlyOwner()
{
ValidatorConfig memory previous = s_validatorConfig;
if (previous.validator != newValidator || previous.gasLimit != newGasLimit) {
s_validatorConfig = ValidatorConfig({
validator: newValidator,
gasLimit: newGasLimit
});
emit ValidatorConfigSet(previous.validator, previous.gasLimit, newValidator, newGasLimit);
}
}
function _validateAnswer(
uint32 aggregatorRoundId,
int256 answer
)
private
{
ValidatorConfig memory vc = s_validatorConfig;
if (address(vc.validator) == address(0)) {
return;
}
uint32 prevAggregatorRoundId = aggregatorRoundId - 1;
int256 prevAggregatorRoundAnswer = s_transmissions[prevAggregatorRoundId].answer;
require(
_callWithExactGasEvenIfTargetIsNoContract(
vc.gasLimit,
address(vc.validator),
abi.encodeWithSignature(
"validate(uint256,int256,uint256,int256)",
uint256(prevAggregatorRoundId),
prevAggregatorRoundAnswer,
uint256(aggregatorRoundId),
answer
)
),
"insufficient gas"
);
}
uint256 private constant CALL_WITH_EXACT_GAS_CUSHION = 5_000;
/**
* @dev calls target address with exactly gasAmount gas and data as calldata
* or reverts if at least gasAmount gas is not available.
*/
function _callWithExactGasEvenIfTargetIsNoContract(
uint256 gasAmount,
address target,
bytes memory data
)
private
returns (bool sufficientGas)
{
// solhint-disable-next-line no-inline-assembly
assembly {
let g := gas()
// Compute g -= CALL_WITH_EXACT_GAS_CUSHION and check for underflow. We
// need the cushion since the logic following the above call to gas also
// costs gas which we cannot account for exactly. So cushion is a
// conservative upper bound for the cost of this logic.
if iszero(lt(g, CALL_WITH_EXACT_GAS_CUSHION)) {
g := sub(g, CALL_WITH_EXACT_GAS_CUSHION)
// If g - g//64 <= gasAmount, we don't have enough gas. (We subtract g//64
// because of EIP-150.)
if gt(sub(g, div(g, 64)), gasAmount) {
// Call and ignore success/return data. Note that we did not check
// whether a contract actually exists at the target address.
pop(call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0))
sufficientGas := true
}
}
}
}
/***************************************************************************
* Section: RequestNewRound
**************************************************************************/
AccessControllerInterface internal s_requesterAccessController;
/**
* @notice emitted when a new requester access controller contract is set
* @param old the address prior to the current setting
* @param current the address of the new access controller contract
*/
event RequesterAccessControllerSet(AccessControllerInterface old, AccessControllerInterface current);
/**
* @notice emitted to immediately request a new round
* @param requester the address of the requester
* @param configDigest the latest transmission's configDigest
* @param epoch the latest transmission's epoch
* @param round the latest transmission's round
*/
event RoundRequested(address indexed requester, bytes32 configDigest, uint32 epoch, uint8 round);
/**
* @notice address of the requester access controller contract
* @return requester access controller address
*/
function getRequesterAccessController()
external
view
returns (AccessControllerInterface)
{
return s_requesterAccessController;
}
/**
* @notice sets the requester access controller
* @param requesterAccessController designates the address of the new requester access controller
*/
function setRequesterAccessController(AccessControllerInterface requesterAccessController)
public
onlyOwner()
{
AccessControllerInterface oldController = s_requesterAccessController;
if (requesterAccessController != oldController) {
s_requesterAccessController = AccessControllerInterface(requesterAccessController);
emit RequesterAccessControllerSet(oldController, requesterAccessController);
}
}
/**
* @notice immediately requests a new round
* @return the aggregatorRoundId of the next round. Note: The report for this round may have been
* transmitted (but not yet mined) *before* requestNewRound() was even called. There is *no*
* guarantee of causality between the request and the report at aggregatorRoundId.
*/
function requestNewRound() external returns (uint80) {
require(msg.sender == owner() || s_requesterAccessController.hasAccess(msg.sender, msg.data),
"Only owner&requester can call");
uint40 latestEpochAndRound = s_hotVars.latestEpochAndRound;
uint32 latestAggregatorRoundId = s_hotVars.latestAggregatorRoundId;
emit RoundRequested(
msg.sender,
s_latestConfigDigest,
uint32(latestEpochAndRound >> 8),
uint8(latestEpochAndRound)
);
return latestAggregatorRoundId + 1;
}
/***************************************************************************
* Section: Transmission
**************************************************************************/
/**
* @notice indicates that a new report was transmitted
* @param aggregatorRoundId the round to which this report was assigned
* @param answer median of the observations attached to this report
* @param transmitter address from which the report was transmitted
* @param observationsTimestamp when were observations made offchain
* @param observations observations transmitted with this report
* @param observers i-th element is the oracle id of the oracle that made the i-th observation
* @param juelsPerFeeCoin exchange rate between feeCoin (e.g. ETH on Ethereum) and LINK, denominated in juels
* @param configDigest configDigest of transmission
* @param epochAndRound least-significant byte is the OCR protocol round number, the other bytes give the big-endian OCR protocol epoch number
*/
event NewTransmission(
uint32 indexed aggregatorRoundId,
int192 answer,
address transmitter,
uint32 observationsTimestamp,
int192[] observations,
bytes observers,
int192 juelsPerFeeCoin,
bytes32 configDigest,
uint40 epochAndRound
);
// Used to relieve stack pressure in transmit
struct Report {
uint32 observationsTimestamp;
bytes observers; // ith element is the index of the ith observer
int192[] observations; // ith element is the ith observation
int192 juelsPerFeeCoin;
}
// _decodeReport decodes a serialized report into a Report struct
function _decodeReport(bytes memory rawReport)
internal
pure
returns (
Report memory
)
{
uint32 observationsTimestamp;
bytes32 rawObservers;
int192[] memory observations;
int192 juelsPerFeeCoin;
(observationsTimestamp, rawObservers, observations, juelsPerFeeCoin) = abi.decode(rawReport, (uint32, bytes32, int192[], int192));
_requireExpectedReportLength(rawReport, observations);
uint256 numObservations = observations.length;
bytes memory observers = abi.encodePacked(rawObservers);
assembly {
// we truncate observers from length 32 to the number of observations
mstore(observers, numObservations)
}
return Report({
observationsTimestamp: observationsTimestamp,
observers: observers,
observations: observations,
juelsPerFeeCoin: juelsPerFeeCoin
});
}
// The constant-length components of the msg.data sent to transmit.
// See the "If we wanted to call sam" example on for example reasoning
// https://solidity.readthedocs.io/en/v0.7.2/abi-spec.html
uint256 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT =
4 + // function selector
32 * 3 + // 3 words containing reportContext
32 + // word containing start location of abiencoded report value
32 + // word containing start location of abiencoded rs value
32 + // word containing start location of abiencoded ss value
32 + // rawVs value
32 + // word containing length of report
32 + // word containing length rs
32 + // word containing length of ss
0; // placeholder
// Make sure the calldata length matches the inputs. Otherwise, the
// transmitter could append an arbitrarily long (up to gas-block limit)
// string of 0 bytes, which we would reimburse at a rate of 16 gas/byte, but
// which would only cost the transmitter 4 gas/byte.
function _requireExpectedMsgDataLength(
bytes calldata report,
bytes32[] calldata rs,
bytes32[] calldata ss
)
private
pure
{
// calldata will never be big enough to make this overflow
uint256 expected = TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT +
report.length + // one byte per entry in report
rs.length * 32 + // 32 bytes per entry in rs
ss.length * 32 + // 32 bytes per entry in ss
0; // placeholder
require(msg.data.length == expected, "calldata length mismatch");
}
/// @inheritdoc OCR2Abstract
function transmit(
// reportContext consists of:
// reportContext[0]: ConfigDigest
// reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round
// reportContext[2]: ExtraHash
bytes32[3] calldata reportContext,
bytes calldata report,
// ECDSA signatures
bytes32[] calldata rs,
bytes32[] calldata ss,
bytes32 rawVs
)
external
override
{
// NOTE: If the arguments to this function are changed, _requireExpectedMsgDataLength and/or
// TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
uint256 initialGas = gasleft(); // This line must come first
HotVars memory hotVars = s_hotVars;
uint40 epochAndRound = uint40(uint256(reportContext[1]));
require(hotVars.latestEpochAndRound < epochAndRound, "stale report");
require(s_transmitters[msg.sender].active, "unauthorized transmitter");
require(s_latestConfigDigest == reportContext[0], "configDigest mismatch");
_requireExpectedMsgDataLength(report, rs, ss);
require(rs.length == hotVars.f + 1, "wrong number of signatures");
require(rs.length == ss.length, "signatures out of registration");
// Verify signatures attached to report
{
bytes32 h = keccak256(abi.encode(keccak256(report), reportContext));
// i-th byte counts number of sigs made by i-th signer
uint256 signedCount = 0;
Signer memory signer;
for (uint i = 0; i < rs.length; i++) {
address signerAddress = ecrecover(h, uint8(rawVs[i])+27, rs[i], ss[i]);
signer = s_signers[signerAddress];
require(signer.active, "signature error");
unchecked{
signedCount += 1 << (8 * signer.index);
}
}
// The first byte of the mask can be 0, because we only ever have 31 oracles
require(signedCount & 0x0001010101010101010101010101010101010101010101010101010101010101 == signedCount, "duplicate signer");
}
int192 juelsPerFeeCoin = _report(hotVars, reportContext[0], epochAndRound, report);
_payTransmitter(hotVars, juelsPerFeeCoin, uint32(initialGas), msg.sender);
}
/**
* @notice details about the most recent report
* @return configDigest domain separation tag for the latest report
* @return epoch epoch in which the latest report was generated
* @return round OCR round in which the latest report was generated
* @return latestAnswer_ median value from latest report
* @return latestTimestamp_ when the latest report was transmitted
*/
function latestTransmissionDetails()
external
view
returns (
bytes32 configDigest,
uint32 epoch,
uint8 round,
int192 latestAnswer_,
uint64 latestTimestamp_
)
{
require(msg.sender == tx.origin, "Only callable by EOA");
return (
s_latestConfigDigest,
uint32(s_hotVars.latestEpochAndRound >> 8),
uint8(s_hotVars.latestEpochAndRound),
s_transmissions[s_hotVars.latestAggregatorRoundId].answer,
s_transmissions[s_hotVars.latestAggregatorRoundId].transmissionTimestamp
);
}
/// @inheritdoc OCR2Abstract
function latestConfigDigestAndEpoch()
external
override
view
virtual
returns(
bool scanLogs,
bytes32 configDigest,
uint32 epoch
)
{
return (false, s_latestConfigDigest, uint32(s_hotVars.latestEpochAndRound >> 8));
}
function _requireExpectedReportLength(
bytes memory report,
int192[] memory observations
)
private
pure
{
uint256 expected =
32 + // observationsTimestamp
32 + // rawObservers
32 + // observations offset
32 + // juelsPerFeeCoin
32 + // observations length
32 * observations.length + // observations payload
0;
require(report.length == expected, "report length mismatch");
}
function _report(
HotVars memory hotVars,
bytes32 configDigest,
uint40 epochAndRound,
bytes memory rawReport
)
internal
returns (int192 juelsPerFeeCoin)
{
Report memory report = _decodeReport(rawReport);
require(report.observations.length <= maxNumOracles, "num observations out of bounds");
// Offchain logic ensures that a quorum of oracles is operating on a matching set of at least
// 2f+1 observations. By assumption, up to f of those can be faulty, which includes being
// malformed. Conversely, more than f observations have to be well-formed and sent on chain.
require(hotVars.f < report.observations.length, "too few values to trust median");
hotVars.latestEpochAndRound = epochAndRound;
// get median, validate its range, store it in new aggregator round
int192 median = report.observations[report.observations.length/2];
require(minAnswer <= median && median <= maxAnswer, "median is out of min-max range");
hotVars.latestAggregatorRoundId++;
s_transmissions[hotVars.latestAggregatorRoundId] =
Transmission({
answer: median,
observationsTimestamp: report.observationsTimestamp,
transmissionTimestamp: uint32(block.timestamp)
});
// persist updates to hotVars
s_hotVars = hotVars;
emit NewTransmission(
hotVars.latestAggregatorRoundId,
median,
msg.sender,
report.observationsTimestamp,
report.observations,
report.observers,
report.juelsPerFeeCoin,
configDigest,
epochAndRound
);
// Emit these for backwards compatibility with offchain consumers
// that only support legacy events
emit NewRound(
hotVars.latestAggregatorRoundId,
address(0x0), // use zero address since we don't have anybody "starting" the round here
report.observationsTimestamp
);
emit AnswerUpdated(
median,
hotVars.latestAggregatorRoundId,
block.timestamp
);
_validateAnswer(hotVars.latestAggregatorRoundId, median);
return report.juelsPerFeeCoin;
}
/***************************************************************************
* Section: v2 AggregatorInterface
**************************************************************************/
/**
* @notice median from the most recent report
*/
function latestAnswer()
public
override
view
virtual
returns (int256)
{
return s_transmissions[s_hotVars.latestAggregatorRoundId].answer;
}
/**
* @notice timestamp of block in which last report was transmitted
*/
function latestTimestamp()
public
override
view
virtual
returns (uint256)
{
return s_transmissions[s_hotVars.latestAggregatorRoundId].transmissionTimestamp;
}
/**
* @notice Aggregator round (NOT OCR round) in which last report was transmitted
*/
function latestRound()
public
override
view
virtual
returns (uint256)
{
return s_hotVars.latestAggregatorRoundId;
}
/**
* @notice median of report from given aggregator round (NOT OCR round)
* @param roundId the aggregator round of the target report
*/
function getAnswer(uint256 roundId)
public
override
view
virtual
returns (int256)
{
if (roundId > 0xFFFFFFFF) { return 0; }
return s_transmissions[uint32(roundId)].answer;
}
/**
* @notice timestamp of block in which report from given aggregator round was transmitted
* @param roundId aggregator round (NOT OCR round) of target report
*/
function getTimestamp(uint256 roundId)
public
override
view
virtual
returns (uint256)
{
if (roundId > 0xFFFFFFFF) { return 0; }
return s_transmissions[uint32(roundId)].transmissionTimestamp;
}
/***************************************************************************
* Section: v3 AggregatorInterface
**************************************************************************/
/**
* @return answers are stored in fixed-point format, with this many digits of precision
*/
uint8 immutable public override decimals;
/**
* @notice aggregator contract version
*/
uint256 constant public override version = 6;
string internal s_description;
/**
* @notice human-readable description of observable this contract is reporting on
*/
function description()
public
override
view
virtual
returns (string memory)
{
return s_description;
}
/**
* @notice details for the given aggregator round
* @param roundId target aggregator round (NOT OCR round). Must fit in uint32
* @return roundId_ roundId
* @return answer median of report from given roundId
* @return startedAt timestamp of when observations were made offchain
* @return updatedAt timestamp of block in which report from given roundId was transmitted
* @return answeredInRound roundId
*/
function getRoundData(uint80 roundId)
public
override
view
virtual
returns (
uint80 roundId_,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
if(roundId > type(uint32).max) { return (0, 0, 0, 0, 0); }
Transmission memory transmission = s_transmissions[uint32(roundId)];
return (
roundId,
transmission.answer,
transmission.observationsTimestamp,
transmission.transmissionTimestamp,
roundId
);
}
/**
* @notice aggregator details for the most recently transmitted report
* @return roundId aggregator round of latest report (NOT OCR round)
* @return answer median of latest report
* @return startedAt timestamp of when observations were made offchain
* @return updatedAt timestamp of block containing latest report
* @return answeredInRound aggregator round of latest report
*/
function latestRoundData()
public
override
view
virtual
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
uint32 latestAggregatorRoundId = s_hotVars.latestAggregatorRoundId;
Transmission memory transmission = s_transmissions[latestAggregatorRoundId];
return (
latestAggregatorRoundId,
transmission.answer,
transmission.observationsTimestamp,
transmission.transmissionTimestamp,
latestAggregatorRoundId
);
}
/***************************************************************************
* Section: Configurable LINK Token
**************************************************************************/
// We assume that the token contract is correct. This contract is not written
// to handle misbehaving ERC20 tokens!
LinkTokenInterface internal s_linkToken;
/*
* @notice emitted when the LINK token contract is set
* @param oldLinkToken the address of the old LINK token contract
* @param newLinkToken the address of the new LINK token contract
*/
event LinkTokenSet(
LinkTokenInterface indexed oldLinkToken,
LinkTokenInterface indexed newLinkToken
);
/**
* @notice sets the LINK token contract used for paying oracles
* @param linkToken the address of the LINK token contract
* @param recipient remaining funds from the previous token contract are transferred
* here
* @dev this function will return early (without an error) without changing any state
* if linkToken equals getLinkToken().
* @dev this will trigger a payout so that a malicious owner cannot take from oracles
* what is already owed to them.
* @dev we assume that the token contract is correct. This contract is not written
* to handle misbehaving ERC20 tokens!
*/
function setLinkToken(
LinkTokenInterface linkToken,
address recipient
) external
onlyOwner()
{
LinkTokenInterface oldLinkToken = s_linkToken;
if (linkToken == oldLinkToken) {
// No change, nothing to be done
return;
}
// call balanceOf as a sanity check on whether we're talking to a token
// contract
linkToken.balanceOf(address(this));
// we break CEI here, but that's okay because we're dealing with a correct
// token contract (by assumption).
_payOracles();
uint256 remainingBalance = oldLinkToken.balanceOf(address(this));
require(oldLinkToken.transfer(recipient, remainingBalance), "transfer remaining funds failed");
s_linkToken = linkToken;
emit LinkTokenSet(oldLinkToken, linkToken);
}
/*
* @notice gets the LINK token contract used for paying oracles
* @return linkToken the address of the LINK token contract
*/
function getLinkToken()
external
view
returns(LinkTokenInterface linkToken)
{
return s_linkToken;
}
/***************************************************************************
* Section: BillingAccessController Management
**************************************************************************/
// Controls who can change billing parameters. A billingAdmin is not able to
// affect any OCR protocol settings and therefore cannot tamper with the
// liveness or integrity of a data feed. However, a billingAdmin can set
// faulty billing parameters causing oracles to be underpaid, or causing them
// to be paid so much that further calls to setConfig, setBilling,
// setLinkToken will always fail due to the contract being underfunded.
AccessControllerInterface internal s_billingAccessController;
/**
* @notice emitted when a new access-control contract is set
* @param old the address prior to the current setting
* @param current the address of the new access-control contract
*/
event BillingAccessControllerSet(AccessControllerInterface old, AccessControllerInterface current);
function _setBillingAccessController(AccessControllerInterface billingAccessController)
internal
{
AccessControllerInterface oldController = s_billingAccessController;
if (billingAccessController != oldController) {
s_billingAccessController = billingAccessController;
emit BillingAccessControllerSet(
oldController,
billingAccessController
);
}
}
/**
* @notice sets billingAccessController
* @param _billingAccessController new billingAccessController contract address
* @dev only owner can call this
*/
function setBillingAccessController(AccessControllerInterface _billingAccessController)
external
onlyOwner
{
_setBillingAccessController(_billingAccessController);
}
/**
* @notice gets billingAccessController
* @return address of billingAccessController contract
*/
function getBillingAccessController()
external
view
returns (AccessControllerInterface)
{
return s_billingAccessController;
}
/***************************************************************************
* Section: Billing Configuration
**************************************************************************/
/**
* @notice emitted when billing parameters are set
* @param maximumGasPriceGwei highest gas price for which transmitter will be compensated
* @param reasonableGasPriceGwei transmitter will receive reward for gas prices under this value
* @param observationPaymentGjuels reward to oracle for contributing an observation to a successfully transmitted report
* @param transmissionPaymentGjuels reward to transmitter of a successful report
* @param accountingGas gas overhead incurred by accounting logic
*/
event BillingSet(
uint32 maximumGasPriceGwei,
uint32 reasonableGasPriceGwei,
uint32 observationPaymentGjuels,
uint32 transmissionPaymentGjuels,
uint24 accountingGas
);
/**
* @notice sets billing parameters
* @param maximumGasPriceGwei highest gas price for which transmitter will be compensated
* @param reasonableGasPriceGwei transmitter will receive reward for gas prices under this value
* @param observationPaymentGjuels reward to oracle for contributing an observation to a successfully transmitted report
* @param transmissionPaymentGjuels reward to transmitter of a successful report
* @param accountingGas gas overhead incurred by accounting logic
* @dev access control provided by billingAccessController
*/
function setBilling(
uint32 maximumGasPriceGwei,
uint32 reasonableGasPriceGwei,
uint32 observationPaymentGjuels,
uint32 transmissionPaymentGjuels,
uint24 accountingGas
)
external
{
AccessControllerInterface access = s_billingAccessController;
require(msg.sender == owner() || access.hasAccess(msg.sender, msg.data),
"Only owner&billingAdmin can call");
_payOracles();
s_hotVars.maximumGasPriceGwei = maximumGasPriceGwei;
s_hotVars.reasonableGasPriceGwei = reasonableGasPriceGwei;
s_hotVars.observationPaymentGjuels = observationPaymentGjuels;
s_hotVars.transmissionPaymentGjuels = transmissionPaymentGjuels;
s_hotVars.accountingGas = accountingGas;
emit BillingSet(maximumGasPriceGwei, reasonableGasPriceGwei,
observationPaymentGjuels, transmissionPaymentGjuels, accountingGas);
}
/**
* @notice gets billing parameters
* @param maximumGasPriceGwei highest gas price for which transmitter will be compensated
* @param reasonableGasPriceGwei transmitter will receive reward for gas prices under this value
* @param observationPaymentGjuels reward to oracle for contributing an observation to a successfully transmitted report
* @param transmissionPaymentGjuels reward to transmitter of a successful report
* @param accountingGas gas overhead of the accounting logic
*/
function getBilling()
external
view
returns (
uint32 maximumGasPriceGwei,
uint32 reasonableGasPriceGwei,
uint32 observationPaymentGjuels,
uint32 transmissionPaymentGjuels,
uint24 accountingGas
)
{
return (
s_hotVars.maximumGasPriceGwei,
s_hotVars.reasonableGasPriceGwei,
s_hotVars.observationPaymentGjuels,
s_hotVars.transmissionPaymentGjuels,
s_hotVars.accountingGas
);
}
/***************************************************************************
* Section: Payments and Withdrawals
**************************************************************************/
/**
* @notice withdraws an oracle's payment from the contract
* @param transmitter the transmitter address of the oracle
* @dev must be called by oracle's payee address
*/
function withdrawPayment(address transmitter)
external
{
require(msg.sender == s_payees[transmitter], "Only payee can withdraw");
_payOracle(transmitter);
}
/**
* @notice query an oracle's payment amount, denominated in juels
* @param transmitterAddress the transmitter address of the oracle
*/
function owedPayment(address transmitterAddress)
public
view
returns (uint256)
{
Transmitter memory transmitter = s_transmitters[transmitterAddress];
if (!transmitter.active) { return 0; }
// safe from overflow:
// s_hotVars.latestAggregatorRoundId - s_rewardFromAggregatorRoundId[transmitter.index] <= 2**32
// s_hotVars.observationPaymentGjuels <= 2**32
// 1 gwei <= 2**32
// hence juelsAmount <= 2**96
uint256 juelsAmount =
uint256(s_hotVars.latestAggregatorRoundId - s_rewardFromAggregatorRoundId[transmitter.index]) *
uint256(s_hotVars.observationPaymentGjuels) *
(1 gwei);
juelsAmount += transmitter.paymentJuels;
return juelsAmount;
}
/**
* @notice emitted when an oracle has been paid LINK
* @param transmitter address from which the oracle sends reports to the transmit method
* @param payee address to which the payment is sent
* @param amount amount of LINK sent
* @param linkToken address of the LINK token contract
*/
event OraclePaid(
address indexed transmitter,
address indexed payee,
uint256 amount,
LinkTokenInterface indexed linkToken
);
// _payOracle pays out transmitter's balance to the corresponding payee, and zeros it out
function _payOracle(address transmitterAddress)
internal
{
Transmitter memory transmitter = s_transmitters[transmitterAddress];
if (!transmitter.active) { return; }
uint256 juelsAmount = owedPayment(transmitterAddress);
if (juelsAmount > 0) {
address payee = s_payees[transmitterAddress];
// Poses no re-entrancy issues, because LINK.transfer does not yield
// control flow.
require(s_linkToken.transfer(payee, juelsAmount), "insufficient funds");
s_rewardFromAggregatorRoundId[transmitter.index] = s_hotVars.latestAggregatorRoundId;
s_transmitters[transmitterAddress].paymentJuels = 0;
emit OraclePaid(transmitterAddress, payee, juelsAmount, s_linkToken);
}
}
// _payOracles pays out all transmitters, and zeros out their balances.
//
// It's much more gas-efficient to do this as a single operation, to avoid
// hitting storage too much.
function _payOracles()
internal
{
unchecked {
LinkTokenInterface linkToken = s_linkToken;
uint32 latestAggregatorRoundId = s_hotVars.latestAggregatorRoundId;
uint32[maxNumOracles] memory rewardFromAggregatorRoundId = s_rewardFromAggregatorRoundId;
address[] memory transmitters = s_transmittersList;
for (uint transmitteridx = 0; transmitteridx < transmitters.length; transmitteridx++) {
uint256 reimbursementAmountJuels = s_transmitters[transmitters[transmitteridx]].paymentJuels;
s_transmitters[transmitters[transmitteridx]].paymentJuels = 0;
uint256 obsCount = latestAggregatorRoundId - rewardFromAggregatorRoundId[transmitteridx];
uint256 juelsAmount =
obsCount * uint256(s_hotVars.observationPaymentGjuels) * (1 gwei) + reimbursementAmountJuels;
if (juelsAmount > 0) {
address payee = s_payees[transmitters[transmitteridx]];
// Poses no re-entrancy issues, because LINK.transfer does not yield
// control flow.
require(linkToken.transfer(payee, juelsAmount), "insufficient funds");
rewardFromAggregatorRoundId[transmitteridx] = latestAggregatorRoundId;
emit OraclePaid(transmitters[transmitteridx], payee, juelsAmount, linkToken);
}
}
// "Zero" the accounting storage variables
s_rewardFromAggregatorRoundId = rewardFromAggregatorRoundId;
}
}
/**
* @notice withdraw any available funds left in the contract, up to amount, after accounting for the funds due to participants in past reports
* @param recipient address to send funds to
* @param amount maximum amount to withdraw, denominated in LINK-wei.
* @dev access control provided by billingAccessController
*/
function withdrawFunds(
address recipient,
uint256 amount
)
external
{
require(msg.sender == owner() || s_billingAccessController.hasAccess(msg.sender, msg.data),
"Only owner&billingAdmin can call");
uint256 linkDue = _totalLinkDue();
uint256 linkBalance = s_linkToken.balanceOf(address(this));
require(linkBalance >= linkDue, "insufficient balance");
require(s_linkToken.transfer(recipient, _min(linkBalance - linkDue, amount)), "insufficient funds");
}
// Total LINK due to participants in past reports (denominated in Juels).
function _totalLinkDue()
internal
view
returns (uint256 linkDue)
{
// Argument for overflow safety: We do all computations in
// uint256s. The inputs to linkDue are:
// - the <= 31 observation rewards each of which has less than
// 64 bits (32 bits for observationPaymentGjuels, 32 bits
// for wei/gwei conversion). Hence 69 bits are sufficient for this part.
// - the <= 31 gas reimbursements, each of which consists of at most 96
// bits. Hence 101 bits are sufficient for this part.
// So we never need more than 102 bits.
address[] memory transmitters = s_transmittersList;
uint256 n = transmitters.length;
uint32 latestAggregatorRoundId = s_hotVars.latestAggregatorRoundId;
uint32[maxNumOracles] memory rewardFromAggregatorRoundId = s_rewardFromAggregatorRoundId;
for (uint i = 0; i < n; i++) {
linkDue += latestAggregatorRoundId - rewardFromAggregatorRoundId[i];
}
// Convert observationPaymentGjuels to uint256, or this overflows!
linkDue *= uint256(s_hotVars.observationPaymentGjuels) * (1 gwei);
for (uint i = 0; i < n; i++) {
linkDue += uint256(s_transmitters[transmitters[i]].paymentJuels);
}
}
/**
* @notice allows oracles to check that sufficient LINK balance is available
* @return availableBalance LINK available on this contract, after accounting for outstanding obligations. can become negative
*/
function linkAvailableForPayment()
external
view
returns (int256 availableBalance)
{
// there are at most one billion LINK, so this cast is safe
int256 balance = int256(s_linkToken.balanceOf(address(this)));
// according to the argument in the definition of _totalLinkDue,
// _totalLinkDue is never greater than 2**102, so this cast is safe
int256 due = int256(_totalLinkDue());
// safe from overflow according to above sizes
return int256(balance) - int256(due);
}
/**
* @notice number of observations oracle is due to be reimbursed for
* @param transmitterAddress address used by oracle for signing or transmitting reports
*/
function oracleObservationCount(address transmitterAddress)
external
view
returns (uint32)
{
Transmitter memory transmitter = s_transmitters[transmitterAddress];
if (!transmitter.active) { return 0; }
return s_hotVars.latestAggregatorRoundId - s_rewardFromAggregatorRoundId[transmitter.index];
}
/***************************************************************************
* Section: Transmitter Payment
**************************************************************************/
// Gas price at which the transmitter should be reimbursed, in gwei/gas
function _reimbursementGasPriceGwei(
uint256 txGasPriceGwei,
uint256 reasonableGasPriceGwei,
uint256 maximumGasPriceGwei
)
internal
pure
returns (uint256)
{
// this happens on the path for transmissions. we'd rather pay out
// a wrong reward than risk a liveness failure due to a revert.
unchecked {
// Reward the transmitter for choosing an efficient gas price: if they manage
// to come in lower than considered reasonable, give them half the savings.
uint256 gasPriceGwei = txGasPriceGwei;
if (txGasPriceGwei < reasonableGasPriceGwei) {
// Give transmitter half the savings for coming in under the reasonable gas price
gasPriceGwei += (reasonableGasPriceGwei - txGasPriceGwei) / 2;
}
// Don't reimburse a gas price higher than maximumGasPriceGwei
return _min(gasPriceGwei, maximumGasPriceGwei);
}
}
// gas reimbursement due the transmitter, in wei
function _transmitterGasCostWei(
uint256 initialGas,
uint256 gasPriceGwei,
uint256 callDataGas,
uint256 accountingGas,
uint256 leftGas
)
internal
pure
returns (uint256)
{
// this happens on the path for transmissions. we'd rather pay out
// a wrong reward than risk a liveness failure due to a revert.
unchecked {
require(initialGas >= leftGas, "leftGas cannot exceed initialGas");
uint256 usedGas =
initialGas - leftGas + // observed gas usage
callDataGas + accountingGas; // estimated gas usage
uint256 fullGasCostWei = usedGas * gasPriceGwei * (1 gwei);
return fullGasCostWei;
}
}
function _payTransmitter(
HotVars memory hotVars,
int192 juelsPerFeeCoin,
uint32 initialGas,
address transmitter
)
internal
virtual
{
// this happens on the path for transmissions. we'd rather pay out
// a wrong reward than risk a liveness failure due to a revert.
unchecked {
// we can't deal with negative juelsPerFeeCoin, better to just not pay
if (juelsPerFeeCoin < 0) {
return;
}
// Reimburse transmitter of the report for gas usage
uint256 gasPriceGwei = _reimbursementGasPriceGwei(
tx.gasprice / (1 gwei), // convert to ETH-gwei units
hotVars.reasonableGasPriceGwei,
hotVars.maximumGasPriceGwei
);
// The following is only an upper bound, as it ignores the cheaper cost for
// 0 bytes. Safe from overflow, because calldata just isn't that long.
uint256 callDataGasCost = 16 * msg.data.length;
uint256 gasLeft = gasleft();
uint256 gasCostEthWei = _transmitterGasCostWei(
uint256(initialGas),
gasPriceGwei,
callDataGasCost,
hotVars.accountingGas,
gasLeft
);
// Even if we assume absurdly large values, this still does not overflow. With
// - usedGas <= 1'000'000 gas <= 2**20 gas
// - weiPerGas <= 1'000'000 gwei <= 2**50 wei
// - hence gasCostEthWei <= 2**70
// - juelsPerFeeCoin <= 2**96 (more than the entire supply)
// we still fit into 166 bits
uint256 gasCostJuels = (gasCostEthWei * uint192(juelsPerFeeCoin))/1e18;
uint96 oldTransmitterPaymentJuels = s_transmitters[transmitter].paymentJuels;
uint96 newTransmitterPaymentJuels = uint96(uint256(oldTransmitterPaymentJuels) +
gasCostJuels + uint256(hotVars.transmissionPaymentGjuels) * (1 gwei));
// overflow *should* never happen, but if it does, let's not persist it.
if (newTransmitterPaymentJuels < oldTransmitterPaymentJuels) {
return;
}
s_transmitters[transmitter].paymentJuels = newTransmitterPaymentJuels;
}
}
/***************************************************************************
* Section: Payee Management
**************************************************************************/
// Addresses at which oracles want to receive payments, by transmitter address
mapping (address /* transmitter */ => address /* payment address */)
internal
s_payees;
// Payee addresses which must be approved by the owner
mapping (address /* transmitter */ => address /* payment address */)
internal
s_proposedPayees;
/**
* @notice emitted when a transfer of an oracle's payee address has been initiated
* @param transmitter address from which the oracle sends reports to the transmit method
* @param current the payee address for the oracle, prior to this setting
* @param proposed the proposed new payee address for the oracle
*/
event PayeeshipTransferRequested(
address indexed transmitter,
address indexed current,
address indexed proposed
);
/**
* @notice emitted when a transfer of an oracle's payee address has been completed
* @param transmitter address from which the oracle sends reports to the transmit method
* @param current the payee address for the oracle, prior to this setting
*/
event PayeeshipTransferred(
address indexed transmitter,
address indexed previous,
address indexed current
);
/**
* @notice sets the payees for transmitting addresses
* @param transmitters addresses oracles use to transmit the reports
* @param payees addresses of payees corresponding to list of transmitters
* @dev must be called by owner
* @dev cannot be used to change payee addresses, only to initially populate them
*/
function setPayees(
address[] calldata transmitters,
address[] calldata payees
)
external
onlyOwner()
{
require(transmitters.length == payees.length, "transmitters.size != payees.size");
for (uint i = 0; i < transmitters.length; i++) {
address transmitter = transmitters[i];
address payee = payees[i];
address currentPayee = s_payees[transmitter];
bool zeroedOut = currentPayee == address(0);
require(zeroedOut || currentPayee == payee, "payee already set");
s_payees[transmitter] = payee;
if (currentPayee != payee) {
emit PayeeshipTransferred(transmitter, currentPayee, payee);
}
}
}
/**
* @notice first step of payeeship transfer (safe transfer pattern)
* @param transmitter transmitter address of oracle whose payee is changing
* @param proposed new payee address
* @dev can only be called by payee address
*/
function transferPayeeship(
address transmitter,
address proposed
)
external
{
require(msg.sender == s_payees[transmitter], "only current payee can update");
require(msg.sender != proposed, "cannot transfer to self");
address previousProposed = s_proposedPayees[transmitter];
s_proposedPayees[transmitter] = proposed;
if (previousProposed != proposed) {
emit PayeeshipTransferRequested(transmitter, msg.sender, proposed);
}
}
/**
* @notice second step of payeeship transfer (safe transfer pattern)
* @param transmitter transmitter address of oracle whose payee is changing
* @dev can only be called by proposed new payee address
*/
function acceptPayeeship(
address transmitter
)
external
{
require(msg.sender == s_proposedPayees[transmitter], "only proposed payees can accept");
address currentPayee = s_payees[transmitter];
s_payees[transmitter] = msg.sender;
s_proposedPayees[transmitter] = address(0);
emit PayeeshipTransferred(transmitter, currentPayee, msg.sender);
}
/***************************************************************************
* Section: TypeAndVersionInterface
**************************************************************************/
function typeAndVersion()
external
override
pure
virtual
returns (string memory)
{
return "OCR2Aggregator 1.0.0";
}
/***************************************************************************
* Section: Helper Functions
**************************************************************************/
function _min(
uint256 a,
uint256 b
)
internal
pure
returns (uint256)
{
unchecked {
if (a < b) { return a; }
return b;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "./interfaces/TypeAndVersionInterface.sol";
import "./lib/ConfigDigestUtilEVMSimple.sol";
import "./OwnerIsCreator.sol";
import "./OCR2Abstract.sol";
/// @title OCRConfigurationStoreEVMSimple
/// @notice This contract stores configurations for protocol versions OCR2 and
/// above in contract storage. It uses the "EVMSimple" config digester.
contract OCRConfigurationStoreEVMSimple is TypeAndVersionInterface {
struct ConfigurationEVMSimple {
address[] signers;
address[] transmitters;
bytes onchainConfig;
bytes offchainConfig;
address contractAddress;
uint64 offchainConfigVersion;
uint32 configCount;
uint8 f;
}
/// @notice a list of configurations keyed by their digest
mapping(bytes32 => ConfigurationEVMSimple) internal s_configurations;
/// @notice emitted when a new configuration is added
event NewConfiguration(bytes32 indexed configDigest);
/// @notice adds a new configuration to the store
function addConfig(ConfigurationEVMSimple calldata configuration) external returns (bytes32) {
bytes32 configDigest = ConfigDigestUtilEVMSimple.configDigestFromConfigData(
block.chainid,
configuration.contractAddress,
configuration.configCount,
configuration.signers,
configuration.transmitters,
configuration.f,
configuration.onchainConfig,
configuration.offchainConfigVersion,
configuration.offchainConfig
);
s_configurations[configDigest] = configuration;
emit NewConfiguration(configDigest);
return configDigest;
}
/// @notice reads a configuration from the store
function readConfig(bytes32 configDigest) external view returns (ConfigurationEVMSimple memory) {
return s_configurations[configDigest];
}
/// @inheritdoc TypeAndVersionInterface
function typeAndVersion() external override pure virtual returns (string memory)
{
return "OCRConfigurationStoreEVMSimple 1.0.0";
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./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;
import "./SimpleWriteAccessController.sol";
/**
* @title SimpleReadAccessController
* @notice Gives access to:
* - any externally owned account (note that offchain actors can always read
* any contract storage regardless of onchain access control measures, so this
* does not weaken the access control while improving usability)
* - accounts explicitly added to an access list
* @dev SimpleReadAccessController is not suitable for access controlling writes
* since it grants any externally owned account access! See
* SimpleWriteAccessController for that.
*/
contract SimpleReadAccessController is SimpleWriteAccessController {
/**
* @notice Returns the access of an address
* @param _user The address to query
*/
function hasAccess(
address _user,
bytes memory _calldata
)
public
view
virtual
override
returns (bool)
{
return super.hasAccess(_user, _calldata) || _user == tx.origin;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./OwnerIsCreator.sol";
import "./interfaces/AccessControllerInterface.sol";
/**
* @title SimpleWriteAccessController
* @notice Gives access to accounts explicitly added to an access list by the
* controller's owner.
* @dev does not make any special permissions for externally, see
* SimpleReadAccessController for that.
*/
contract SimpleWriteAccessController is AccessControllerInterface, OwnerIsCreator {
bool public checkEnabled;
mapping(address => bool) internal accessList;
event AddedAccess(address user);
event RemovedAccess(address user);
event CheckAccessEnabled();
event CheckAccessDisabled();
constructor()
// TODO
// this is modified from the version in the Chainlink monorepo
// OwnerIsCreator()
{
checkEnabled = true;
}
/**
* @notice Returns the access of an address
* @param _user The address to query
*/
function hasAccess(
address _user,
bytes memory
)
public
view
virtual
override
returns (bool)
{
return accessList[_user] || !checkEnabled;
}
/**
* @notice Adds an address to the access list
* @param _user The address to add
*/
function addAccess(address _user)
external
onlyOwner()
{
if (!accessList[_user]) {
accessList[_user] = true;
emit AddedAccess(_user);
}
}
/**
* @notice Removes an address from the access list
* @param _user The address to remove
*/
function removeAccess(address _user)
external
onlyOwner()
{
if (accessList[_user]) {
accessList[_user] = false;
emit RemovedAccess(_user);
}
}
/**
* @notice makes the access check enforced
*/
function enableAccessCheck()
external
onlyOwner()
{
if (!checkEnabled) {
checkEnabled = true;
emit CheckAccessEnabled();
}
}
/**
* @notice makes the access check unenforced
*/
function disableAccessCheck()
external
onlyOwner()
{
if (checkEnabled) {
checkEnabled = false;
emit CheckAccessDisabled();
}
}
/**
* @dev reverts if the caller does not have access
*/
modifier checkAccess() {
require(hasAccess(msg.sender, msg.data), "No access");
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AccessControllerInterface {
function hasAccess(address user, bytes calldata data) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorInterface {
function latestAnswer() external view returns (int256);
function latestTimestamp() external view returns (uint256);
function latestRound() external view returns (uint256);
function getAnswer(uint256 roundId) external view returns (int256);
function getTimestamp(uint256 roundId) external view returns (uint256);
event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);
event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./AggregatorInterface.sol";
import "./AggregatorV3Interface.sol";
interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface
{
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
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
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorValidatorInterface {
function validate(
uint256 previousRoundId,
int256 previousAnswer,
uint256 currentRoundId,
int256 currentAnswer
) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface LinkTokenInterface {
function allowance(address owner, address spender) external view returns (uint256 remaining);
function approve(address spender, uint256 value) external returns (bool success);
function balanceOf(address owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 decimalPlaces);
function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);
function increaseApproval(address spender, uint256 subtractedValue) external;
function name() external view returns (string memory tokenName);
function symbol() external view returns (string memory tokenSymbol);
function totalSupply() external view returns (uint256 totalTokensIssued);
function transfer(address to, uint256 value) external returns (bool success);
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success);
function transferFrom(address from, address to, uint256 value) external returns (bool success);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface OwnableInterface {
function owner()
external
returns (
address
);
function transferOwnership(
address recipient
)
external;
function acceptOwnership()
external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface TypeAndVersionInterface{
function typeAndVersion()
external
pure
returns (string memory);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title ConfigDigestUtilEVMSimple
/// @notice ConfigDigest related utility functions for "EVMSimple" config
/// digester
library ConfigDigestUtilEVMSimple {
function configDigestFromConfigData(
uint256 chainId,
address contractAddress,
uint64 configCount,
address[] memory signers,
address[] memory transmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
) internal pure returns (bytes32)
{
uint256 hash = uint256(
keccak256(
abi.encode(
chainId,
contractAddress,
configCount,
signers,
transmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig
)));
uint256 prefixMask = type(uint256).max << (256-16); // 0xFFFF00..00
uint256 prefix = 0x0001 << (256-16); // 0x000100..00
return bytes32((prefix & prefixMask) | (hash & ~prefixMask));
}
}