Contract Name:
AirseekerRegistry
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../utils/interfaces/ISelfMulticall.sol";
interface IAccessControlRegistryAdminned is ISelfMulticall {
function accessControlRegistry() external view returns (address);
function adminRoleDescription() external view returns (string memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IAccessControlRegistryAdminned.sol";
interface IAccessControlRegistryAdminnedWithManager is
IAccessControlRegistryAdminned
{
function manager() external view returns (address);
function adminRole() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IOwnable {
function owner() external view returns (address);
function renounceOwnership() external;
function transferOwnership(address newOwner) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
import "../vendor/@openzeppelin/[email protected]/access/Ownable.sol";
import "../utils/ExtendedSelfMulticall.sol";
import "./interfaces/IAirseekerRegistry.sol";
import "../vendor/@openzeppelin/[email protected]/utils/structs/EnumerableSet.sol";
import "./interfaces/IApi3ServerV1.sol";
/// @title A contract where active data feeds and their specs are registered by
/// the contract owner for the respective Airseeker to refer to
/// @notice Airseeker is an application that pushes API provider-signed data to
/// chain when certain conditions are met so that the data feeds served on the
/// Api3ServerV1 contract are updated according to the respective specs. In
/// other words, this contract is an on-chain configuration file for an
/// Airseeker (or multiple Airseekers in a setup with redundancy).
/// The Airseeker must know which data feeds are active (and thus need to be
/// updated), the constituting Airnode (the oracle node that API providers
/// operate to sign data) addresses and request template IDs, what the
/// respective on-chain data feed values are, what the update parameters are,
/// and the URL of the signed APIs (from which Airseeker can fetch signed data)
/// that are hosted by the respective API providers.
/// The contract owner is responsible with leaving the state of this contract
/// in a way that Airseeker expects. For example, if a dAPI name is activated
/// without registering the respective data feed, the Airseeker will not have
/// access to the data that it needs to execute updates.
contract AirseekerRegistry is
Ownable,
ExtendedSelfMulticall,
IAirseekerRegistry
{
using EnumerableSet for EnumerableSet.Bytes32Set;
/// @notice Maximum number of Beacons in a Beacon set that can be
/// registered
/// @dev Api3ServerV1 introduces the concept of a Beacon, which is a
/// single-source data feed. Api3ServerV1 allows Beacons to be read
/// individually, or arbitrary combinations of them to be aggregated
/// on-chain to form multiple-source data feeds, which are called Beacon
/// sets. This contract does not support Beacon sets that consist of more
/// than `MAXIMUM_BEACON_COUNT_IN_SET` Beacons to be registered.
uint256 public constant override MAXIMUM_BEACON_COUNT_IN_SET = 21;
/// @notice Maximum encoded update parameters length
uint256 public constant override MAXIMUM_UPDATE_PARAMETERS_LENGTH = 1024;
/// @notice Maximum signed API URL length
uint256 public constant override MAXIMUM_SIGNED_API_URL_LENGTH = 256;
/// @notice Api3ServerV1 contract address
address public immutable override api3ServerV1;
/// @notice Airnode address to signed API URL
/// @dev An Airseeker can be configured to refer to additional signed APIs
/// than the ones whose URLs are stored in this contract for redundancy
mapping(address => string) public override airnodeToSignedApiUrl;
/// @notice Data feed ID to encoded details
mapping(bytes32 => bytes) public override dataFeedIdToDetails;
// Api3ServerV1 uses Beacon IDs (see the `deriveBeaconId()` implementation)
// and Beacon set IDs (see the `deriveBeaconSetId()` implementation) to
// address data feeds. We use data feed ID as a general term to refer to a
// Beacon ID/Beacon set ID.
// A data feed ID is immutable (i.e., it always points to the same Beacon
// or Beacon set). Api3ServerV1 allows a dAPI name to be pointed to a data
// feed ID by privileged accounts to implement a mutable data feed
// addressing scheme.
// If the data feed ID or dAPI name should be used to read a data feed
// depends on the use case. To support both schemes, AirseekerRegistry
// allows data feeds specs to be defined with either the data feed ID or
// the dAPI name.
EnumerableSet.Bytes32Set private activeDataFeedIds;
EnumerableSet.Bytes32Set private activeDapiNames;
// Considering that the update parameters are typically reused between data
// feeds, a hash map is used to avoid storing the same update parameters
// redundantly
mapping(bytes32 => bytes32) private dataFeedIdToUpdateParametersHash;
mapping(bytes32 => bytes32) private dapiNameToUpdateParametersHash;
mapping(bytes32 => bytes) private updateParametersHashToValue;
// Length of `abi.encode(address, bytes32)`
uint256 private constant DATA_FEED_DETAILS_LENGTH_FOR_SINGLE_BEACON =
32 + 32;
// Length of `abi.encode(address[2], bytes32[2])`
uint256
private constant DATA_FEED_DETAILS_LENGTH_FOR_BEACON_SET_WITH_TWO_BEACONS =
32 + 32 + (32 + 2 * 32) + (32 + 2 * 32);
// Length of
// `abi.encode(address[MAXIMUM_BEACON_COUNT_IN_SET], bytes32[MAXIMUM_BEACON_COUNT_IN_SET])`
uint256 private constant MAXIMUM_DATA_FEED_DETAILS_LENGTH =
32 +
32 +
(32 + MAXIMUM_BEACON_COUNT_IN_SET * 32) +
(32 + MAXIMUM_BEACON_COUNT_IN_SET * 32);
/// @dev Reverts if the data feed ID is zero
/// @param dataFeedId Data feed ID
modifier onlyNonZeroDataFeedId(bytes32 dataFeedId) {
require(dataFeedId != bytes32(0), "Data feed ID zero");
_;
}
/// @dev Reverts if the dAPI name is zero
/// @param dapiName dAPI name
modifier onlyNonZeroDapiName(bytes32 dapiName) {
require(dapiName != bytes32(0), "dAPI name zero");
_;
}
/// @dev Reverts if the update parameters are too long
/// @param updateParameters Update parameters
modifier onlyValidUpdateParameters(bytes calldata updateParameters) {
require(
updateParameters.length <= MAXIMUM_UPDATE_PARAMETERS_LENGTH,
"Update parameters too long"
);
_;
}
/// @param owner_ Owner address
/// @param api3ServerV1_ Api3ServerV1 contract address
constructor(address owner_, address api3ServerV1_) Ownable(owner_) {
require(api3ServerV1_ != address(0), "Api3ServerV1 address zero");
api3ServerV1 = api3ServerV1_;
}
/// @notice Returns the owner address
/// @return Owner address
function owner() public view override(Ownable, IOwnable) returns (address) {
return super.owner();
}
/// @notice Overriden to be disabled
function renounceOwnership() public pure override(Ownable, IOwnable) {
revert("Ownership cannot be renounced");
}
/// @notice Overriden to be disabled
function transferOwnership(
address
) public pure override(Ownable, IOwnable) {
revert("Ownership cannot be transferred");
}
/// @notice Called by the owner to set the data feed ID to be activated
/// @param dataFeedId Data feed ID
function setDataFeedIdToBeActivated(
bytes32 dataFeedId
) external override onlyOwner onlyNonZeroDataFeedId(dataFeedId) {
if (activeDataFeedIds.add(dataFeedId)) {
emit ActivatedDataFeedId(dataFeedId);
}
}
/// @notice Called by the owner to set the dAPI name to be activated
/// @param dapiName dAPI name
function setDapiNameToBeActivated(
bytes32 dapiName
) external override onlyOwner onlyNonZeroDapiName(dapiName) {
if (activeDapiNames.add(dapiName)) {
emit ActivatedDapiName(dapiName);
}
}
/// @notice Called by the owner to set the data feed ID to be deactivated
/// @param dataFeedId Data feed ID
function setDataFeedIdToBeDeactivated(
bytes32 dataFeedId
) external override onlyOwner onlyNonZeroDataFeedId(dataFeedId) {
if (activeDataFeedIds.remove(dataFeedId)) {
emit DeactivatedDataFeedId(dataFeedId);
}
}
/// @notice Called by the owner to set the dAPI name to be deactivated
/// @param dapiName dAPI name
function setDapiNameToBeDeactivated(
bytes32 dapiName
) external override onlyOwner onlyNonZeroDapiName(dapiName) {
if (activeDapiNames.remove(dapiName)) {
emit DeactivatedDapiName(dapiName);
}
}
/// @notice Called by the owner to set the data feed ID update parameters.
/// The update parameters must be encoded in a format that Airseeker
/// expects.
/// @param dataFeedId Data feed ID
/// @param updateParameters Update parameters
function setDataFeedIdUpdateParameters(
bytes32 dataFeedId,
bytes calldata updateParameters
)
external
override
onlyOwner
onlyNonZeroDataFeedId(dataFeedId)
onlyValidUpdateParameters(updateParameters)
{
bytes32 updateParametersHash = keccak256(updateParameters);
if (
dataFeedIdToUpdateParametersHash[dataFeedId] != updateParametersHash
) {
dataFeedIdToUpdateParametersHash[dataFeedId] = updateParametersHash;
if (
updateParametersHashToValue[updateParametersHash].length !=
updateParameters.length
) {
updateParametersHashToValue[
updateParametersHash
] = updateParameters;
}
emit UpdatedDataFeedIdUpdateParameters(
dataFeedId,
updateParameters
);
}
}
/// @notice Called by the owner to set the dAPI name update parameters.
/// The update parameters must be encoded in a format that Airseeker
/// expects.
/// @param dapiName dAPI name
/// @param updateParameters Update parameters
function setDapiNameUpdateParameters(
bytes32 dapiName,
bytes calldata updateParameters
)
external
override
onlyOwner
onlyNonZeroDapiName(dapiName)
onlyValidUpdateParameters(updateParameters)
{
bytes32 updateParametersHash = keccak256(updateParameters);
if (dapiNameToUpdateParametersHash[dapiName] != updateParametersHash) {
dapiNameToUpdateParametersHash[dapiName] = updateParametersHash;
if (
updateParametersHashToValue[updateParametersHash].length !=
updateParameters.length
) {
updateParametersHashToValue[
updateParametersHash
] = updateParameters;
}
emit UpdatedDapiNameUpdateParameters(dapiName, updateParameters);
}
}
/// @notice Called by the owner to set the signed API URL for the Airnode.
/// The signed API must implement the specific interface that Airseeker
/// expects.
/// @param airnode Airnode address
/// @param signedApiUrl Signed API URL
function setSignedApiUrl(
address airnode,
string calldata signedApiUrl
) external override onlyOwner {
require(airnode != address(0), "Airnode address zero");
require(
abi.encodePacked(signedApiUrl).length <=
MAXIMUM_SIGNED_API_URL_LENGTH,
"Signed API URL too long"
);
if (
keccak256(abi.encodePacked(airnodeToSignedApiUrl[airnode])) !=
keccak256(abi.encodePacked(signedApiUrl))
) {
airnodeToSignedApiUrl[airnode] = signedApiUrl;
emit UpdatedSignedApiUrl(airnode, signedApiUrl);
}
}
/// @notice Registers the data feed. In the case that the data feed is a
/// Beacon, the details should be the ABI-encoded Airnode address and
/// template ID. In the case that the data feed is a Beacon set, the
/// details should be the ABI-encoded Airnode addresses array and template
/// IDs array.
/// @param dataFeedDetails Data feed details
/// @return dataFeedId Data feed ID
function registerDataFeed(
bytes calldata dataFeedDetails
) external override returns (bytes32 dataFeedId) {
uint256 dataFeedDetailsLength = dataFeedDetails.length;
if (
dataFeedDetailsLength == DATA_FEED_DETAILS_LENGTH_FOR_SINGLE_BEACON
) {
// dataFeedId maps to a Beacon
(address airnode, bytes32 templateId) = abi.decode(
dataFeedDetails,
(address, bytes32)
);
require(airnode != address(0), "Airnode address zero");
dataFeedId = deriveBeaconId(airnode, templateId);
} else if (
dataFeedDetailsLength >=
DATA_FEED_DETAILS_LENGTH_FOR_BEACON_SET_WITH_TWO_BEACONS
) {
require(
dataFeedDetailsLength <= MAXIMUM_DATA_FEED_DETAILS_LENGTH,
"Data feed details too long"
);
(address[] memory airnodes, bytes32[] memory templateIds) = abi
.decode(dataFeedDetails, (address[], bytes32[]));
require(
abi.encode(airnodes, templateIds).length ==
dataFeedDetailsLength,
"Data feed details trail"
);
uint256 beaconCount = airnodes.length;
require(
beaconCount == templateIds.length,
"Parameter length mismatch"
);
bytes32[] memory beaconIds = new bytes32[](beaconCount);
for (uint256 ind = 0; ind < beaconCount; ind++) {
require(airnodes[ind] != address(0), "Airnode address zero");
beaconIds[ind] = deriveBeaconId(
airnodes[ind],
templateIds[ind]
);
}
dataFeedId = deriveBeaconSetId(beaconIds);
} else {
revert("Data feed details too short");
}
if (dataFeedIdToDetails[dataFeedId].length != dataFeedDetailsLength) {
dataFeedIdToDetails[dataFeedId] = dataFeedDetails;
emit RegisteredDataFeed(dataFeedId, dataFeedDetails);
}
}
/// @notice In an imaginary array consisting of the active data feed IDs
/// and active dAPI names, picks the index-th identifier, and returns all
/// data about the respective data feed that is available. Whenever data is
/// not available (including the case where index does not correspond to an
/// active data feed), returns empty values.
/// @dev Airseeker uses this function to get all the data it needs about an
/// active data feed with a single RPC call.
/// Since active data feed IDs and dAPI names are kept in respective
/// EnumerableSet types, addition and removal of items to these may change
/// the order of the remaining items. Therefore, do not depend on an index
/// to address a specific data feed consistently.
/// @param index Index
/// @return dataFeedId Data feed ID
/// @return dapiName dAPI name (`bytes32(0)` if the active data feed is
/// identified by a data feed ID)
/// @return dataFeedDetails Data feed details
/// @return dataFeedValue Data feed value read from Api3ServerV1
/// @return dataFeedTimestamp Data feed timestamp read from Api3ServerV1
/// @return beaconValues Beacon values read from Api3ServerV1
/// @return beaconTimestamps Beacon timestamps read from Api3ServerV1
/// @return updateParameters Update parameters
/// @return signedApiUrls Signed API URLs of the Beacon Airnodes
function activeDataFeed(
uint256 index
)
external
view
override
returns (
bytes32 dataFeedId,
bytes32 dapiName,
bytes memory dataFeedDetails,
int224 dataFeedValue,
uint32 dataFeedTimestamp,
int224[] memory beaconValues,
uint32[] memory beaconTimestamps,
bytes memory updateParameters,
string[] memory signedApiUrls
)
{
uint256 activeDataFeedIdsLength = activeDataFeedIdCount();
if (index < activeDataFeedIdsLength) {
dataFeedId = activeDataFeedIds.at(index);
updateParameters = dataFeedIdToUpdateParameters(dataFeedId);
} else if (index < activeDataFeedIdsLength + activeDapiNames.length()) {
dapiName = activeDapiNames.at(index - activeDataFeedIdsLength);
dataFeedId = IApi3ServerV1(api3ServerV1).dapiNameHashToDataFeedId(
keccak256(abi.encodePacked(dapiName))
);
updateParameters = dapiNameToUpdateParameters(dapiName);
}
if (dataFeedId != bytes32(0)) {
dataFeedDetails = dataFeedIdToDetails[dataFeedId];
(dataFeedValue, dataFeedTimestamp) = IApi3ServerV1(api3ServerV1)
.dataFeeds(dataFeedId);
}
if (dataFeedDetails.length != 0) {
if (
dataFeedDetails.length ==
DATA_FEED_DETAILS_LENGTH_FOR_SINGLE_BEACON
) {
beaconValues = new int224[](1);
beaconTimestamps = new uint32[](1);
signedApiUrls = new string[](1);
(address airnode, bytes32 templateId) = abi.decode(
dataFeedDetails,
(address, bytes32)
);
(beaconValues[0], beaconTimestamps[0]) = IApi3ServerV1(
api3ServerV1
).dataFeeds(deriveBeaconId(airnode, templateId));
signedApiUrls[0] = airnodeToSignedApiUrl[airnode];
} else {
(address[] memory airnodes, bytes32[] memory templateIds) = abi
.decode(dataFeedDetails, (address[], bytes32[]));
uint256 beaconCount = airnodes.length;
beaconValues = new int224[](beaconCount);
beaconTimestamps = new uint32[](beaconCount);
signedApiUrls = new string[](beaconCount);
for (uint256 ind = 0; ind < beaconCount; ind++) {
(beaconValues[ind], beaconTimestamps[ind]) = IApi3ServerV1(
api3ServerV1
).dataFeeds(
deriveBeaconId(airnodes[ind], templateIds[ind])
);
signedApiUrls[ind] = airnodeToSignedApiUrl[airnodes[ind]];
}
}
}
}
/// @notice Returns the number of active data feeds identified by a data
/// feed ID or dAPI name
/// @return Active data feed count
function activeDataFeedCount() external view override returns (uint256) {
return activeDataFeedIdCount() + activeDapiNameCount();
}
/// @notice Returns the number of active data feeds identified by a data
/// feed ID
/// @return Active data feed ID count
function activeDataFeedIdCount() public view override returns (uint256) {
return activeDataFeedIds.length();
}
/// @notice Returns the number of active data feeds identified by a dAPI
/// name
/// @return Active dAPI name count
function activeDapiNameCount() public view override returns (uint256) {
return activeDapiNames.length();
}
/// @notice Data feed ID to update parameters
/// @param dataFeedId Data feed ID
/// @return updateParameters Update parameters
function dataFeedIdToUpdateParameters(
bytes32 dataFeedId
) public view override returns (bytes memory updateParameters) {
updateParameters = updateParametersHashToValue[
dataFeedIdToUpdateParametersHash[dataFeedId]
];
}
/// @notice dAPI name to update parameters
/// @param dapiName dAPI name
/// @return updateParameters Update parameters
function dapiNameToUpdateParameters(
bytes32 dapiName
) public view override returns (bytes memory updateParameters) {
updateParameters = updateParametersHashToValue[
dapiNameToUpdateParametersHash[dapiName]
];
}
/// @notice Returns if the data feed with ID is registered
/// @param dataFeedId Data feed ID
/// @return If the data feed with ID is registered
function dataFeedIsRegistered(
bytes32 dataFeedId
) external view override returns (bool) {
return dataFeedIdToDetails[dataFeedId].length != 0;
}
/// @notice Derives the Beacon ID from the Airnode address and template ID
/// @param airnode Airnode address
/// @param templateId Template ID
/// @return beaconId Beacon ID
function deriveBeaconId(
address airnode,
bytes32 templateId
) private pure returns (bytes32 beaconId) {
beaconId = keccak256(abi.encodePacked(airnode, templateId));
}
/// @notice Derives the Beacon set ID from the Beacon IDs
/// @param beaconIds Beacon IDs
/// @return beaconSetId Beacon set ID
function deriveBeaconSetId(
bytes32[] memory beaconIds
) private pure returns (bytes32 beaconSetId) {
beaconSetId = keccak256(abi.encode(beaconIds));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../access/interfaces/IOwnable.sol";
import "../../utils/interfaces/IExtendedSelfMulticall.sol";
interface IAirseekerRegistry is IOwnable, IExtendedSelfMulticall {
event ActivatedDataFeedId(bytes32 indexed dataFeedId);
event ActivatedDapiName(bytes32 indexed dapiName);
event DeactivatedDataFeedId(bytes32 indexed dataFeedId);
event DeactivatedDapiName(bytes32 indexed dapiName);
event UpdatedDataFeedIdUpdateParameters(
bytes32 indexed dataFeedId,
bytes updateParameters
);
event UpdatedDapiNameUpdateParameters(
bytes32 indexed dapiName,
bytes updateParameters
);
event UpdatedSignedApiUrl(address indexed airnode, string signedApiUrl);
event RegisteredDataFeed(bytes32 indexed dataFeedId, bytes dataFeedDetails);
function setDataFeedIdToBeActivated(bytes32 dataFeedId) external;
function setDapiNameToBeActivated(bytes32 dapiName) external;
function setDataFeedIdToBeDeactivated(bytes32 dataFeedId) external;
function setDapiNameToBeDeactivated(bytes32 dapiName) external;
function setDataFeedIdUpdateParameters(
bytes32 dataFeedId,
bytes calldata updateParameters
) external;
function setDapiNameUpdateParameters(
bytes32 dapiName,
bytes calldata updateParameters
) external;
function setSignedApiUrl(
address airnode,
string calldata signedApiUrl
) external;
function registerDataFeed(
bytes calldata dataFeedDetails
) external returns (bytes32 dataFeedId);
function activeDataFeed(
uint256 index
)
external
view
returns (
bytes32 dataFeedId,
bytes32 dapiName,
bytes memory dataFeedDetails,
int224 dataFeedValue,
uint32 dataFeedTimestamp,
int224[] memory beaconValues,
uint32[] memory beaconTimestamps,
bytes memory updateParameters,
string[] memory signedApiUrls
);
function activeDataFeedCount() external view returns (uint256);
function activeDataFeedIdCount() external view returns (uint256);
function activeDapiNameCount() external view returns (uint256);
function dataFeedIdToUpdateParameters(
bytes32 dataFeedId
) external view returns (bytes memory updateParameters);
function dapiNameToUpdateParameters(
bytes32 dapiName
) external view returns (bytes memory updateParameters);
function dataFeedIsRegistered(
bytes32 dataFeedId
) external view returns (bool);
function MAXIMUM_BEACON_COUNT_IN_SET() external view returns (uint256);
function MAXIMUM_UPDATE_PARAMETERS_LENGTH() external view returns (uint256);
function MAXIMUM_SIGNED_API_URL_LENGTH() external view returns (uint256);
function api3ServerV1() external view returns (address);
function airnodeToSignedApiUrl(
address airnode
) external view returns (string memory signedApiUrl);
function dataFeedIdToDetails(
bytes32 dataFeedId
) external view returns (bytes memory dataFeedDetails);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IOevDapiServer.sol";
import "./IBeaconUpdatesWithSignedData.sol";
interface IApi3ServerV1 is IOevDapiServer, IBeaconUpdatesWithSignedData {
function readDataFeedWithId(
bytes32 dataFeedId
) external view returns (int224 value, uint32 timestamp);
function readDataFeedWithDapiNameHash(
bytes32 dapiNameHash
) external view returns (int224 value, uint32 timestamp);
function readDataFeedWithIdAsOevProxy(
bytes32 dataFeedId
) external view returns (int224 value, uint32 timestamp);
function readDataFeedWithDapiNameHashAsOevProxy(
bytes32 dapiNameHash
) external view returns (int224 value, uint32 timestamp);
function dataFeeds(
bytes32 dataFeedId
) external view returns (int224 value, uint32 timestamp);
function oevProxyToIdToDataFeed(
address proxy,
bytes32 dataFeedId
) external view returns (int224 value, uint32 timestamp);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IDataFeedServer.sol";
interface IBeaconUpdatesWithSignedData is IDataFeedServer {
function updateBeaconWithSignedData(
address airnode,
bytes32 templateId,
uint256 timestamp,
bytes calldata data,
bytes calldata signature
) external returns (bytes32 beaconId);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../access/interfaces/IAccessControlRegistryAdminnedWithManager.sol";
import "./IDataFeedServer.sol";
interface IDapiServer is
IAccessControlRegistryAdminnedWithManager,
IDataFeedServer
{
event SetDapiName(
bytes32 indexed dataFeedId,
bytes32 indexed dapiName,
address sender
);
function setDapiName(bytes32 dapiName, bytes32 dataFeedId) external;
function dapiNameToDataFeedId(
bytes32 dapiName
) external view returns (bytes32);
// solhint-disable-next-line func-name-mixedcase
function DAPI_NAME_SETTER_ROLE_DESCRIPTION()
external
view
returns (string memory);
function dapiNameSetterRole() external view returns (bytes32);
function dapiNameHashToDataFeedId(
bytes32 dapiNameHash
) external view returns (bytes32 dataFeedId);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../utils/interfaces/IExtendedSelfMulticall.sol";
interface IDataFeedServer is IExtendedSelfMulticall {
event UpdatedBeaconWithSignedData(
bytes32 indexed beaconId,
int224 value,
uint32 timestamp
);
event UpdatedBeaconSetWithBeacons(
bytes32 indexed beaconSetId,
int224 value,
uint32 timestamp
);
function updateBeaconSetWithBeacons(
bytes32[] memory beaconIds
) external returns (bytes32 beaconSetId);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IOevDataFeedServer.sol";
import "./IDapiServer.sol";
interface IOevDapiServer is IOevDataFeedServer, IDapiServer {}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IDataFeedServer.sol";
interface IOevDataFeedServer is IDataFeedServer {
event UpdatedOevProxyBeaconWithSignedData(
bytes32 indexed beaconId,
address indexed proxy,
bytes32 indexed updateId,
int224 value,
uint32 timestamp
);
event UpdatedOevProxyBeaconSetWithSignedData(
bytes32 indexed beaconSetId,
address indexed proxy,
bytes32 indexed updateId,
int224 value,
uint32 timestamp
);
event Withdrew(
address indexed oevProxy,
address oevBeneficiary,
uint256 amount
);
function updateOevProxyDataFeedWithSignedData(
address oevProxy,
bytes32 dataFeedId,
bytes32 updateId,
uint256 timestamp,
bytes calldata data,
bytes[] calldata packedOevUpdateSignatures
) external payable;
function withdraw(address oevProxy) external;
function oevProxyToBalance(
address oevProxy
) external view returns (uint256 balance);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "./SelfMulticall.sol";
import "./interfaces/IExtendedSelfMulticall.sol";
/// @title Contract that extends SelfMulticall to fetch some of the global
/// variables
/// @notice Available global variables are limited to the ones that Airnode
/// tends to need
contract ExtendedSelfMulticall is SelfMulticall, IExtendedSelfMulticall {
/// @notice Returns the chain ID
/// @return Chain ID
function getChainId() external view override returns (uint256) {
return block.chainid;
}
/// @notice Returns the account balance
/// @param account Account address
/// @return Account balance
function getBalance(
address account
) external view override returns (uint256) {
return account.balance;
}
/// @notice Returns if the account contains bytecode
/// @dev An account not containing any bytecode does not indicate that it
/// is an EOA or it will not contain any bytecode in the future.
/// Contract construction and `SELFDESTRUCT` updates the bytecode at the
/// end of the transaction.
/// @return If the account contains bytecode
function containsBytecode(
address account
) external view override returns (bool) {
return account.code.length > 0;
}
/// @notice Returns the current block number
/// @return Current block number
function getBlockNumber() external view override returns (uint256) {
return block.number;
}
/// @notice Returns the current block timestamp
/// @return Current block timestamp
function getBlockTimestamp() external view override returns (uint256) {
return block.timestamp;
}
/// @notice Returns the current block basefee
/// @return Current block basefee
function getBlockBasefee() external view override returns (uint256) {
return block.basefee;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./ISelfMulticall.sol";
interface IExtendedSelfMulticall is ISelfMulticall {
function getChainId() external view returns (uint256);
function getBalance(address account) external view returns (uint256);
function containsBytecode(address account) external view returns (bool);
function getBlockNumber() external view returns (uint256);
function getBlockTimestamp() external view returns (uint256);
function getBlockBasefee() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ISelfMulticall {
function multicall(
bytes[] calldata data
) external returns (bytes[] memory returndata);
function tryMulticall(
bytes[] calldata data
) external returns (bool[] memory successes, bytes[] memory returndata);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./interfaces/ISelfMulticall.sol";
/// @title Contract that enables calls to the inheriting contract to be batched
/// @notice Implements two ways of batching, one requires none of the calls to
/// revert and the other tolerates individual calls reverting
/// @dev This implementation uses delegatecall for individual function calls.
/// Since delegatecall is a message call, it can only be made to functions that
/// are externally visible. This means that a contract cannot multicall its own
/// functions that use internal/private visibility modifiers.
/// Refer to OpenZeppelin's Multicall.sol for a similar implementation.
contract SelfMulticall is ISelfMulticall {
/// @notice Batches calls to the inheriting contract and reverts as soon as
/// one of the batched calls reverts
/// @param data Array of calldata of batched calls
/// @return returndata Array of returndata of batched calls
function multicall(
bytes[] calldata data
) external override returns (bytes[] memory returndata) {
uint256 callCount = data.length;
returndata = new bytes[](callCount);
for (uint256 ind = 0; ind < callCount; ) {
bool success;
// solhint-disable-next-line avoid-low-level-calls
(success, returndata[ind]) = address(this).delegatecall(data[ind]);
if (!success) {
bytes memory returndataWithRevertData = returndata[ind];
if (returndataWithRevertData.length > 0) {
// Adapted from OpenZeppelin's Address.sol
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndataWithRevertData)
revert(
add(32, returndataWithRevertData),
returndata_size
)
}
} else {
revert("Multicall: No revert string");
}
}
unchecked {
ind++;
}
}
}
/// @notice Batches calls to the inheriting contract but does not revert if
/// any of the batched calls reverts
/// @param data Array of calldata of batched calls
/// @return successes Array of success conditions of batched calls
/// @return returndata Array of returndata of batched calls
function tryMulticall(
bytes[] calldata data
)
external
override
returns (bool[] memory successes, bytes[] memory returndata)
{
uint256 callCount = data.length;
successes = new bool[](callCount);
returndata = new bytes[](callCount);
for (uint256 ind = 0; ind < callCount; ) {
// solhint-disable-next-line avoid-low-level-calls
(successes[ind], returndata[ind]) = address(this).delegatecall(
data[ind]
);
unchecked {
ind++;
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}