Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {CBORChainlink} from "./vendor/CBORChainlink.sol";
import {BufferChainlink} from "./vendor/BufferChainlink.sol";
/**
* @title Library for common Chainlink functions
* @dev Uses imported CBOR library for encoding to buffer
*/
library Chainlink {
uint256 internal constant defaultBufferSize = 256; // solhint-disable-line const-name-snakecase
using CBORChainlink for BufferChainlink.buffer;
struct Request {
bytes32 id;
address callbackAddress;
bytes4 callbackFunctionId;
uint256 nonce;
BufferChainlink.buffer buf;
}
/**
* @notice Initializes a Chainlink request
* @dev Sets the ID, callback address, and callback function signature on the request
* @param self The uninitialized request
* @param jobId The Job Specification ID
* @param callbackAddr The callback address
* @param callbackFunc The callback function signature
* @return The initialized request
*/
function initialize(
Request memory self,
bytes32 jobId,
address callbackAddr,
bytes4 callbackFunc
) internal pure returns (Chainlink.Request memory) {
BufferChainlink.init(self.buf, defaultBufferSize);
self.id = jobId;
self.callbackAddress = callbackAddr;
self.callbackFunctionId = callbackFunc;
return self;
}
/**
* @notice Sets the data for the buffer without encoding CBOR on-chain
* @dev CBOR can be closed with curly-brackets {} or they can be left off
* @param self The initialized request
* @param data The CBOR data
*/
function setBuffer(Request memory self, bytes memory data) internal pure {
BufferChainlink.init(self.buf, data.length);
BufferChainlink.append(self.buf, data);
}
/**
* @notice Adds a string value to the request with a given key name
* @param self The initialized request
* @param key The name of the key
* @param value The string value to add
*/
function add(
Request memory self,
string memory key,
string memory value
) internal pure {
self.buf.encodeString(key);
self.buf.encodeString(value);
}
/**
* @notice Adds a bytes value to the request with a given key name
* @param self The initialized request
* @param key The name of the key
* @param value The bytes value to add
*/
function addBytes(
Request memory self,
string memory key,
bytes memory value
) internal pure {
self.buf.encodeString(key);
self.buf.encodeBytes(value);
}
/**
* @notice Adds a int256 value to the request with a given key name
* @param self The initialized request
* @param key The name of the key
* @param value The int256 value to add
*/
function addInt(
Request memory self,
string memory key,
int256 value
) internal pure {
self.buf.encodeString(key);
self.buf.encodeInt(value);
}
/**
* @notice Adds a uint256 value to the request with a given key name
* @param self The initialized request
* @param key The name of the key
* @param value The uint256 value to add
*/
function addUint(
Request memory self,
string memory key,
uint256 value
) internal pure {
self.buf.encodeString(key);
self.buf.encodeUInt(value);
}
/**
* @notice Adds an array of strings to the request with a given key name
* @param self The initialized request
* @param key The name of the key
* @param values The array of string values to add
*/
function addStringArray(
Request memory self,
string memory key,
string[] memory values
) internal pure {
self.buf.encodeString(key);
self.buf.startArray();
for (uint256 i = 0; i < values.length; i++) {
self.buf.encodeString(values[i]);
}
self.buf.endSequence();
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.19;
import {BufferChainlink} from "./BufferChainlink.sol";
library CBORChainlink {
using BufferChainlink for BufferChainlink.buffer;
uint8 private constant MAJOR_TYPE_INT = 0;
uint8 private constant MAJOR_TYPE_NEGATIVE_INT = 1;
uint8 private constant MAJOR_TYPE_BYTES = 2;
uint8 private constant MAJOR_TYPE_STRING = 3;
uint8 private constant MAJOR_TYPE_ARRAY = 4;
uint8 private constant MAJOR_TYPE_MAP = 5;
uint8 private constant MAJOR_TYPE_TAG = 6;
uint8 private constant MAJOR_TYPE_CONTENT_FREE = 7;
uint8 private constant TAG_TYPE_BIGNUM = 2;
uint8 private constant TAG_TYPE_NEGATIVE_BIGNUM = 3;
function encodeFixedNumeric(BufferChainlink.buffer memory buf, uint8 major, uint64 value) private pure {
if(value <= 23) {
buf.appendUint8(uint8((major << 5) | value));
} else if (value <= 0xFF) {
buf.appendUint8(uint8((major << 5) | 24));
buf.appendInt(value, 1);
} else if (value <= 0xFFFF) {
buf.appendUint8(uint8((major << 5) | 25));
buf.appendInt(value, 2);
} else if (value <= 0xFFFFFFFF) {
buf.appendUint8(uint8((major << 5) | 26));
buf.appendInt(value, 4);
} else {
buf.appendUint8(uint8((major << 5) | 27));
buf.appendInt(value, 8);
}
}
function encodeIndefiniteLengthType(BufferChainlink.buffer memory buf, uint8 major) private pure {
buf.appendUint8(uint8((major << 5) | 31));
}
function encodeUInt(BufferChainlink.buffer memory buf, uint value) internal pure {
if(value > 0xFFFFFFFFFFFFFFFF) {
encodeBigNum(buf, value);
} else {
encodeFixedNumeric(buf, MAJOR_TYPE_INT, uint64(value));
}
}
function encodeInt(BufferChainlink.buffer memory buf, int value) internal pure {
if(value < -0x10000000000000000) {
encodeSignedBigNum(buf, value);
} else if(value > 0xFFFFFFFFFFFFFFFF) {
encodeBigNum(buf, uint(value));
} else if(value >= 0) {
encodeFixedNumeric(buf, MAJOR_TYPE_INT, uint64(uint256(value)));
} else {
encodeFixedNumeric(buf, MAJOR_TYPE_NEGATIVE_INT, uint64(uint256(-1 - value)));
}
}
function encodeBytes(BufferChainlink.buffer memory buf, bytes memory value) internal pure {
encodeFixedNumeric(buf, MAJOR_TYPE_BYTES, uint64(value.length));
buf.append(value);
}
function encodeBigNum(BufferChainlink.buffer memory buf, uint value) internal pure {
buf.appendUint8(uint8((MAJOR_TYPE_TAG << 5) | TAG_TYPE_BIGNUM));
encodeBytes(buf, abi.encode(value));
}
function encodeSignedBigNum(BufferChainlink.buffer memory buf, int input) internal pure {
buf.appendUint8(uint8((MAJOR_TYPE_TAG << 5) | TAG_TYPE_NEGATIVE_BIGNUM));
encodeBytes(buf, abi.encode(uint256(-1 - input)));
}
function encodeString(BufferChainlink.buffer memory buf, string memory value) internal pure {
encodeFixedNumeric(buf, MAJOR_TYPE_STRING, uint64(bytes(value).length));
buf.append(bytes(value));
}
function startArray(BufferChainlink.buffer memory buf) internal pure {
encodeIndefiniteLengthType(buf, MAJOR_TYPE_ARRAY);
}
function startMap(BufferChainlink.buffer memory buf) internal pure {
encodeIndefiniteLengthType(buf, MAJOR_TYPE_MAP);
}
function endSequence(BufferChainlink.buffer memory buf) internal pure {
encodeIndefiniteLengthType(buf, MAJOR_TYPE_CONTENT_FREE);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev A library for working with mutable byte buffers in Solidity.
*
* Byte buffers are mutable and expandable, and provide a variety of primitives
* for writing to them. At any time you can fetch a bytes object containing the
* current contents of the buffer. The bytes object should not be stored between
* operations, as it may change due to resizing of the buffer.
*/
library BufferChainlink {
/**
* @dev Represents a mutable buffer. Buffers have a current value (buf) and
* a capacity. The capacity may be longer than the current value, in
* which case it can be extended without the need to allocate more memory.
*/
struct buffer {
bytes buf;
uint256 capacity;
}
/**
* @dev Initializes a buffer with an initial capacity.
* @param buf The buffer to initialize.
* @param capacity The number of bytes of space to allocate the buffer.
* @return The buffer, for chaining.
*/
function init(buffer memory buf, uint256 capacity) internal pure returns (buffer memory) {
if (capacity % 32 != 0) {
capacity += 32 - (capacity % 32);
}
// Allocate space for the buffer data
buf.capacity = capacity;
assembly {
let ptr := mload(0x40)
mstore(buf, ptr)
mstore(ptr, 0)
mstore(0x40, add(32, add(ptr, capacity)))
}
return buf;
}
/**
* @dev Initializes a new buffer from an existing bytes object.
* Changes to the buffer may mutate the original value.
* @param b The bytes object to initialize the buffer with.
* @return A new buffer.
*/
function fromBytes(bytes memory b) internal pure returns (buffer memory) {
buffer memory buf;
buf.buf = b;
buf.capacity = b.length;
return buf;
}
function resize(buffer memory buf, uint256 capacity) private pure {
bytes memory oldbuf = buf.buf;
init(buf, capacity);
append(buf, oldbuf);
}
function max(uint256 a, uint256 b) private pure returns (uint256) {
if (a > b) {
return a;
}
return b;
}
/**
* @dev Sets buffer length to 0.
* @param buf The buffer to truncate.
* @return The original buffer, for chaining..
*/
function truncate(buffer memory buf) internal pure returns (buffer memory) {
assembly {
let bufptr := mload(buf)
mstore(bufptr, 0)
}
return buf;
}
/**
* @dev Writes a byte string to a buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param off The start offset to write to.
* @param data The data to append.
* @param len The number of bytes to copy.
* @return The original buffer, for chaining.
*/
function write(
buffer memory buf,
uint256 off,
bytes memory data,
uint256 len
) internal pure returns (buffer memory) {
require(len <= data.length);
if (off + len > buf.capacity) {
resize(buf, max(buf.capacity, len + off) * 2);
}
uint256 dest;
uint256 src;
assembly {
// Memory address of the buffer data
let bufptr := mload(buf)
// Length of existing buffer data
let buflen := mload(bufptr)
// Start address = buffer address + offset + sizeof(buffer length)
dest := add(add(bufptr, 32), off)
// Update buffer length if we're extending it
if gt(add(len, off), buflen) {
mstore(bufptr, add(len, off))
}
src := add(data, 32)
}
// Copy word-length chunks while possible
for (; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}
// Copy remaining bytes
unchecked {
uint256 mask = (256**(32 - len)) - 1;
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
return buf;
}
/**
* @dev Appends a byte string to a buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @param len The number of bytes to copy.
* @return The original buffer, for chaining.
*/
function append(
buffer memory buf,
bytes memory data,
uint256 len
) internal pure returns (buffer memory) {
return write(buf, buf.buf.length, data, len);
}
/**
* @dev Appends a byte string to a buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer, for chaining.
*/
function append(buffer memory buf, bytes memory data) internal pure returns (buffer memory) {
return write(buf, buf.buf.length, data, data.length);
}
/**
* @dev Writes a byte to the buffer. Resizes if doing so would exceed the
* capacity of the buffer.
* @param buf The buffer to append to.
* @param off The offset to write the byte at.
* @param data The data to append.
* @return The original buffer, for chaining.
*/
function writeUint8(
buffer memory buf,
uint256 off,
uint8 data
) internal pure returns (buffer memory) {
if (off >= buf.capacity) {
resize(buf, buf.capacity * 2);
}
assembly {
// Memory address of the buffer data
let bufptr := mload(buf)
// Length of existing buffer data
let buflen := mload(bufptr)
// Address = buffer address + sizeof(buffer length) + off
let dest := add(add(bufptr, off), 32)
mstore8(dest, data)
// Update buffer length if we extended it
if eq(off, buflen) {
mstore(bufptr, add(buflen, 1))
}
}
return buf;
}
/**
* @dev Appends a byte to the buffer. Resizes if doing so would exceed the
* capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer, for chaining.
*/
function appendUint8(buffer memory buf, uint8 data) internal pure returns (buffer memory) {
return writeUint8(buf, buf.buf.length, data);
}
/**
* @dev Writes up to 32 bytes to the buffer. Resizes if doing so would
* exceed the capacity of the buffer.
* @param buf The buffer to append to.
* @param off The offset to write at.
* @param data The data to append.
* @param len The number of bytes to write (left-aligned).
* @return The original buffer, for chaining.
*/
function write(
buffer memory buf,
uint256 off,
bytes32 data,
uint256 len
) private pure returns (buffer memory) {
if (len + off > buf.capacity) {
resize(buf, (len + off) * 2);
}
unchecked {
uint256 mask = (256**len) - 1;
// Right-align data
data = data >> (8 * (32 - len));
assembly {
// Memory address of the buffer data
let bufptr := mload(buf)
// Address = buffer address + sizeof(buffer length) + off + len
let dest := add(add(bufptr, off), len)
mstore(dest, or(and(mload(dest), not(mask)), data))
// Update buffer length if we extended it
if gt(add(off, len), mload(bufptr)) {
mstore(bufptr, add(off, len))
}
}
}
return buf;
}
/**
* @dev Writes a bytes20 to the buffer. Resizes if doing so would exceed the
* capacity of the buffer.
* @param buf The buffer to append to.
* @param off The offset to write at.
* @param data The data to append.
* @return The original buffer, for chaining.
*/
function writeBytes20(
buffer memory buf,
uint256 off,
bytes20 data
) internal pure returns (buffer memory) {
return write(buf, off, bytes32(data), 20);
}
/**
* @dev Appends a bytes20 to the buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer, for chhaining.
*/
function appendBytes20(buffer memory buf, bytes20 data) internal pure returns (buffer memory) {
return write(buf, buf.buf.length, bytes32(data), 20);
}
/**
* @dev Appends a bytes32 to the buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer, for chaining.
*/
function appendBytes32(buffer memory buf, bytes32 data) internal pure returns (buffer memory) {
return write(buf, buf.buf.length, data, 32);
}
/**
* @dev Writes an integer to the buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param off The offset to write at.
* @param data The data to append.
* @param len The number of bytes to write (right-aligned).
* @return The original buffer, for chaining.
*/
function writeInt(
buffer memory buf,
uint256 off,
uint256 data,
uint256 len
) private pure returns (buffer memory) {
if (len + off > buf.capacity) {
resize(buf, (len + off) * 2);
}
uint256 mask = (256**len) - 1;
assembly {
// Memory address of the buffer data
let bufptr := mload(buf)
// Address = buffer address + off + sizeof(buffer length) + len
let dest := add(add(bufptr, off), len)
mstore(dest, or(and(mload(dest), not(mask)), data))
// Update buffer length if we extended it
if gt(add(off, len), mload(bufptr)) {
mstore(bufptr, add(off, len))
}
}
return buf;
}
/**
* @dev Appends a byte to the end of the buffer. Resizes if doing so would
* exceed the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer.
*/
function appendInt(
buffer memory buf,
uint256 data,
uint256 len
) internal pure returns (buffer memory) {
return writeInt(buf, buf.buf.length, data, len);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../Chainlink.sol";
import "../vendor/CBORChainlink.sol";
import "../vendor/BufferChainlink.sol";
contract ChainlinkTestHelper {
using Chainlink for Chainlink.Request;
using CBORChainlink for BufferChainlink.buffer;
Chainlink.Request private req;
event RequestData(bytes payload);
function closeEvent() public {
emit RequestData(req.buf.buf);
}
function setBuffer(bytes memory data) public {
Chainlink.Request memory r2 = req;
r2.setBuffer(data);
req = r2;
}
function add(string memory _key, string memory _value) public {
Chainlink.Request memory r2 = req;
r2.add(_key, _value);
req = r2;
}
function addBytes(string memory _key, bytes memory _value) public {
Chainlink.Request memory r2 = req;
r2.addBytes(_key, _value);
req = r2;
}
function addInt(string memory _key, int256 _value) public {
Chainlink.Request memory r2 = req;
r2.addInt(_key, _value);
req = r2;
}
function addUint(string memory _key, uint256 _value) public {
Chainlink.Request memory r2 = req;
r2.addUint(_key, _value);
req = r2;
}
// Temporarily have method receive bytes32[] memory until experimental
// string[] memory can be invoked from truffle tests.
function addStringArray(string memory _key, string[] memory _values) public {
Chainlink.Request memory r2 = req;
r2.addStringArray(_key, _values);
req = r2;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Chainlink.sol";
import "./interfaces/ENSInterface.sol";
import "./interfaces/LinkTokenInterface.sol";
import "./interfaces/ChainlinkRequestInterface.sol";
import "./interfaces/OperatorInterface.sol";
import "./interfaces/PointerInterface.sol";
import {ENSResolver as ENSResolver_Chainlink} from "./vendor/ENSResolver.sol";
/**
* @title The ChainlinkClient contract
* @notice Contract writers can inherit this contract in order to create requests for the
* Chainlink network
*/
abstract contract ChainlinkClient {
using Chainlink for Chainlink.Request;
uint256 internal constant LINK_DIVISIBILITY = 10**18;
uint256 private constant AMOUNT_OVERRIDE = 0;
address private constant SENDER_OVERRIDE = address(0);
uint256 private constant ORACLE_ARGS_VERSION = 1;
uint256 private constant OPERATOR_ARGS_VERSION = 2;
bytes32 private constant ENS_TOKEN_SUBNAME = keccak256("link");
bytes32 private constant ENS_ORACLE_SUBNAME = keccak256("oracle");
address private constant LINK_TOKEN_POINTER = 0xC89bD4E1632D3A43CB03AAAd5262cbe4038Bc571;
ENSInterface private s_ens;
bytes32 private s_ensNode;
LinkTokenInterface private s_link;
OperatorInterface private s_oracle;
uint256 private s_requestCount = 1;
mapping(bytes32 => address) private s_pendingRequests;
event ChainlinkRequested(bytes32 indexed id);
event ChainlinkFulfilled(bytes32 indexed id);
event ChainlinkCancelled(bytes32 indexed id);
/**
* @notice Creates a request that can hold additional parameters
* @param specId The Job Specification ID that the request will be created for
* @param callbackAddr address to operate the callback on
* @param callbackFunctionSignature function signature to use for the callback
* @return A Chainlink Request struct in memory
*/
function buildChainlinkRequest(
bytes32 specId,
address callbackAddr,
bytes4 callbackFunctionSignature
) internal pure returns (Chainlink.Request memory) {
Chainlink.Request memory req;
return req.initialize(specId, callbackAddr, callbackFunctionSignature);
}
/**
* @notice Creates a request that can hold additional parameters
* @param specId The Job Specification ID that the request will be created for
* @param callbackFunctionSignature function signature to use for the callback
* @return A Chainlink Request struct in memory
*/
function buildOperatorRequest(bytes32 specId, bytes4 callbackFunctionSignature)
internal
view
returns (Chainlink.Request memory)
{
Chainlink.Request memory req;
return req.initialize(specId, address(this), callbackFunctionSignature);
}
/**
* @notice Creates a Chainlink request to the stored oracle address
* @dev Calls `chainlinkRequestTo` with the stored oracle address
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @return requestId The request ID
*/
function sendChainlinkRequest(Chainlink.Request memory req, uint256 payment) internal returns (bytes32) {
return sendChainlinkRequestTo(address(s_oracle), req, payment);
}
/**
* @notice Creates a Chainlink request to the specified oracle address
* @dev Generates and stores a request ID, increments the local nonce, and uses `transferAndCall` to
* send LINK which creates a request on the target oracle contract.
* Emits ChainlinkRequested event.
* @param oracleAddress The address of the oracle for the request
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @return requestId The request ID
*/
function sendChainlinkRequestTo(
address oracleAddress,
Chainlink.Request memory req,
uint256 payment
) internal returns (bytes32 requestId) {
uint256 nonce = s_requestCount;
s_requestCount = nonce + 1;
bytes memory encodedRequest = abi.encodeWithSelector(
ChainlinkRequestInterface.oracleRequest.selector,
SENDER_OVERRIDE, // Sender value - overridden by onTokenTransfer by the requesting contract's address
AMOUNT_OVERRIDE, // Amount value - overridden by onTokenTransfer by the actual amount of LINK sent
req.id,
address(this),
req.callbackFunctionId,
nonce,
ORACLE_ARGS_VERSION,
req.buf.buf
);
return _rawRequest(oracleAddress, nonce, payment, encodedRequest);
}
/**
* @notice Creates a Chainlink request to the stored oracle address
* @dev This function supports multi-word response
* @dev Calls `sendOperatorRequestTo` with the stored oracle address
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @return requestId The request ID
*/
function sendOperatorRequest(Chainlink.Request memory req, uint256 payment) internal returns (bytes32) {
return sendOperatorRequestTo(address(s_oracle), req, payment);
}
/**
* @notice Creates a Chainlink request to the specified oracle address
* @dev This function supports multi-word response
* @dev Generates and stores a request ID, increments the local nonce, and uses `transferAndCall` to
* send LINK which creates a request on the target oracle contract.
* Emits ChainlinkRequested event.
* @param oracleAddress The address of the oracle for the request
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @return requestId The request ID
*/
function sendOperatorRequestTo(
address oracleAddress,
Chainlink.Request memory req,
uint256 payment
) internal returns (bytes32 requestId) {
uint256 nonce = s_requestCount;
s_requestCount = nonce + 1;
bytes memory encodedRequest = abi.encodeWithSelector(
OperatorInterface.operatorRequest.selector,
SENDER_OVERRIDE, // Sender value - overridden by onTokenTransfer by the requesting contract's address
AMOUNT_OVERRIDE, // Amount value - overridden by onTokenTransfer by the actual amount of LINK sent
req.id,
req.callbackFunctionId,
nonce,
OPERATOR_ARGS_VERSION,
req.buf.buf
);
return _rawRequest(oracleAddress, nonce, payment, encodedRequest);
}
/**
* @notice Make a request to an oracle
* @param oracleAddress The address of the oracle for the request
* @param nonce used to generate the request ID
* @param payment The amount of LINK to send for the request
* @param encodedRequest data encoded for request type specific format
* @return requestId The request ID
*/
function _rawRequest(
address oracleAddress,
uint256 nonce,
uint256 payment,
bytes memory encodedRequest
) private returns (bytes32 requestId) {
requestId = keccak256(abi.encodePacked(this, nonce));
s_pendingRequests[requestId] = oracleAddress;
emit ChainlinkRequested(requestId);
require(s_link.transferAndCall(oracleAddress, payment, encodedRequest), "unable to transferAndCall to oracle");
}
/**
* @notice Allows a request to be cancelled if it has not been fulfilled
* @dev Requires keeping track of the expiration value emitted from the oracle contract.
* Deletes the request from the `pendingRequests` mapping.
* Emits ChainlinkCancelled event.
* @param requestId The request ID
* @param payment The amount of LINK sent for the request
* @param callbackFunc The callback function specified for the request
* @param expiration The time of the expiration for the request
*/
function cancelChainlinkRequest(
bytes32 requestId,
uint256 payment,
bytes4 callbackFunc,
uint256 expiration
) internal {
OperatorInterface requested = OperatorInterface(s_pendingRequests[requestId]);
delete s_pendingRequests[requestId];
emit ChainlinkCancelled(requestId);
requested.cancelOracleRequest(requestId, payment, callbackFunc, expiration);
}
/**
* @notice the next request count to be used in generating a nonce
* @dev starts at 1 in order to ensure consistent gas cost
* @return returns the next request count to be used in a nonce
*/
function getNextRequestCount() internal view returns (uint256) {
return s_requestCount;
}
/**
* @notice Sets the stored oracle address
* @param oracleAddress The address of the oracle contract
*/
function setChainlinkOracle(address oracleAddress) internal {
s_oracle = OperatorInterface(oracleAddress);
}
/**
* @notice Sets the LINK token address
* @param linkAddress The address of the LINK token contract
*/
function setChainlinkToken(address linkAddress) internal {
s_link = LinkTokenInterface(linkAddress);
}
/**
* @notice Sets the Chainlink token address for the public
* network as given by the Pointer contract
*/
function setPublicChainlinkToken() internal {
setChainlinkToken(PointerInterface(LINK_TOKEN_POINTER).getAddress());
}
/**
* @notice Retrieves the stored address of the LINK token
* @return The address of the LINK token
*/
function chainlinkTokenAddress() internal view returns (address) {
return address(s_link);
}
/**
* @notice Retrieves the stored address of the oracle contract
* @return The address of the oracle contract
*/
function chainlinkOracleAddress() internal view returns (address) {
return address(s_oracle);
}
/**
* @notice Allows for a request which was created on another contract to be fulfilled
* on this contract
* @param oracleAddress The address of the oracle contract that will fulfill the request
* @param requestId The request ID used for the response
*/
function addChainlinkExternalRequest(address oracleAddress, bytes32 requestId) internal notPendingRequest(requestId) {
s_pendingRequests[requestId] = oracleAddress;
}
/**
* @notice Sets the stored oracle and LINK token contracts with the addresses resolved by ENS
* @dev Accounts for subnodes having different resolvers
* @param ensAddress The address of the ENS contract
* @param node The ENS node hash
*/
function useChainlinkWithENS(address ensAddress, bytes32 node) internal {
s_ens = ENSInterface(ensAddress);
s_ensNode = node;
bytes32 linkSubnode = keccak256(abi.encodePacked(s_ensNode, ENS_TOKEN_SUBNAME));
ENSResolver_Chainlink resolver = ENSResolver_Chainlink(s_ens.resolver(linkSubnode));
setChainlinkToken(resolver.addr(linkSubnode));
updateChainlinkOracleWithENS();
}
/**
* @notice Sets the stored oracle contract with the address resolved by ENS
* @dev This may be called on its own as long as `useChainlinkWithENS` has been called previously
*/
function updateChainlinkOracleWithENS() internal {
bytes32 oracleSubnode = keccak256(abi.encodePacked(s_ensNode, ENS_ORACLE_SUBNAME));
ENSResolver_Chainlink resolver = ENSResolver_Chainlink(s_ens.resolver(oracleSubnode));
setChainlinkOracle(resolver.addr(oracleSubnode));
}
/**
* @notice Ensures that the fulfillment is valid for this contract
* @dev Use if the contract developer prefers methods instead of modifiers for validation
* @param requestId The request ID for fulfillment
*/
function validateChainlinkCallback(bytes32 requestId)
internal
recordChainlinkFulfillment(requestId)
// solhint-disable-next-line no-empty-blocks
{
}
/**
* @dev Reverts if the sender is not the oracle of the request.
* Emits ChainlinkFulfilled event.
* @param requestId The request ID for fulfillment
*/
modifier recordChainlinkFulfillment(bytes32 requestId) {
require(msg.sender == s_pendingRequests[requestId], "Source must be the oracle of the request");
delete s_pendingRequests[requestId];
emit ChainlinkFulfilled(requestId);
_;
}
/**
* @dev Reverts if the request is already pending
* @param requestId The request ID for fulfillment
*/
modifier notPendingRequest(bytes32 requestId) {
require(s_pendingRequests[requestId] == address(0), "Request is already pending");
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ENSInterface {
// Logged when the owner of a node assigns a new owner to a subnode.
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);
// Logged when the owner of a node transfers ownership to a new account.
event Transfer(bytes32 indexed node, address owner);
// Logged when the resolver for a node changes.
event NewResolver(bytes32 indexed node, address resolver);
// Logged when the TTL of a node changes
event NewTTL(bytes32 indexed node, uint64 ttl);
function setSubnodeOwner(
bytes32 node,
bytes32 label,
address owner
) external;
function setResolver(bytes32 node, address resolver) external;
function setOwner(bytes32 node, address owner) external;
function setTTL(bytes32 node, uint64 ttl) external;
function owner(bytes32 node) external view returns (address);
function resolver(bytes32 node) external view returns (address);
function ttl(bytes32 node) external view returns (uint64);
}
// 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 ChainlinkRequestInterface {
function oracleRequest(
address sender,
uint256 requestPrice,
bytes32 serviceAgreementID,
address callbackAddress,
bytes4 callbackFunctionId,
uint256 nonce,
uint256 dataVersion,
bytes calldata data
) external;
function cancelOracleRequest(
bytes32 requestId,
uint256 payment,
bytes4 callbackFunctionId,
uint256 expiration
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./OracleInterface.sol";
import "./ChainlinkRequestInterface.sol";
interface OperatorInterface is OracleInterface, ChainlinkRequestInterface {
function operatorRequest(
address sender,
uint256 payment,
bytes32 specId,
bytes4 callbackFunctionId,
uint256 nonce,
uint256 dataVersion,
bytes calldata data
) external;
function fulfillOracleRequest2(
bytes32 requestId,
uint256 payment,
address callbackAddress,
bytes4 callbackFunctionId,
uint256 expiration,
bytes calldata data
) external returns (bool);
function ownerTransferAndCall(
address to,
uint256 value,
bytes calldata data
) external returns (bool success);
function distributeFunds(address payable[] calldata receivers, uint256[] calldata amounts) external payable;
function getAuthorizedSenders() external returns (address[] memory);
function setAuthorizedSenders(address[] calldata senders) external;
function getForwarder() external returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface PointerInterface {
function getAddress() external view returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract ENSResolver {
function addr(bytes32 node) public view virtual returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface OracleInterface {
function fulfillOracleRequest(
bytes32 requestId,
uint256 payment,
address callbackAddress,
bytes4 callbackFunctionId,
uint256 expiration,
bytes32 data
) external returns (bool);
function isAuthorizedSender(address node) external view returns (bool);
function withdraw(address recipient, uint256 amount) external;
function withdrawable() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../ChainlinkClient.sol";
contract ChainlinkClientTestHelper is ChainlinkClient {
constructor(address _link, address _oracle) {
setChainlinkToken(_link);
setChainlinkOracle(_oracle);
}
event Request(bytes32 id, address callbackAddress, bytes4 callbackfunctionSelector, bytes data);
event LinkAmount(uint256 amount);
function publicNewRequest(
bytes32 _id,
address _address,
bytes memory _fulfillmentSignature
) public {
Chainlink.Request memory req = buildChainlinkRequest(_id, _address, bytes4(keccak256(_fulfillmentSignature)));
emit Request(req.id, req.callbackAddress, req.callbackFunctionId, req.buf.buf);
}
function publicRequest(
bytes32 _id,
address _address,
bytes memory _fulfillmentSignature,
uint256 _wei
) public {
Chainlink.Request memory req = buildChainlinkRequest(_id, _address, bytes4(keccak256(_fulfillmentSignature)));
sendChainlinkRequest(req, _wei);
}
function publicRequestRunTo(
address _oracle,
bytes32 _id,
address _address,
bytes memory _fulfillmentSignature,
uint256 _wei
) public {
Chainlink.Request memory run = buildChainlinkRequest(_id, _address, bytes4(keccak256(_fulfillmentSignature)));
sendChainlinkRequestTo(_oracle, run, _wei);
}
function publicRequestOracleData(
bytes32 _id,
bytes memory _fulfillmentSignature,
uint256 _wei
) public {
Chainlink.Request memory req = buildOperatorRequest(_id, bytes4(keccak256(_fulfillmentSignature)));
sendOperatorRequest(req, _wei);
}
function publicRequestOracleDataFrom(
address _oracle,
bytes32 _id,
address _address,
bytes memory _fulfillmentSignature,
uint256 _wei
) public {
Chainlink.Request memory run = buildOperatorRequest(_id, bytes4(keccak256(_fulfillmentSignature)));
sendOperatorRequestTo(_oracle, run, _wei);
}
function publicCancelRequest(
bytes32 _requestId,
uint256 _payment,
bytes4 _callbackFunctionId,
uint256 _expiration
) public {
cancelChainlinkRequest(_requestId, _payment, _callbackFunctionId, _expiration);
}
function publicChainlinkToken() public view returns (address) {
return chainlinkTokenAddress();
}
function publicFulfillChainlinkRequest(bytes32 _requestId, bytes32) public {
fulfillRequest(_requestId, bytes32(0));
}
function fulfillRequest(bytes32 _requestId, bytes32) public {
validateChainlinkCallback(_requestId);
}
function publicLINK(uint256 _amount) public {
emit LinkAmount(LINK_DIVISIBILITY * _amount);
}
function publicOracleAddress() public view returns (address) {
return chainlinkOracleAddress();
}
function publicAddExternalRequest(address _oracle, bytes32 _requestId) public {
addChainlinkExternalRequest(_oracle, _requestId);
}
}
// SPDX-License-Identifier: MIT
// Example of a single consumer contract which owns the subscription.
pragma solidity ^0.8.0;
import "../interfaces/LinkTokenInterface.sol";
import "../interfaces/VRFCoordinatorV2Interface.sol";
import "../dev/VRFConsumerBaseV2.sol";
contract VRFSingleConsumerExample is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
LinkTokenInterface LINKTOKEN;
struct RequestConfig {
uint64 subId;
uint32 callbackGasLimit;
uint16 requestConfirmations;
uint32 numWords;
bytes32 keyHash;
}
RequestConfig public s_requestConfig;
uint256[] public s_randomWords;
uint256 public s_requestId;
constructor(
address vrfCoordinator,
address link,
uint32 callbackGasLimit,
uint16 requestConfirmations,
uint32 numWords,
bytes32 keyHash
) VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
LINKTOKEN = LinkTokenInterface(link);
s_requestConfig = RequestConfig({
subId: 0, // Unset
callbackGasLimit: callbackGasLimit,
requestConfirmations: requestConfirmations,
numWords: numWords,
keyHash: keyHash
});
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
s_randomWords = randomWords;
}
function requestRandomWords() external {
RequestConfig memory rc = s_requestConfig;
// Will revert if subscription is not set and funded.
s_requestId = COORDINATOR.requestRandomWords(
rc.keyHash,
rc.subId,
rc.requestConfirmations,
rc.callbackGasLimit,
rc.numWords
);
}
// Assumes this contract owns link
function topUpSubscription(uint256 amount) external {
LINKTOKEN.transferAndCall(address(COORDINATOR), amount, abi.encode(s_requestConfig.subId));
}
function unsubscribe() external {
// Returns funds to this address
COORDINATOR.cancelSubscription(s_requestConfig.subId, address(this));
s_requestConfig.subId = 0;
}
function subscribe() external {
address[] memory consumers = new address[](1);
consumers[0] = address(this);
s_requestConfig.subId = COORDINATOR.createSubscription();
COORDINATOR.addConsumer(s_requestConfig.subId, consumers[0]);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface VRFCoordinatorV2Interface {
/**
* @notice Returns the global config that applies to all VRF requests.
* @return minimumRequestBlockConfirmations - A minimum number of confirmation
* blocks on VRF requests before oracles should respond.
* @return fulfillmentFlatFeeLinkPPM - The charge per request on top of the gas fees.
* Its flat fee specified in millionths of LINK.
* @return maxGasLimit - The maximum gas limit supported for a fulfillRandomWords callback.
* @return stalenessSeconds - How long we wait until we consider the ETH/LINK price
* (used for converting gas costs to LINK) is stale and use `fallbackWeiPerUnitLink`
* @return gasAfterPaymentCalculation - How much gas is used outside of the payment calculation,
* i.e. the gas overhead of actually making the payment to oracles.
* @return minimumSubscriptionBalance - The minimum subscription balance required to make a request. Its set to be about 300%
* of the cost of a single request to handle in ETH/LINK price between request and fulfillment time.
* @return fallbackWeiPerUnitLink - fallback ETH/LINK price in the case of a stale feed.
*/
function getConfig()
external
view
returns (
uint16 minimumRequestBlockConfirmations,
uint32 fulfillmentFlatFeeLinkPPM,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
uint96 minimumSubscriptionBalance,
int256 fallbackWeiPerUnitLink
);
/**
* @notice Request a set of random words.
* @param keyHash - Corresponds to a particular oracle job which uses
* that key for generating the VRF proof. Different keyHash's have different gas price
* ceilings, so you can select a specific one to bound your maximum per request cost.
* @param subId - The ID of the VRF subscription. Must be funded
* with at least minimumSubscriptionBalance (see getConfig) LINK
* before making a request.
* @param minimumRequestConfirmations - How many blocks you'd like the
* oracle to wait before responding to the request. See SECURITY CONSIDERATIONS
* for why you may want to request more. The acceptable range is
* [minimumRequestBlockConfirmations, 200].
* @param callbackGasLimit - How much gas you'd like to receive in your
* fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords
* may be slightly less than this amount because of gas used calling the function
* (argument decoding etc.), so you may need to request slightly more than you expect
* to have inside fulfillRandomWords. The acceptable range is
* [5000, maxGasLimit].
* @param numWords - The number of uint256 random values you'd like to receive
* in your fulfillRandomWords callback. Note these numbers are expanded in a
* secure way by the VRFCoordinator from a single random value supplied by the oracle.
* @return requestId - A unique identifier of the request. Can be used to match
* a request to a response in fulfillRandomWords.
*/
function requestRandomWords(
bytes32 keyHash,
uint64 subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external returns (uint256 requestId);
/**
* @notice Create a VRF subscription.
* @return subId - A unique subscription id.
* @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.
* @dev Note to fund the subscription, use transferAndCall. For example
* @dev LINKTOKEN.transferAndCall(
* @dev address(COORDINATOR),
* @dev amount,
* @dev abi.encode(subId));
*/
function createSubscription() external returns (uint64 subId);
/**
* @notice Get a VRF subscription.
* @param subId - ID of the subscription
* @return balance - LINK balance of the subscription in juels.
* @return owner - Owner of the subscription
* @return consumers - List of consumer address which are able to use this subscription.
*/
function getSubscription(uint64 subId)
external
view
returns (
uint96 balance,
address owner,
address[] memory consumers
);
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @param newOwner - proposed new owner of the subscription
*/
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external;
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @dev will revert if original owner of subId has
* not requested that msg.sender become the new owner.
*/
function acceptSubscriptionOwnerTransfer(uint64 subId) external;
/**
* @notice Add a consumer to a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - New consumer which can use the subscription
*/
function addConsumer(uint64 subId, address consumer) external;
/**
* @notice Remove a consumer from a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - Consumer to remove from the subscription
*/
function removeConsumer(uint64 subId, address consumer) external;
/**
* @notice Withdraw funds from a VRF subscription
* @param subId - ID of the subscription
* @param to - Where to send the withdrawn LINK to
* @param amount - How much to withdraw in juels
*/
function defundSubscription(
uint64 subId,
address to,
uint96 amount
) external;
/**
* @notice Cancel a subscription
* @param subId - ID of the subscription
* @param to - Where to send the remaining LINK to
*/
function cancelSubscription(uint64 subId, address to) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/** ****************************************************************************
* @notice Interface for contracts using VRF randomness
* *****************************************************************************
* @dev PURPOSE
*
* @dev Reggie the Random Oracle (not his real job) wants to provide randomness
* @dev to Vera the verifier in such a way that Vera can be sure he's not
* @dev making his output up to suit himself. Reggie provides Vera a public key
* @dev to which he knows the secret key. Each time Vera provides a seed to
* @dev Reggie, he gives back a value which is computed completely
* @dev deterministically from the seed and the secret key.
*
* @dev Reggie provides a proof by which Vera can verify that the output was
* @dev correctly computed once Reggie tells it to her, but without that proof,
* @dev the output is indistinguishable to her from a uniform random sample
* @dev from the output space.
*
* @dev The purpose of this contract is to make it easy for unrelated contracts
* @dev to talk to Vera the verifier about the work Reggie is doing, to provide
* @dev simple access to a verifiable source of randomness. It ensures 2 things:
* @dev 1. The fulfillment came from the VRFCoordinator
* @dev 2. The consumer contract implements fulfillRandomWords.
* *****************************************************************************
* @dev USAGE
*
* @dev Calling contracts must inherit from VRFConsumerBase, and can
* @dev initialize VRFConsumerBase's attributes in their constructor as
* @dev shown:
*
* @dev contract VRFConsumer {
* @dev constuctor(<other arguments>, address _vrfCoordinator, address _link)
* @dev VRFConsumerBase(_vrfCoordinator) public {
* @dev <initialization with other arguments goes here>
* @dev }
* @dev }
*
* @dev The oracle will have given you an ID for the VRF keypair they have
* @dev committed to (let's call it keyHash). Create subscription, fund it
* @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface
* @dev subscription management functions).
* @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,
* @dev callbackGasLimit, numWords),
* @dev see (VRFCoordinatorInterface for a description of the arguments).
*
* @dev Once the VRFCoordinator has received and validated the oracle's response
* @dev to your request, it will call your contract's fulfillRandomWords method.
*
* @dev The randomness argument to fulfillRandomWords is a set of random words
* @dev generated from your requestId and the blockHash of the request.
*
* @dev If your contract could have concurrent requests open, you can use the
* @dev requestId returned from requestRandomWords to track which response is associated
* @dev with which randomness request.
* @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind,
* @dev if your contract could have multiple requests in flight simultaneously.
*
* @dev Colliding `requestId`s are cryptographically impossible as long as seeds
* @dev differ.
*
* *****************************************************************************
* @dev SECURITY CONSIDERATIONS
*
* @dev A method with the ability to call your fulfillRandomness method directly
* @dev could spoof a VRF response with any random value, so it's critical that
* @dev it cannot be directly called by anything other than this base contract
* @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
*
* @dev For your users to trust that your contract's random behavior is free
* @dev from malicious interference, it's best if you can write it so that all
* @dev behaviors implied by a VRF response are executed *during* your
* @dev fulfillRandomness method. If your contract must store the response (or
* @dev anything derived from it) and use it later, you must ensure that any
* @dev user-significant behavior which depends on that stored value cannot be
* @dev manipulated by a subsequent VRF request.
*
* @dev Similarly, both miners and the VRF oracle itself have some influence
* @dev over the order in which VRF responses appear on the blockchain, so if
* @dev your contract could have multiple VRF requests in flight simultaneously,
* @dev you must ensure that the order in which the VRF responses arrive cannot
* @dev be used to manipulate your contract's user-significant behavior.
*
* @dev Since the block hash of the block which contains the requestRandomness
* @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
* @dev miner could, in principle, fork the blockchain to evict the block
* @dev containing the request, forcing the request to be included in a
* @dev different block with a different hash, and therefore a different input
* @dev to the VRF. However, such an attack would incur a substantial economic
* @dev cost. This cost scales with the number of blocks the VRF oracle waits
* @dev until it calls responds to a request. It is for this reason that
* @dev that you can signal to an oracle you'd like them to wait longer before
* @dev responding to the request (however this is not enforced in the contract
* @dev and so remains effective only in the case of unmodified oracle software).
*/
abstract contract VRFConsumerBaseV2 {
error OnlyCoordinatorCanFulfill(address have, address want);
address private immutable vrfCoordinator;
/**
* @param _vrfCoordinator address of VRFCoordinator contract
*/
constructor(address _vrfCoordinator) {
vrfCoordinator = _vrfCoordinator;
}
/**
* @notice fulfillRandomness handles the VRF response. Your contract must
* @notice implement it. See "SECURITY CONSIDERATIONS" above for important
* @notice principles to keep in mind when implementing your fulfillRandomness
* @notice method.
*
* @dev VRFConsumerBaseV2 expects its subcontracts to have a method with this
* @dev signature, and will call it once it has verified the proof
* @dev associated with the randomness. (It is triggered via a call to
* @dev rawFulfillRandomness, below.)
*
* @param requestId The Id initially returned by requestRandomness
* @param randomWords the VRF output expanded to the requested number of words
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;
// rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
// proof. rawFulfillRandomness then calls fulfillRandomness, after validating
// the origin of the call
function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external {
if (msg.sender != vrfCoordinator) {
revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator);
}
fulfillRandomWords(requestId, randomWords);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/LinkTokenInterface.sol";
import "../interfaces/VRFCoordinatorV2Interface.sol";
import "../dev/VRFConsumerBaseV2.sol";
contract VRFMaliciousConsumerV2 is VRFConsumerBaseV2 {
uint256[] public s_randomWords;
uint256 public s_requestId;
VRFCoordinatorV2Interface COORDINATOR;
LinkTokenInterface LINKTOKEN;
uint64 public s_subId;
uint256 public s_gasAvailable;
bytes32 s_keyHash;
constructor(address vrfCoordinator, address link) VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
LINKTOKEN = LinkTokenInterface(link);
}
function setKeyHash(bytes32 keyHash) public {
s_keyHash = keyHash;
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
s_gasAvailable = gasleft();
s_randomWords = randomWords;
s_requestId = requestId;
// Should revert
COORDINATOR.requestRandomWords(s_keyHash, s_subId, 1, 200000, 1);
}
function testCreateSubscriptionAndFund(uint96 amount) external {
if (s_subId == 0) {
s_subId = COORDINATOR.createSubscription();
COORDINATOR.addConsumer(s_subId, address(this));
}
// Approve the link transfer.
LINKTOKEN.transferAndCall(address(COORDINATOR), amount, abi.encode(s_subId));
}
function updateSubscription(address[] memory consumers) external {
require(s_subId != 0, "subID not set");
for (uint256 i = 0; i < consumers.length; i++) {
COORDINATOR.addConsumer(s_subId, consumers[i]);
}
}
function testRequestRandomness() external returns (uint256) {
return COORDINATOR.requestRandomWords(s_keyHash, s_subId, 1, 500000, 1);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/LinkTokenInterface.sol";
import "../interfaces/VRFCoordinatorV2Interface.sol";
import "../dev/VRFConsumerBaseV2.sol";
contract VRFConsumerV2 is VRFConsumerBaseV2 {
uint256[] public s_randomWords;
uint256 public s_requestId;
VRFCoordinatorV2Interface COORDINATOR;
LinkTokenInterface LINKTOKEN;
uint64 public s_subId;
uint256 public s_gasAvailable;
constructor(address vrfCoordinator, address link) VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
LINKTOKEN = LinkTokenInterface(link);
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
s_gasAvailable = gasleft();
s_randomWords = randomWords;
s_requestId = requestId;
}
function testCreateSubscriptionAndFund(uint96 amount) external {
if (s_subId == 0) {
s_subId = COORDINATOR.createSubscription();
COORDINATOR.addConsumer(s_subId, address(this));
}
// Approve the link transfer.
LINKTOKEN.transferAndCall(address(COORDINATOR), amount, abi.encode(s_subId));
}
function updateSubscription(address[] memory consumers) external {
require(s_subId != 0, "subID not set");
for (uint256 i = 0; i < consumers.length; i++) {
COORDINATOR.addConsumer(s_subId, consumers[i]);
}
}
function testRequestRandomness(
bytes32 keyHash,
uint64 subId,
uint16 minReqConfs,
uint32 callbackGasLimit,
uint32 numWords
) external returns (uint256) {
return COORDINATOR.requestRandomWords(keyHash, subId, minReqConfs, callbackGasLimit, numWords);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/LinkTokenInterface.sol";
import "../interfaces/VRFCoordinatorV2Interface.sol";
import "../dev/VRFConsumerBaseV2.sol";
contract VRFConsumerExternalSubOwnerExample is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
LinkTokenInterface LINKTOKEN;
struct RequestConfig {
uint64 subId;
uint32 callbackGasLimit;
uint16 requestConfirmations;
uint32 numWords;
bytes32 keyHash;
}
RequestConfig s_requestConfig;
uint256[] s_randomWords;
uint256 s_requestId;
constructor(
address vrfCoordinator,
address link,
uint32 callbackGasLimit,
uint16 requestConfirmations,
uint32 numWords,
bytes32 keyHash
) VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
LINKTOKEN = LinkTokenInterface(link);
s_requestConfig = RequestConfig({
subId: 0, // Initially unset
callbackGasLimit: callbackGasLimit,
requestConfirmations: requestConfirmations,
numWords: numWords,
keyHash: keyHash
});
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
s_randomWords = randomWords;
}
function requestRandomWords() external {
RequestConfig memory rc = s_requestConfig;
// Will revert if subscription is not set and funded.
s_requestId = COORDINATOR.requestRandomWords(
rc.keyHash,
rc.subId,
rc.requestConfirmations,
rc.callbackGasLimit,
rc.numWords
);
}
function setSubscriptionID(uint64 subId) public {
s_requestConfig.subId = subId;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/LinkTokenInterface.sol";
import "../interfaces/BlockhashStoreInterface.sol";
import "../interfaces/AggregatorV3Interface.sol";
import "../interfaces/TypeAndVersionInterface.sol";
import "./VRF.sol";
import "../ConfirmedOwner.sol";
import "./VRFConsumerBaseV2.sol";
contract VRFCoordinatorV2 is VRF, ConfirmedOwner, TypeAndVersionInterface {
LinkTokenInterface public immutable LINK;
AggregatorV3Interface public immutable LINK_ETH_FEED;
BlockhashStoreInterface public immutable BLOCKHASH_STORE;
// We need to maintain a list of consuming addresses.
// This bound ensures we are able to loop over them as needed.
// Should a user require more consumers, they can use multiple subscriptions.
uint16 public constant MAX_CONSUMERS = 100;
error TooManyConsumers();
error InsufficientBalance();
error InvalidConsumer(uint64 subId, address consumer);
error InvalidSubscription();
error OnlyCallableFromLink();
error InvalidCalldata();
error MustBeSubOwner(address owner);
error MustBeRequestedOwner(address proposedOwner);
error BalanceInvariantViolated(uint256 internalBalance, uint256 externalBalance); // Should never happen
event FundsRecovered(address to, uint256 amount);
struct Subscription {
// There are only 1e9*1e18 = 1e27 juels in existence, so the balance can fit in uint96 (2^96 ~ 7e28)
uint96 balance; // Common link balance used for all consumer requests.
address owner; // Owner can fund/withdraw/cancel the sub.
address requestedOwner; // For safely transferring sub ownership.
// Maintains the list of keys in s_consumers.
// We do this for 2 reasons:
// 1. To be able to clean up all keys from s_consumers when canceling a subscription.
// 2. To be able to return the list of all consumers in getSubscription.
// Note that we need the s_consumers map to be able to directly check if a
// consumer is valid without reading all the consumers from storage.
address[] consumers;
}
struct Consumer {
uint64 subId;
uint64 nonce;
}
mapping(address => mapping(uint64 => Consumer)) /* consumer */ /* subId */
private s_consumers;
mapping(uint64 => Subscription) /* subId */ /* subscription */
private s_subscriptions;
uint64 private s_currentSubId;
// s_totalBalance tracks the total link sent to/from
// this contract through onTokenTransfer, defundSubscription, cancelSubscription and oracleWithdraw.
// A discrepancy with this contract's link balance indicates someone
// sent tokens using transfer and so we may need to use recoverFunds.
uint96 public s_totalBalance;
event SubscriptionCreated(uint64 indexed subId, address owner);
event SubscriptionFunded(uint64 indexed subId, uint256 oldBalance, uint256 newBalance);
event SubscriptionConsumerAdded(uint64 indexed subId, address consumer);
event SubscriptionConsumerRemoved(uint64 indexed subId, address consumer);
event SubscriptionDefunded(uint64 indexed subId, uint256 oldBalance, uint256 newBalance);
event SubscriptionCanceled(uint64 indexed subId, address to, uint256 amount);
event SubscriptionOwnerTransferRequested(uint64 indexed subId, address from, address to);
event SubscriptionOwnerTransferred(uint64 indexed subId, address from, address to);
// Set this maximum to 200 to give us a 56 block window to fulfill
// the request before requiring the block hash feeder.
uint16 public constant MAX_REQUEST_CONFIRMATIONS = 200;
uint32 public constant MAX_NUM_WORDS = 500;
// The minimum gas limit that could be requested for a callback.
// Set to 5k to ensure plenty of room to make the call itself.
uint256 public constant MIN_GAS_LIMIT = 5_000;
error InvalidRequestConfirmations(uint16 have, uint16 min, uint16 max);
error GasLimitTooBig(uint32 have, uint32 want);
error NumWordsTooBig(uint32 have, uint32 want);
error ProvingKeyAlreadyRegistered(bytes32 keyHash);
error NoSuchProvingKey(bytes32 keyHash);
error InvalidLinkWeiPrice(int256 linkWei);
error InsufficientGasForConsumer(uint256 have, uint256 want);
error NoCorrespondingRequest();
error IncorrectCommitment();
error BlockhashNotInStore(uint256 blockNum);
error PaymentTooLarge();
error Reentrant();
struct RequestCommitment {
uint64 blockNum;
uint64 subId;
uint32 callbackGasLimit;
uint32 numWords;
address sender;
}
mapping(bytes32 => address) /* keyHash */ /* oracle */
private s_provingKeys;
mapping(address => uint96) /* oracle */ /* LINK balance */
private s_withdrawableTokens;
mapping(uint256 => bytes32) /* requestID */ /* commitment */
private s_requestCommitments;
event ProvingKeyRegistered(bytes32 keyHash, address indexed oracle);
event ProvingKeyDeregistered(bytes32 keyHash, address indexed oracle);
event RandomWordsRequested(
bytes32 indexed keyHash,
uint256 requestId,
uint256 preSeed,
uint64 subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords,
address indexed sender
);
event RandomWordsFulfilled(uint256 indexed requestId, uint256[] output, bool success);
struct Config {
uint16 minimumRequestConfirmations;
// Flat fee charged per fulfillment in millionths of link
// So fee range is [0, 2^32/10^6].
uint32 fulfillmentFlatFeeLinkPPM;
uint32 maxGasLimit;
// stalenessSeconds is how long before we consider the feed price to be stale
// and fallback to fallbackWeiPerUnitLink.
uint32 stalenessSeconds;
// Gas to cover oracle payment after we calculate the payment.
// We make it configurable in case those operations are repriced.
uint32 gasAfterPaymentCalculation;
uint96 minimumSubscriptionBalance;
// Re-entrancy protection.
bool reentrancyLock;
}
int256 internal s_fallbackWeiPerUnitLink;
Config private s_config;
event ConfigSet(
uint16 minimumRequestConfirmations,
uint32 fulfillmentFlatFeeLinkPPM,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
uint96 minimumSubscriptionBalance,
int256 fallbackWeiPerUnitLink
);
constructor(
address link,
address blockhashStore,
address linkEthFeed
) ConfirmedOwner(msg.sender) {
LINK = LinkTokenInterface(link);
LINK_ETH_FEED = AggregatorV3Interface(linkEthFeed);
BLOCKHASH_STORE = BlockhashStoreInterface(blockhashStore);
}
/**
* @notice Registers a proving key to an oracle.
* @param oracle address of the oracle
* @param publicProvingKey key that oracle can use to submit vrf fulfillments
*/
function registerProvingKey(address oracle, uint256[2] calldata publicProvingKey) external onlyOwner {
bytes32 kh = hashOfKey(publicProvingKey);
if (s_provingKeys[kh] != address(0)) {
revert ProvingKeyAlreadyRegistered(kh);
}
s_provingKeys[kh] = oracle;
emit ProvingKeyRegistered(kh, oracle);
}
/**
* @notice Deregisters a proving key to an oracle.
* @param publicProvingKey key that oracle can use to submit vrf fulfillments
*/
function deregisterProvingKey(uint256[2] calldata publicProvingKey) external onlyOwner {
bytes32 kh = hashOfKey(publicProvingKey);
address oracle = s_provingKeys[kh];
if (oracle == address(0)) {
revert NoSuchProvingKey(kh);
}
delete s_provingKeys[kh];
emit ProvingKeyDeregistered(kh, oracle);
}
/**
* @notice Returns the serviceAgreements key associated with this public key
* @param publicKey the key to return the address for
*/
function hashOfKey(uint256[2] memory publicKey) public pure returns (bytes32) {
return keccak256(abi.encode(publicKey));
}
function setConfig(
uint16 minimumRequestConfirmations,
uint32 fulfillmentFlatFeeLinkPPM,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
uint96 minimumSubscriptionBalance,
int256 fallbackWeiPerUnitLink
) external onlyOwner {
if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) {
revert InvalidRequestConfirmations(
minimumRequestConfirmations,
minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
if (fallbackWeiPerUnitLink <= 0) {
revert InvalidLinkWeiPrice(fallbackWeiPerUnitLink);
}
s_config = Config({
minimumRequestConfirmations: minimumRequestConfirmations,
fulfillmentFlatFeeLinkPPM: fulfillmentFlatFeeLinkPPM,
maxGasLimit: maxGasLimit,
stalenessSeconds: stalenessSeconds,
gasAfterPaymentCalculation: gasAfterPaymentCalculation,
minimumSubscriptionBalance: minimumSubscriptionBalance,
reentrancyLock: false
});
s_fallbackWeiPerUnitLink = fallbackWeiPerUnitLink;
emit ConfigSet(
minimumRequestConfirmations,
fulfillmentFlatFeeLinkPPM,
maxGasLimit,
stalenessSeconds,
gasAfterPaymentCalculation,
minimumSubscriptionBalance,
fallbackWeiPerUnitLink
);
}
/**
* @notice read the current configuration of the coordinator.
*/
function getConfig()
external
view
returns (
uint16 minimumRequestConfirmations,
uint32 fulfillmentFlatFeeLinkPPM,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
uint96 minimumSubscriptionBalance,
int256 fallbackWeiPerUnitLink
)
{
Config memory config = s_config;
return (
config.minimumRequestConfirmations,
config.fulfillmentFlatFeeLinkPPM,
config.maxGasLimit,
config.stalenessSeconds,
config.gasAfterPaymentCalculation,
config.minimumSubscriptionBalance,
s_fallbackWeiPerUnitLink
);
}
function recoverFunds(address to) external onlyOwner {
uint256 externalBalance = LINK.balanceOf(address(this));
uint256 internalBalance = uint256(s_totalBalance);
if (internalBalance > externalBalance) {
revert BalanceInvariantViolated(internalBalance, externalBalance);
}
if (internalBalance < externalBalance) {
uint256 amount = externalBalance - internalBalance;
LINK.transfer(to, amount);
emit FundsRecovered(to, amount);
}
// If the balances are equal, nothing to be done.
}
// Want to ensure these arguments can fit inside of 2 words
// so in the worst case where the consuming contract has to read all of them
// from storage, it only has to read 2 words.
function requestRandomWords(
bytes32 keyHash, // Corresponds to a particular offchain job which uses that key for the proofs
uint64 subId,
uint16 requestConfirmations,
uint32 callbackGasLimit,
uint32 numWords // Desired number of random words
) external nonReentrant returns (uint256) {
// Input validation using the subscription storage.
if (s_subscriptions[subId].owner == address(0)) {
revert InvalidSubscription();
}
// Its important to ensure that the consumer is in fact who they say they
// are, otherwise they could use someone else's subscription balance.
Consumer memory consumer = s_consumers[msg.sender][subId];
if (consumer.subId == 0) {
revert InvalidConsumer(subId, msg.sender);
}
// Input validation using the config storage word.
if (
requestConfirmations < s_config.minimumRequestConfirmations || requestConfirmations > MAX_REQUEST_CONFIRMATIONS
) {
revert InvalidRequestConfirmations(
requestConfirmations,
s_config.minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
if (s_subscriptions[subId].balance < s_config.minimumSubscriptionBalance) {
revert InsufficientBalance();
}
if (callbackGasLimit > s_config.maxGasLimit) {
revert GasLimitTooBig(callbackGasLimit, s_config.maxGasLimit);
}
if (numWords > MAX_NUM_WORDS) {
revert NumWordsTooBig(numWords, MAX_NUM_WORDS);
}
// Note we do not check whether the keyHash is valid to save gas.
// The consequence for users is that they can send requests
// for invalid keyHashes which will simply not be fulfilled.
uint64 nonce = consumer.nonce + 1;
uint256 preSeed = uint256(keccak256(abi.encode(keyHash, msg.sender, subId, nonce)));
uint256 requestId = uint256(keccak256(abi.encode(keyHash, preSeed)));
s_requestCommitments[requestId] = keccak256(
abi.encode(requestId, block.number, subId, callbackGasLimit, numWords, msg.sender)
);
emit RandomWordsRequested(
keyHash,
requestId,
preSeed,
subId,
requestConfirmations,
callbackGasLimit,
numWords,
msg.sender
);
s_consumers[msg.sender][subId].nonce = nonce;
return requestId;
}
function getCommitment(uint256 requestId) external view returns (bytes32) {
return s_requestCommitments[requestId];
}
/**
* @dev calls target address with exactly gasAmount gas and data as calldata
* or reverts if at least gasAmount gas is not available.
* The maximum amount of gasAmount is all gas available but 1/64th.
* The minimum amount of gasAmount is MIN_GAS_LIMIT.
*/
function callWithExactGas(
uint256 gasAmount,
address target,
bytes memory data
) private returns (bool success) {
// solhint-disable-next-line no-inline-assembly
assembly {
let g := gas()
// Compute g -= MIN_GAS_LIMIT and check for underflow
if lt(g, MIN_GAS_LIMIT) {
revert(0, 0)
}
g := sub(g, MIN_GAS_LIMIT)
// if g - g//64 <= gasAmount, revert
// (we subtract g//64 because of EIP-150)
if iszero(gt(sub(g, div(g, 64)), gasAmount)) {
revert(0, 0)
}
// solidity calls check that a contract actually exists at the destination, so we do the same
if iszero(extcodesize(target)) {
revert(0, 0)
}
// call and return whether we succeeded. ignore return data
success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
}
return success;
}
function getRandomnessFromProof(Proof memory proof, RequestCommitment memory rc)
private
view
returns (
bytes32 keyHash,
uint256 requestId,
uint256 randomness
)
{
keyHash = hashOfKey(proof.pk);
// Only registered proving keys are permitted.
address oracle = s_provingKeys[keyHash];
if (oracle == address(0)) {
revert NoSuchProvingKey(keyHash);
}
requestId = uint256(keccak256(abi.encode(keyHash, proof.seed)));
bytes32 commitment = s_requestCommitments[requestId];
if (commitment == 0) {
revert NoCorrespondingRequest();
}
if (
commitment != keccak256(abi.encode(requestId, rc.blockNum, rc.subId, rc.callbackGasLimit, rc.numWords, rc.sender))
) {
revert IncorrectCommitment();
}
bytes32 blockHash = blockhash(rc.blockNum);
if (blockHash == bytes32(0)) {
blockHash = BLOCKHASH_STORE.getBlockhash(rc.blockNum);
if (blockHash == bytes32(0)) {
revert BlockhashNotInStore(rc.blockNum);
}
}
// The seed actually used by the VRF machinery, mixing in the blockhash
uint256 actualSeed = uint256(keccak256(abi.encodePacked(proof.seed, blockHash)));
randomness = VRF.randomValueFromVRFProof(proof, actualSeed); // Reverts on failure
}
function fulfillRandomWords(Proof memory proof, RequestCommitment memory rc) external nonReentrant {
uint256 startGas = gasleft();
(bytes32 keyHash, uint256 requestId, uint256 randomness) = getRandomnessFromProof(proof, rc);
uint256[] memory randomWords = new uint256[](rc.numWords);
for (uint256 i = 0; i < rc.numWords; i++) {
randomWords[i] = uint256(keccak256(abi.encode(randomness, i)));
}
delete s_requestCommitments[requestId];
VRFConsumerBaseV2 v;
bytes memory resp = abi.encodeWithSelector(v.rawFulfillRandomWords.selector, proof.seed, randomWords);
uint256 gasPreCallback = gasleft();
if (gasPreCallback < rc.callbackGasLimit) {
revert InsufficientGasForConsumer(gasPreCallback, rc.callbackGasLimit);
}
// Call with explicitly the amount of callback gas requested
// Important to not let them exhaust the gas budget and avoid oracle payment.
// Do not allow any non-view/non-pure coordinator functions to be called
// during the consumers callback code via reentrancyLock.
s_config.reentrancyLock = true;
bool success = callWithExactGas(rc.callbackGasLimit, rc.sender, resp);
emit RandomWordsFulfilled(requestId, randomWords, success);
s_config.reentrancyLock = false;
// We want to charge users exactly for how much gas they use in their callback.
// The gasAfterPaymentCalculation is meant to cover these additional operations where we
// decrement the subscription balance and increment the oracles withdrawable balance.
// We also add the flat link fee to the payment amount.
// Its specified in millionths of link, if s_config.fulfillmentFlatFeeLinkPPM = 1
// 1 link / 1e6 = 1e18 juels / 1e6 = 1e12 juels.
uint96 payment = calculatePaymentAmount(
startGas,
s_config.gasAfterPaymentCalculation,
s_config.fulfillmentFlatFeeLinkPPM,
tx.gasprice
);
if (s_subscriptions[rc.subId].balance < payment) {
revert InsufficientBalance();
}
s_subscriptions[rc.subId].balance -= payment;
s_withdrawableTokens[s_provingKeys[keyHash]] += payment;
}
// Get the amount of gas used for fulfillment
function calculatePaymentAmount(
uint256 startGas,
uint256 gasAfterPaymentCalculation,
uint32 fulfillmentFlatFeeLinkPPM,
uint256 weiPerUnitGas
) internal view returns (uint96) {
int256 weiPerUnitLink;
weiPerUnitLink = getFeedData();
if (weiPerUnitLink <= 0) {
revert InvalidLinkWeiPrice(weiPerUnitLink);
}
// (1e18 juels/link) (wei/gas * gas) / (wei/link) = juels
uint256 paymentNoFee = (1e18 * weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft())) /
uint256(weiPerUnitLink);
uint256 fee = 1e12 * uint256(fulfillmentFlatFeeLinkPPM);
if (paymentNoFee > (1e27 - fee)) {
revert PaymentTooLarge(); // Payment + fee cannot be more than all of the link in existence.
}
return uint96(paymentNoFee + fee);
}
function getFeedData() private view returns (int256) {
uint32 stalenessSeconds = s_config.stalenessSeconds;
bool staleFallback = stalenessSeconds > 0;
uint256 timestamp;
int256 weiPerUnitLink;
(, weiPerUnitLink, , timestamp, ) = LINK_ETH_FEED.latestRoundData();
// solhint-disable-next-line not-rely-on-time
if (staleFallback && stalenessSeconds < block.timestamp - timestamp) {
weiPerUnitLink = s_fallbackWeiPerUnitLink;
}
return weiPerUnitLink;
}
function oracleWithdraw(address recipient, uint96 amount) external nonReentrant {
if (s_withdrawableTokens[msg.sender] < amount) {
revert InsufficientBalance();
}
s_withdrawableTokens[msg.sender] -= amount;
s_totalBalance -= amount;
if (!LINK.transfer(recipient, amount)) {
revert InsufficientBalance();
}
}
function onTokenTransfer(
address sender,
uint256 amount,
bytes calldata data
) external nonReentrant {
if (msg.sender != address(LINK)) {
revert OnlyCallableFromLink();
}
if (data.length != 32) {
revert InvalidCalldata();
}
uint64 subId = abi.decode(data, (uint64));
if (s_subscriptions[subId].owner == address(0)) {
revert InvalidSubscription();
}
address owner = s_subscriptions[subId].owner;
if (owner != sender) {
revert MustBeSubOwner(owner);
}
uint256 oldBalance = s_subscriptions[subId].balance;
s_subscriptions[subId].balance += uint96(amount);
s_totalBalance += uint96(amount);
emit SubscriptionFunded(subId, oldBalance, oldBalance + amount);
}
function getSubscription(uint64 subId)
external
view
returns (
uint96 balance,
address owner,
address[] memory consumers
)
{
if (s_subscriptions[subId].owner == address(0)) {
revert InvalidSubscription();
}
return (s_subscriptions[subId].balance, s_subscriptions[subId].owner, s_subscriptions[subId].consumers);
}
function createSubscription() external nonReentrant returns (uint64) {
s_currentSubId++;
uint64 currentSubId = s_currentSubId;
address[] memory consumers = new address[](0);
s_subscriptions[currentSubId] = Subscription({
balance: 0,
owner: msg.sender,
requestedOwner: address(0),
consumers: consumers
});
emit SubscriptionCreated(currentSubId, msg.sender);
return currentSubId;
}
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external onlySubOwner(subId) nonReentrant {
// Proposing to address(0) would never be claimable so don't need to check.
if (s_subscriptions[subId].requestedOwner != newOwner) {
s_subscriptions[subId].requestedOwner = newOwner;
emit SubscriptionOwnerTransferRequested(subId, msg.sender, newOwner);
}
}
function acceptSubscriptionOwnerTransfer(uint64 subId) external nonReentrant {
if (s_subscriptions[subId].owner == address(0)) {
revert InvalidSubscription();
}
if (s_subscriptions[subId].requestedOwner != msg.sender) {
revert MustBeRequestedOwner(s_subscriptions[subId].requestedOwner);
}
address oldOwner = s_subscriptions[subId].owner;
s_subscriptions[subId].owner = msg.sender;
s_subscriptions[subId].requestedOwner = address(0);
emit SubscriptionOwnerTransferred(subId, oldOwner, msg.sender);
}
function removeConsumer(uint64 subId, address consumer) external onlySubOwner(subId) nonReentrant {
if (s_consumers[consumer][subId].subId == 0) {
revert InvalidConsumer(subId, consumer);
}
// Note bounded by MAX_CONSUMERS
address[] memory consumers = s_subscriptions[subId].consumers;
uint256 lastConsumerIndex = consumers.length - 1;
for (uint256 i = 0; i < consumers.length; i++) {
if (consumers[i] == consumer) {
address last = consumers[lastConsumerIndex];
// Storage write to preserve last element
s_subscriptions[subId].consumers[i] = last;
// Storage remove last element
s_subscriptions[subId].consumers.pop();
break;
}
}
delete s_consumers[consumer][subId];
emit SubscriptionConsumerRemoved(subId, consumer);
}
function addConsumer(uint64 subId, address consumer) external onlySubOwner(subId) nonReentrant {
// Already maxed, cannot add any more consumers.
if (s_subscriptions[subId].consumers.length == MAX_CONSUMERS) {
revert TooManyConsumers();
}
if (s_consumers[consumer][subId].subId != 0) {
// Idempotence - do nothing if already added.
// Ensures uniqueness in s_subscriptions[subId].consumers.
return;
}
s_consumers[consumer][subId] = Consumer({subId: subId, nonce: 0});
s_subscriptions[subId].consumers.push(consumer);
emit SubscriptionConsumerAdded(subId, consumer);
}
function defundSubscription(
uint64 subId,
address to,
uint96 amount
) external onlySubOwner(subId) nonReentrant {
if (s_subscriptions[subId].balance < amount) {
revert InsufficientBalance();
}
uint256 oldBalance = s_subscriptions[subId].balance;
s_subscriptions[subId].balance -= amount;
s_totalBalance -= amount;
if (!LINK.transfer(to, amount)) {
revert InsufficientBalance();
}
emit SubscriptionDefunded(subId, oldBalance, s_subscriptions[subId].balance);
}
// Keep this separate from zeroing, perhaps there is a use case where consumers
// want to keep the subId, but withdraw all the link.
function cancelSubscription(uint64 subId, address to) external onlySubOwner(subId) nonReentrant {
Subscription memory sub = s_subscriptions[subId];
uint96 balance = sub.balance;
// Note bounded by MAX_CONSUMERS;
// If no consumers, does nothing.
for (uint256 i = 0; i < sub.consumers.length; i++) {
delete s_consumers[sub.consumers[i]][subId];
}
delete s_subscriptions[subId];
s_totalBalance -= balance;
if (!LINK.transfer(to, uint256(balance))) {
revert InsufficientBalance();
}
emit SubscriptionCanceled(subId, to, balance);
}
modifier onlySubOwner(uint64 subId) {
address owner = s_subscriptions[subId].owner;
if (owner == address(0)) {
revert InvalidSubscription();
}
if (msg.sender != owner) {
revert MustBeSubOwner(owner);
}
_;
}
modifier nonReentrant() {
if (s_config.reentrancyLock) {
revert Reentrant();
}
_;
}
/**
* @notice The type and version of this contract
* @return Type and version string
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "VRFCoordinatorV2 1.0.0";
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface BlockhashStoreInterface {
function getBlockhash(uint256 number) external view returns (bytes32);
}
// 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);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
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;
abstract contract TypeAndVersionInterface {
function typeAndVersion() external pure virtual returns (string memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/** ****************************************************************************
* @notice Verification of verifiable-random-function (VRF) proofs, following
* @notice https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.3
* @notice See https://eprint.iacr.org/2017/099.pdf for security proofs.
* @dev Bibliographic references:
* @dev Goldberg, et al., "Verifiable Random Functions (VRFs)", Internet Draft
* @dev draft-irtf-cfrg-vrf-05, IETF, Aug 11 2019,
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05
* @dev Papadopoulos, et al., "Making NSEC5 Practical for DNSSEC", Cryptology
* @dev ePrint Archive, Report 2017/099, https://eprint.iacr.org/2017/099.pdf
* ****************************************************************************
* @dev USAGE
* @dev The main entry point is randomValueFromVRFProof. See its docstring.
* ****************************************************************************
* @dev PURPOSE
* @dev Reggie the Random Oracle (not his real job) wants to provide randomness
* @dev to Vera the verifier in such a way that Vera can be sure he's not
* @dev making his output up to suit himself. Reggie provides Vera a public key
* @dev to which he knows the secret key. Each time Vera provides a seed to
* @dev Reggie, he gives back a value which is computed completely
* @dev deterministically from the seed and the secret key.
* @dev Reggie provides a proof by which Vera can verify that the output was
* @dev correctly computed once Reggie tells it to her, but without that proof,
* @dev the output is computationally indistinguishable to her from a uniform
* @dev random sample from the output space.
* @dev The purpose of this contract is to perform that verification.
* ****************************************************************************
* @dev DESIGN NOTES
* @dev The VRF algorithm verified here satisfies the full unqiqueness, full
* @dev collision resistance, and full pseudorandomness security properties.
* @dev See "SECURITY PROPERTIES" below, and
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-3
* @dev An elliptic curve point is generally represented in the solidity code
* @dev as a uint256[2], corresponding to its affine coordinates in
* @dev GF(FIELD_SIZE).
* @dev For the sake of efficiency, this implementation deviates from the spec
* @dev in some minor ways:
* @dev - Keccak hash rather than the SHA256 hash recommended in
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5
* @dev Keccak costs much less gas on the EVM, and provides similar security.
* @dev - Secp256k1 curve instead of the P-256 or ED25519 curves recommended in
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5
* @dev For curve-point multiplication, it's much cheaper to abuse ECRECOVER
* @dev - hashToCurve recursively hashes until it finds a curve x-ordinate. On
* @dev the EVM, this is slightly more efficient than the recommendation in
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.1.1
* @dev step 5, to concatenate with a nonce then hash, and rehash with the
* @dev nonce updated until a valid x-ordinate is found.
* @dev - hashToCurve does not include a cipher version string or the byte 0x1
* @dev in the hash message, as recommended in step 5.B of the draft
* @dev standard. They are unnecessary here because no variation in the
* @dev cipher suite is allowed.
* @dev - Similarly, the hash input in scalarFromCurvePoints does not include a
* @dev commitment to the cipher suite, either, which differs from step 2 of
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.3
* @dev . Also, the hash input is the concatenation of the uncompressed
* @dev points, not the compressed points as recommended in step 3.
* @dev - In the calculation of the challenge value "c", the "u" value (i.e.
* @dev the value computed by Reggie as the nonce times the secp256k1
* @dev generator point, see steps 5 and 7 of
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.3
* @dev ) is replaced by its ethereum address, i.e. the lower 160 bits of the
* @dev keccak hash of the original u. This is because we only verify the
* @dev calculation of u up to its address, by abusing ECRECOVER.
* ****************************************************************************
* @dev SECURITY PROPERTIES
* @dev Here are the security properties for this VRF:
* @dev Full uniqueness: For any seed and valid VRF public key, there is
* @dev exactly one VRF output which can be proved to come from that seed, in
* @dev the sense that the proof will pass verifyVRFProof.
* @dev Full collision resistance: It's cryptographically infeasible to find
* @dev two seeds with same VRF output from a fixed, valid VRF key
* @dev Full pseudorandomness: Absent the proofs that the VRF outputs are
* @dev derived from a given seed, the outputs are computationally
* @dev indistinguishable from randomness.
* @dev https://eprint.iacr.org/2017/099.pdf, Appendix B contains the proofs
* @dev for these properties.
* @dev For secp256k1, the key validation described in section
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.6
* @dev is unnecessary, because secp256k1 has cofactor 1, and the
* @dev representation of the public key used here (affine x- and y-ordinates
* @dev of the secp256k1 point on the standard y^2=x^3+7 curve) cannot refer to
* @dev the point at infinity.
* ****************************************************************************
* @dev OTHER SECURITY CONSIDERATIONS
*
* @dev The seed input to the VRF could in principle force an arbitrary amount
* @dev of work in hashToCurve, by requiring extra rounds of hashing and
* @dev checking whether that's yielded the x ordinate of a secp256k1 point.
* @dev However, under the Random Oracle Model the probability of choosing a
* @dev point which forces n extra rounds in hashToCurve is 2⁻ⁿ. The base cost
* @dev for calling hashToCurve is about 25,000 gas, and each round of checking
* @dev for a valid x ordinate costs about 15,555 gas, so to find a seed for
* @dev which hashToCurve would cost more than 2,017,000 gas, one would have to
* @dev try, in expectation, about 2¹²⁸ seeds, which is infeasible for any
* @dev foreseeable computational resources. (25,000 + 128 * 15,555 < 2,017,000.)
* @dev Since the gas block limit for the Ethereum main net is 10,000,000 gas,
* @dev this means it is infeasible for an adversary to prevent correct
* @dev operation of this contract by choosing an adverse seed.
* @dev (See TestMeasureHashToCurveGasCost for verification of the gas cost for
* @dev hashToCurve.)
* @dev It may be possible to make a secure constant-time hashToCurve function.
* @dev See notes in hashToCurve docstring.
*/
contract VRF {
// See https://www.secg.org/sec2-v2.pdf, section 2.4.1, for these constants.
// Number of points in Secp256k1
uint256 private constant GROUP_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
// Prime characteristic of the galois field over which Secp256k1 is defined
uint256 private constant FIELD_SIZE =
// solium-disable-next-line indentation
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
uint256 private constant WORD_LENGTH_BYTES = 0x20;
// (base^exponent) % FIELD_SIZE
// Cribbed from https://medium.com/@rbkhmrcr/precompiles-solidity-e5d29bd428c4
function bigModExp(uint256 base, uint256 exponent) internal view returns (uint256 exponentiation) {
uint256 callResult;
uint256[6] memory bigModExpContractInputs;
bigModExpContractInputs[0] = WORD_LENGTH_BYTES; // Length of base
bigModExpContractInputs[1] = WORD_LENGTH_BYTES; // Length of exponent
bigModExpContractInputs[2] = WORD_LENGTH_BYTES; // Length of modulus
bigModExpContractInputs[3] = base;
bigModExpContractInputs[4] = exponent;
bigModExpContractInputs[5] = FIELD_SIZE;
uint256[1] memory output;
assembly {
// solhint-disable-line no-inline-assembly
callResult := staticcall(
not(0), // Gas cost: no limit
0x05, // Bigmodexp contract address
bigModExpContractInputs,
0xc0, // Length of input segment: 6*0x20-bytes
output,
0x20 // Length of output segment
)
}
if (callResult == 0) {
revert("bigModExp failure!");
}
return output[0];
}
// Let q=FIELD_SIZE. q % 4 = 3, ∴ x≡r^2 mod q ⇒ x^SQRT_POWER≡±r mod q. See
// https://en.wikipedia.org/wiki/Modular_square_root#Prime_or_prime_power_modulus
uint256 private constant SQRT_POWER = (FIELD_SIZE + 1) >> 2;
// Computes a s.t. a^2 = x in the field. Assumes a exists
function squareRoot(uint256 x) internal view returns (uint256) {
return bigModExp(x, SQRT_POWER);
}
// The value of y^2 given that (x,y) is on secp256k1.
function ySquared(uint256 x) internal pure returns (uint256) {
// Curve is y^2=x^3+7. See section 2.4.1 of https://www.secg.org/sec2-v2.pdf
uint256 xCubed = mulmod(x, mulmod(x, x, FIELD_SIZE), FIELD_SIZE);
return addmod(xCubed, 7, FIELD_SIZE);
}
// True iff p is on secp256k1
function isOnCurve(uint256[2] memory p) internal pure returns (bool) {
// Section 2.3.6. in https://www.secg.org/sec1-v2.pdf
// requires each ordinate to be in [0, ..., FIELD_SIZE-1]
require(p[0] < FIELD_SIZE, "invalid x-ordinate");
require(p[1] < FIELD_SIZE, "invalid y-ordinate");
return ySquared(p[0]) == mulmod(p[1], p[1], FIELD_SIZE);
}
// Hash x uniformly into {0, ..., FIELD_SIZE-1}.
function fieldHash(bytes memory b) internal pure returns (uint256 x_) {
x_ = uint256(keccak256(b));
// Rejecting if x >= FIELD_SIZE corresponds to step 2.1 in section 2.3.4 of
// http://www.secg.org/sec1-v2.pdf , which is part of the definition of
// string_to_point in the IETF draft
while (x_ >= FIELD_SIZE) {
x_ = uint256(keccak256(abi.encodePacked(x_)));
}
}
// Hash b to a random point which hopefully lies on secp256k1. The y ordinate
// is always even, due to
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.1.1
// step 5.C, which references arbitrary_string_to_point, defined in
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5 as
// returning the point with given x ordinate, and even y ordinate.
function newCandidateSecp256k1Point(bytes memory b) internal view returns (uint256[2] memory p) {
unchecked {
p[0] = fieldHash(b);
p[1] = squareRoot(ySquared(p[0]));
if (p[1] % 2 == 1) {
// Note that 0 <= p[1] < FIELD_SIZE
// so this cannot wrap, we use unchecked to save gas.
p[1] = FIELD_SIZE - p[1];
}
}
}
// Domain-separation tag for initial hash in hashToCurve. Corresponds to
// vrf.go/hashToCurveHashPrefix
uint256 internal constant HASH_TO_CURVE_HASH_PREFIX = 1;
// Cryptographic hash function onto the curve.
//
// Corresponds to algorithm in section 5.4.1.1 of the draft standard. (But see
// DESIGN NOTES above for slight differences.)
//
// TODO(alx): Implement a bounded-computation hash-to-curve, as described in
// "Construction of Rational Points on Elliptic Curves over Finite Fields"
// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.831.5299&rep=rep1&type=pdf
// and suggested by
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-01#section-5.2.2
// (Though we can't used exactly that because secp256k1's j-invariant is 0.)
//
// This would greatly simplify the analysis in "OTHER SECURITY CONSIDERATIONS"
// https://www.pivotaltracker.com/story/show/171120900
function hashToCurve(uint256[2] memory pk, uint256 input) internal view returns (uint256[2] memory rv) {
rv = newCandidateSecp256k1Point(abi.encodePacked(HASH_TO_CURVE_HASH_PREFIX, pk, input));
while (!isOnCurve(rv)) {
rv = newCandidateSecp256k1Point(abi.encodePacked(rv[0]));
}
}
/** *********************************************************************
* @notice Check that product==scalar*multiplicand
*
* @dev Based on Vitalik Buterin's idea in ethresear.ch post cited below.
*
* @param multiplicand: secp256k1 point
* @param scalar: non-zero GF(GROUP_ORDER) scalar
* @param product: secp256k1 expected to be multiplier * multiplicand
* @return verifies true iff product==scalar*multiplicand, with cryptographically high probability
*/
function ecmulVerify(
uint256[2] memory multiplicand,
uint256 scalar,
uint256[2] memory product
) internal pure returns (bool verifies) {
require(scalar != 0, "zero scalar"); // Rules out an ecrecover failure case
uint256 x = multiplicand[0]; // x ordinate of multiplicand
uint8 v = multiplicand[1] % 2 == 0 ? 27 : 28; // parity of y ordinate
// https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9
// Point corresponding to address ecrecover(0, v, x, s=scalar*x) is
// (x⁻¹ mod GROUP_ORDER) * (scalar * x * multiplicand - 0 * g), i.e.
// scalar*multiplicand. See https://crypto.stackexchange.com/a/18106
bytes32 scalarTimesX = bytes32(mulmod(scalar, x, GROUP_ORDER));
address actual = ecrecover(bytes32(0), v, bytes32(x), scalarTimesX);
// Explicit conversion to address takes bottom 160 bits
address expected = address(uint160(uint256(keccak256(abi.encodePacked(product)))));
return (actual == expected);
}
// Returns x1/z1-x2/z2=(x1z2-x2z1)/(z1z2) in projective coordinates on P¹(𝔽ₙ)
function projectiveSub(
uint256 x1,
uint256 z1,
uint256 x2,
uint256 z2
) internal pure returns (uint256 x3, uint256 z3) {
unchecked {
uint256 num1 = mulmod(z2, x1, FIELD_SIZE);
// Note this cannot wrap since x2 is a point in [0, FIELD_SIZE-1]
// we use unchecked to save gas.
uint256 num2 = mulmod(FIELD_SIZE - x2, z1, FIELD_SIZE);
(x3, z3) = (addmod(num1, num2, FIELD_SIZE), mulmod(z1, z2, FIELD_SIZE));
}
}
// Returns x1/z1*x2/z2=(x1x2)/(z1z2), in projective coordinates on P¹(𝔽ₙ)
function projectiveMul(
uint256 x1,
uint256 z1,
uint256 x2,
uint256 z2
) internal pure returns (uint256 x3, uint256 z3) {
(x3, z3) = (mulmod(x1, x2, FIELD_SIZE), mulmod(z1, z2, FIELD_SIZE));
}
/** **************************************************************************
@notice Computes elliptic-curve sum, in projective co-ordinates
@dev Using projective coordinates avoids costly divisions
@dev To use this with p and q in affine coordinates, call
@dev projectiveECAdd(px, py, qx, qy). This will return
@dev the addition of (px, py, 1) and (qx, qy, 1), in the
@dev secp256k1 group.
@dev This can be used to calculate the z which is the inverse to zInv
@dev in isValidVRFOutput. But consider using a faster
@dev re-implementation such as ProjectiveECAdd in the golang vrf package.
@dev This function assumes [px,py,1],[qx,qy,1] are valid projective
coordinates of secp256k1 points. That is safe in this contract,
because this method is only used by linearCombination, which checks
points are on the curve via ecrecover.
**************************************************************************
@param px The first affine coordinate of the first summand
@param py The second affine coordinate of the first summand
@param qx The first affine coordinate of the second summand
@param qy The second affine coordinate of the second summand
(px,py) and (qx,qy) must be distinct, valid secp256k1 points.
**************************************************************************
Return values are projective coordinates of [px,py,1]+[qx,qy,1] as points
on secp256k1, in P²(𝔽ₙ)
@return sx
@return sy
@return sz
*/
function projectiveECAdd(
uint256 px,
uint256 py,
uint256 qx,
uint256 qy
)
internal
pure
returns (
uint256 sx,
uint256 sy,
uint256 sz
)
{
unchecked {
// See "Group law for E/K : y^2 = x^3 + ax + b", in section 3.1.2, p. 80,
// "Guide to Elliptic Curve Cryptography" by Hankerson, Menezes and Vanstone
// We take the equations there for (sx,sy), and homogenize them to
// projective coordinates. That way, no inverses are required, here, and we
// only need the one inverse in affineECAdd.
// We only need the "point addition" equations from Hankerson et al. Can
// skip the "point doubling" equations because p1 == p2 is cryptographically
// impossible, and required not to be the case in linearCombination.
// Add extra "projective coordinate" to the two points
(uint256 z1, uint256 z2) = (1, 1);
// (lx, lz) = (qy-py)/(qx-px), i.e., gradient of secant line.
// Cannot wrap since px and py are in [0, FIELD_SIZE-1]
uint256 lx = addmod(qy, FIELD_SIZE - py, FIELD_SIZE);
uint256 lz = addmod(qx, FIELD_SIZE - px, FIELD_SIZE);
uint256 dx; // Accumulates denominator from sx calculation
// sx=((qy-py)/(qx-px))^2-px-qx
(sx, dx) = projectiveMul(lx, lz, lx, lz); // ((qy-py)/(qx-px))^2
(sx, dx) = projectiveSub(sx, dx, px, z1); // ((qy-py)/(qx-px))^2-px
(sx, dx) = projectiveSub(sx, dx, qx, z2); // ((qy-py)/(qx-px))^2-px-qx
uint256 dy; // Accumulates denominator from sy calculation
// sy=((qy-py)/(qx-px))(px-sx)-py
(sy, dy) = projectiveSub(px, z1, sx, dx); // px-sx
(sy, dy) = projectiveMul(sy, dy, lx, lz); // ((qy-py)/(qx-px))(px-sx)
(sy, dy) = projectiveSub(sy, dy, py, z1); // ((qy-py)/(qx-px))(px-sx)-py
if (dx != dy) {
// Cross-multiply to put everything over a common denominator
sx = mulmod(sx, dy, FIELD_SIZE);
sy = mulmod(sy, dx, FIELD_SIZE);
sz = mulmod(dx, dy, FIELD_SIZE);
} else {
// Already over a common denominator, use that for z ordinate
sz = dx;
}
}
}
// p1+p2, as affine points on secp256k1.
//
// invZ must be the inverse of the z returned by projectiveECAdd(p1, p2).
// It is computed off-chain to save gas.
//
// p1 and p2 must be distinct, because projectiveECAdd doesn't handle
// point doubling.
function affineECAdd(
uint256[2] memory p1,
uint256[2] memory p2,
uint256 invZ
) internal pure returns (uint256[2] memory) {
uint256 x;
uint256 y;
uint256 z;
(x, y, z) = projectiveECAdd(p1[0], p1[1], p2[0], p2[1]);
require(mulmod(z, invZ, FIELD_SIZE) == 1, "invZ must be inverse of z");
// Clear the z ordinate of the projective representation by dividing through
// by it, to obtain the affine representation
return [mulmod(x, invZ, FIELD_SIZE), mulmod(y, invZ, FIELD_SIZE)];
}
// True iff address(c*p+s*g) == lcWitness, where g is generator. (With
// cryptographically high probability.)
function verifyLinearCombinationWithGenerator(
uint256 c,
uint256[2] memory p,
uint256 s,
address lcWitness
) internal pure returns (bool) {
// Rule out ecrecover failure modes which return address 0.
unchecked {
require(lcWitness != address(0), "bad witness");
uint8 v = (p[1] % 2 == 0) ? 27 : 28; // parity of y-ordinate of p
// Note this cannot wrap (X - Y % X), but we use unchecked to save
// gas.
bytes32 pseudoHash = bytes32(GROUP_ORDER - mulmod(p[0], s, GROUP_ORDER)); // -s*p[0]
bytes32 pseudoSignature = bytes32(mulmod(c, p[0], GROUP_ORDER)); // c*p[0]
// https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9
// The point corresponding to the address returned by
// ecrecover(-s*p[0],v,p[0],c*p[0]) is
// (p[0]⁻¹ mod GROUP_ORDER)*(c*p[0]-(-s)*p[0]*g)=c*p+s*g.
// See https://crypto.stackexchange.com/a/18106
// https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v
address computed = ecrecover(pseudoHash, v, bytes32(p[0]), pseudoSignature);
return computed == lcWitness;
}
}
// c*p1 + s*p2. Requires cp1Witness=c*p1 and sp2Witness=s*p2. Also
// requires cp1Witness != sp2Witness (which is fine for this application,
// since it is cryptographically impossible for them to be equal. In the
// (cryptographically impossible) case that a prover accidentally derives
// a proof with equal c*p1 and s*p2, they should retry with a different
// proof nonce.) Assumes that all points are on secp256k1
// (which is checked in verifyVRFProof below.)
function linearCombination(
uint256 c,
uint256[2] memory p1,
uint256[2] memory cp1Witness,
uint256 s,
uint256[2] memory p2,
uint256[2] memory sp2Witness,
uint256 zInv
) internal pure returns (uint256[2] memory) {
unchecked {
// Note we are relying on the wrap around here
require((cp1Witness[0] % FIELD_SIZE) != (sp2Witness[0] % FIELD_SIZE), "points in sum must be distinct");
require(ecmulVerify(p1, c, cp1Witness), "First mul check failed");
require(ecmulVerify(p2, s, sp2Witness), "Second mul check failed");
return affineECAdd(cp1Witness, sp2Witness, zInv);
}
}
// Domain-separation tag for the hash taken in scalarFromCurvePoints.
// Corresponds to scalarFromCurveHashPrefix in vrf.go
uint256 internal constant SCALAR_FROM_CURVE_POINTS_HASH_PREFIX = 2;
// Pseudo-random number from inputs. Matches vrf.go/scalarFromCurvePoints, and
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.3
// The draft calls (in step 7, via the definition of string_to_int, in
// https://datatracker.ietf.org/doc/html/rfc8017#section-4.2 ) for taking the
// first hash without checking that it corresponds to a number less than the
// group order, which will lead to a slight bias in the sample.
//
// TODO(alx): We could save a bit of gas by following the standard here and
// using the compressed representation of the points, if we collated the y
// parities into a single bytes32.
// https://www.pivotaltracker.com/story/show/171120588
function scalarFromCurvePoints(
uint256[2] memory hash,
uint256[2] memory pk,
uint256[2] memory gamma,
address uWitness,
uint256[2] memory v
) internal pure returns (uint256 s) {
return uint256(keccak256(abi.encodePacked(SCALAR_FROM_CURVE_POINTS_HASH_PREFIX, hash, pk, gamma, v, uWitness)));
}
// True if (gamma, c, s) is a correctly constructed randomness proof from pk
// and seed. zInv must be the inverse of the third ordinate from
// projectiveECAdd applied to cGammaWitness and sHashWitness. Corresponds to
// section 5.3 of the IETF draft.
//
// TODO(alx): Since I'm only using pk in the ecrecover call, I could only pass
// the x ordinate, and the parity of the y ordinate in the top bit of uWitness
// (which I could make a uint256 without using any extra space.) Would save
// about 2000 gas. https://www.pivotaltracker.com/story/show/170828567
function verifyVRFProof(
uint256[2] memory pk,
uint256[2] memory gamma,
uint256 c,
uint256 s,
uint256 seed,
address uWitness,
uint256[2] memory cGammaWitness,
uint256[2] memory sHashWitness,
uint256 zInv
) internal view {
unchecked {
require(isOnCurve(pk), "public key is not on curve");
require(isOnCurve(gamma), "gamma is not on curve");
require(isOnCurve(cGammaWitness), "cGammaWitness is not on curve");
require(isOnCurve(sHashWitness), "sHashWitness is not on curve");
// Step 5. of IETF draft section 5.3 (pk corresponds to 5.3's Y, and here
// we use the address of u instead of u itself. Also, here we add the
// terms instead of taking the difference, and in the proof consruction in
// vrf.GenerateProof, we correspondingly take the difference instead of
// taking the sum as they do in step 7 of section 5.1.)
require(verifyLinearCombinationWithGenerator(c, pk, s, uWitness), "addr(c*pk+s*g)!=_uWitness");
// Step 4. of IETF draft section 5.3 (pk corresponds to Y, seed to alpha_string)
uint256[2] memory hash = hashToCurve(pk, seed);
// Step 6. of IETF draft section 5.3, but see note for step 5 about +/- terms
uint256[2] memory v = linearCombination(c, gamma, cGammaWitness, s, hash, sHashWitness, zInv);
// Steps 7. and 8. of IETF draft section 5.3
uint256 derivedC = scalarFromCurvePoints(hash, pk, gamma, uWitness, v);
require(c == derivedC, "invalid proof");
}
}
// Domain-separation tag for the hash used as the final VRF output.
// Corresponds to vrfRandomOutputHashPrefix in vrf.go
uint256 internal constant VRF_RANDOM_OUTPUT_HASH_PREFIX = 3;
struct Proof {
uint256[2] pk;
uint256[2] gamma;
uint256 c;
uint256 s;
uint256 seed;
address uWitness;
uint256[2] cGammaWitness;
uint256[2] sHashWitness;
uint256 zInv;
}
/* ***************************************************************************
* @notice Returns proof's output, if proof is valid. Otherwise reverts
* @param proof vrf proof components
* @param seed seed used to generate the vrf output
*
* Throws if proof is invalid, otherwise:
* @return output i.e., the random output implied by the proof
* ***************************************************************************
*/
function randomValueFromVRFProof(Proof memory proof, uint256 seed) internal view returns (uint256 output) {
verifyVRFProof(
proof.pk,
proof.gamma,
proof.c,
proof.s,
seed,
proof.uWitness,
proof.cGammaWitness,
proof.sHashWitness,
proof.zInv
);
output = uint256(keccak256(abi.encode(VRF_RANDOM_OUTPUT_HASH_PREFIX, proof.gamma)));
}
}
// 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;
interface OwnableInterface {
function owner() external returns (address);
function transferOwnership(address recipient) external;
function acceptOwnership() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../dev/VRFCoordinatorV2.sol";
contract VRFCoordinatorV2TestHelper is VRFCoordinatorV2 {
uint96 s_paymentAmount;
uint256 s_gasStart;
constructor(
address link,
address blockhashStore,
address linkEthFeed
)
// solhint-disable-next-line no-empty-blocks
VRFCoordinatorV2(link, blockhashStore, linkEthFeed)
{
/* empty */
}
function calculatePaymentAmountTest(
uint256 gasAfterPaymentCalculation,
uint32 fulfillmentFlatFeeLinkPPM,
uint256 weiPerUnitGas
) external {
s_paymentAmount = calculatePaymentAmount(
gasleft(),
gasAfterPaymentCalculation,
fulfillmentFlatFeeLinkPPM,
weiPerUnitGas
);
}
function getPaymentAmount() public view returns (uint96) {
return s_paymentAmount;
}
function getGasStart() public view returns (uint256) {
return s_gasStart;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./ConfirmedOwner.sol";
import "./interfaces/AggregatorValidatorInterface.sol";
import "./interfaces/TypeAndVersionInterface.sol";
contract ValidatorProxy is AggregatorValidatorInterface, TypeAndVersionInterface, ConfirmedOwner {
/// @notice Uses a single storage slot to store the current address
struct AggregatorConfiguration {
address target;
bool hasNewProposal;
}
struct ValidatorConfiguration {
AggregatorValidatorInterface target;
bool hasNewProposal;
}
// Configuration for the current aggregator
AggregatorConfiguration private s_currentAggregator;
// Proposed aggregator address
address private s_proposedAggregator;
// Configuration for the current validator
ValidatorConfiguration private s_currentValidator;
// Proposed validator address
AggregatorValidatorInterface private s_proposedValidator;
event AggregatorProposed(address indexed aggregator);
event AggregatorUpgraded(address indexed previous, address indexed current);
event ValidatorProposed(AggregatorValidatorInterface indexed validator);
event ValidatorUpgraded(AggregatorValidatorInterface indexed previous, AggregatorValidatorInterface indexed current);
/// @notice The proposed aggregator called validate, but the call was not passed on to any validators
event ProposedAggregatorValidateCall(
address indexed proposed,
uint256 previousRoundId,
int256 previousAnswer,
uint256 currentRoundId,
int256 currentAnswer
);
/**
* @notice Construct the ValidatorProxy with an aggregator and a validator
* @param aggregator address
* @param validator address
*/
constructor(address aggregator, AggregatorValidatorInterface validator) ConfirmedOwner(msg.sender) {
s_currentAggregator = AggregatorConfiguration({target: aggregator, hasNewProposal: false});
s_currentValidator = ValidatorConfiguration({target: validator, hasNewProposal: false});
}
/**
* @notice Validate a transmission
* @dev Must be called by either the `s_currentAggregator.target`, or the `s_proposedAggregator`.
* If called by the `s_currentAggregator.target` this function passes the call on to the `s_currentValidator.target`
* and the `s_proposedValidator`, if it is set.
* If called by the `s_proposedAggregator` this function emits a `ProposedAggregatorValidateCall` to signal that
* the call was received.
* @dev To guard against external `validate` calls reverting, we use raw calls here.
* We favour `call` over try-catch to ensure that failures are avoided even if the validator address is incorrectly
* set as a non-contract address.
* @dev If the `aggregator` and `validator` are the same contract or collude, this could exhibit reentrancy behavior.
* However, since that contract would have to be explicitly written for reentrancy and that the `owner` would have
* to configure this contract to use that malicious contract, we refrain from using mutex or check here.
* @dev This does not perform any checks on any roundId, so it is possible that a validator receive different reports
* for the same roundId at different points in time. Validator implementations should be aware of this.
* @param previousRoundId uint256
* @param previousAnswer int256
* @param currentRoundId uint256
* @param currentAnswer int256
* @return bool
*/
function validate(
uint256 previousRoundId,
int256 previousAnswer,
uint256 currentRoundId,
int256 currentAnswer
) external override returns (bool) {
address currentAggregator = s_currentAggregator.target;
if (msg.sender != currentAggregator) {
address proposedAggregator = s_proposedAggregator;
require(msg.sender == proposedAggregator, "Not a configured aggregator");
// If the aggregator is still in proposed state, emit an event and don't push to any validator.
// This is to confirm that `validate` is being called prior to upgrade.
emit ProposedAggregatorValidateCall(
proposedAggregator,
previousRoundId,
previousAnswer,
currentRoundId,
currentAnswer
);
return true;
}
// Send the validate call to the current validator
ValidatorConfiguration memory currentValidator = s_currentValidator;
address currentValidatorAddress = address(currentValidator.target);
require(currentValidatorAddress != address(0), "No validator set");
currentValidatorAddress.call(
abi.encodeWithSelector(
AggregatorValidatorInterface.validate.selector,
previousRoundId,
previousAnswer,
currentRoundId,
currentAnswer
)
);
// If there is a new proposed validator, send the validate call to that validator also
if (currentValidator.hasNewProposal) {
address(s_proposedValidator).call(
abi.encodeWithSelector(
AggregatorValidatorInterface.validate.selector,
previousRoundId,
previousAnswer,
currentRoundId,
currentAnswer
)
);
}
return true;
}
/** AGGREGATOR CONFIGURATION FUNCTIONS **/
/**
* @notice Propose an aggregator
* @dev A zero address can be used to unset the proposed aggregator. Only owner can call.
* @param proposed address
*/
function proposeNewAggregator(address proposed) external onlyOwner {
require(s_proposedAggregator != proposed && s_currentAggregator.target != proposed, "Invalid proposal");
s_proposedAggregator = proposed;
// If proposed is zero address, hasNewProposal = false
s_currentAggregator.hasNewProposal = (proposed != address(0));
emit AggregatorProposed(proposed);
}
/**
* @notice Upgrade the aggregator by setting the current aggregator as the proposed aggregator.
* @dev Must have a proposed aggregator. Only owner can call.
*/
function upgradeAggregator() external onlyOwner {
// Get configuration in memory
AggregatorConfiguration memory current = s_currentAggregator;
address previous = current.target;
address proposed = s_proposedAggregator;
// Perform the upgrade
require(current.hasNewProposal, "No proposal");
s_currentAggregator = AggregatorConfiguration({target: proposed, hasNewProposal: false});
delete s_proposedAggregator;
emit AggregatorUpgraded(previous, proposed);
}
/**
* @notice Get aggregator details
* @return current address
* @return hasProposal bool
* @return proposed address
*/
function getAggregators()
external
view
returns (
address current,
bool hasProposal,
address proposed
)
{
current = s_currentAggregator.target;
hasProposal = s_currentAggregator.hasNewProposal;
proposed = s_proposedAggregator;
}
/** VALIDATOR CONFIGURATION FUNCTIONS **/
/**
* @notice Propose an validator
* @dev A zero address can be used to unset the proposed validator. Only owner can call.
* @param proposed address
*/
function proposeNewValidator(AggregatorValidatorInterface proposed) external onlyOwner {
require(s_proposedValidator != proposed && s_currentValidator.target != proposed, "Invalid proposal");
s_proposedValidator = proposed;
// If proposed is zero address, hasNewProposal = false
s_currentValidator.hasNewProposal = (address(proposed) != address(0));
emit ValidatorProposed(proposed);
}
/**
* @notice Upgrade the validator by setting the current validator as the proposed validator.
* @dev Must have a proposed validator. Only owner can call.
*/
function upgradeValidator() external onlyOwner {
// Get configuration in memory
ValidatorConfiguration memory current = s_currentValidator;
AggregatorValidatorInterface previous = current.target;
AggregatorValidatorInterface proposed = s_proposedValidator;
// Perform the upgrade
require(current.hasNewProposal, "No proposal");
s_currentValidator = ValidatorConfiguration({target: proposed, hasNewProposal: false});
delete s_proposedValidator;
emit ValidatorUpgraded(previous, proposed);
}
/**
* @notice Get validator details
* @return current address
* @return hasProposal bool
* @return proposed address
*/
function getValidators()
external
view
returns (
AggregatorValidatorInterface current,
bool hasProposal,
AggregatorValidatorInterface proposed
)
{
current = s_currentValidator.target;
hasProposal = s_currentValidator.hasNewProposal;
proposed = s_proposedValidator;
}
/**
* @notice The type and version of this contract
* @return Type and version string
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "ValidatorProxy 1.0.0";
}
}
// 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;
import "../interfaces/TypeAndVersionInterface.sol";
import "../interfaces/AggregatorValidatorInterface.sol";
import "../interfaces/AccessControllerInterface.sol";
import "../SimpleWriteAccessController.sol";
/* ./dev dependencies - to be moved from ./dev after audit */
import "./interfaces/FlagsInterface.sol";
import "./interfaces/ForwarderInterface.sol";
import "./vendor/@eth-optimism/contracts/0.4.7/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_CrossDomainMessenger.sol";
/**
* @title OptimismValidator - makes xDomain L2 Flags contract call (using L2 xDomain Forwarder contract)
* @notice Allows to raise and lower Flags on the Optimism L2 network through L1 bridge
* - The internal AccessController controls the access of the validate method
*/
contract OptimismValidator is TypeAndVersionInterface, AggregatorValidatorInterface, SimpleWriteAccessController {
/// @dev Follows: https://eips.ethereum.org/EIPS/eip-1967
address public constant FLAG_OPTIMISM_SEQ_OFFLINE =
address(bytes20(bytes32(uint256(keccak256("chainlink.flags.optimism-seq-offline")) - 1)));
// Encode underlying Flags call/s
bytes private constant CALL_RAISE_FLAG =
abi.encodeWithSelector(FlagsInterface.raiseFlag.selector, FLAG_OPTIMISM_SEQ_OFFLINE);
bytes private constant CALL_LOWER_FLAG =
abi.encodeWithSelector(FlagsInterface.lowerFlag.selector, FLAG_OPTIMISM_SEQ_OFFLINE);
uint32 private constant CALL_GAS_LIMIT = 1_200_000;
int256 private constant ANSWER_SEQ_OFFLINE = 1;
address public immutable CROSS_DOMAIN_MESSENGER;
address public immutable L2_CROSS_DOMAIN_FORWARDER;
address public immutable L2_FLAGS;
/**
* @param crossDomainMessengerAddr address the xDomain bridge messenger (Optimism bridge L1) contract address
* @param l2CrossDomainForwarderAddr the L2 Forwarder contract address
* @param l2FlagsAddr the L2 Flags contract address
*/
constructor(
address crossDomainMessengerAddr,
address l2CrossDomainForwarderAddr,
address l2FlagsAddr
) {
require(crossDomainMessengerAddr != address(0), "Invalid xDomain Messenger address");
require(l2CrossDomainForwarderAddr != address(0), "Invalid L2 xDomain Forwarder address");
require(l2FlagsAddr != address(0), "Invalid L2 Flags address");
CROSS_DOMAIN_MESSENGER = crossDomainMessengerAddr;
L2_CROSS_DOMAIN_FORWARDER = l2CrossDomainForwarderAddr;
L2_FLAGS = l2FlagsAddr;
}
/**
* @notice versions:
*
* - OptimismValidator 0.1.0: initial release
*
* @inheritdoc TypeAndVersionInterface
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "OptimismValidator 0.1.0";
}
/**
* @notice validate method sends an xDomain L2 tx to update Flags contract, in case of change from `previousAnswer`.
* @dev A message is sent via the Optimism CrossDomainMessenger L1 contract. The "payment" for L2 execution happens on L1,
* using the gas attached to this tx (some extra gas is burned by the Optimism bridge to avoid DoS attacks).
* This method is accessed controlled.
* @param previousAnswer previous aggregator answer
* @param currentAnswer new aggregator answer - value of 1 considers the service offline.
*/
function validate(
uint256, /* previousRoundId */
int256 previousAnswer,
uint256, /* currentRoundId */
int256 currentAnswer
) external override checkAccess returns (bool) {
// Avoids resending to L2 the same tx on every call
if (previousAnswer == currentAnswer) {
return true; // noop
}
// Encode the Forwarder call
bytes4 selector = ForwarderInterface.forward.selector;
address target = L2_FLAGS;
// Choose and encode the underlying Flags call
bytes memory data = currentAnswer == ANSWER_SEQ_OFFLINE ? CALL_RAISE_FLAG : CALL_LOWER_FLAG;
bytes memory message = abi.encodeWithSelector(selector, target, data);
// Make the xDomain call
iOVM_CrossDomainMessenger(CROSS_DOMAIN_MESSENGER).sendMessage(L2_CROSS_DOMAIN_FORWARDER, message, CALL_GAS_LIMIT);
// return success
return true;
}
}
// 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;
import "./ConfirmedOwner.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, ConfirmedOwner {
bool public checkEnabled;
mapping(address => bool) internal accessList;
event AddedAccess(address user);
event RemovedAccess(address user);
event CheckAccessEnabled();
event CheckAccessDisabled();
constructor() ConfirmedOwner(msg.sender) {
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.6;
interface FlagsInterface {
function getFlag(address) external view returns (bool);
function getFlags(address[] calldata) external view returns (bool[] memory);
function raiseFlag(address) external;
function raiseFlags(address[] calldata) external;
function lowerFlag(address) external;
function lowerFlags(address[] calldata) external;
function setRaisingAccessController(address) external;
function setLoweringAccessController(address) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title ForwarderInterface - forwards a call to a target, under some conditions
interface ForwarderInterface {
/**
* @notice forward calls the `target` with `data`
* @param target contract address to be called
* @param data to send to target contract
*/
function forward(address target, bytes memory data) external;
}
pragma solidity >=0.7.6 <0.9.0;
/**
* @title iOVM_CrossDomainMessenger
*/
interface iOVM_CrossDomainMessenger {
/**********
* Events *
**********/
event SentMessage(bytes message);
event RelayedMessage(bytes32 msgHash);
event FailedRelayedMessage(bytes32 msgHash);
/*************
* Variables *
*************/
function xDomainMessageSender() external view returns (address);
/********************
* Public Functions *
********************/
/**
* Sends a cross domain message to the target messenger.
* @param _target Target contract address.
* @param _message Message to send to the target.
* @param _gasLimit Gas limit for the provided message.
*/
function sendMessage(
address _target,
bytes calldata _message,
uint32 _gasLimit
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/TypeAndVersionInterface.sol";
/* ./dev dependencies - to be moved from ./dev after audit */
import "./CrossDomainForwarder.sol";
import "./vendor/@eth-optimism/contracts/0.4.7/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_CrossDomainMessenger.sol";
/**
* @title OptimismCrossDomainForwarder - L1 xDomain account representation
* @notice L2 Contract which receives messages from a specific L1 address and transparently forwards them to the destination.
* @dev Any other L2 contract which uses this contract's address as a privileged position,
* can be considered to be owned by the `l1Owner`
*/
contract OptimismCrossDomainForwarder is TypeAndVersionInterface, CrossDomainForwarder {
// OVM_L2CrossDomainMessenger is a precompile usually deployed to 0x4200000000000000000000000000000000000007
address private immutable OVM_CROSS_DOMAIN_MESSENGER;
/**
* @notice creates a new Optimism xDomain Forwarder contract
* @param crossDomainMessengerAddr the xDomain bridge messenger (Optimism bridge L2) contract address
* @param l1OwnerAddr the L1 owner address that will be allowed to call the forward fn
*/
constructor(address crossDomainMessengerAddr, address l1OwnerAddr) CrossDomainForwarder(l1OwnerAddr) {
require(crossDomainMessengerAddr != address(0), "Invalid xDomain Messenger address");
OVM_CROSS_DOMAIN_MESSENGER = crossDomainMessengerAddr;
}
/**
* @notice versions:
*
* - OptimismCrossDomainForwarder 0.1.0: initial release
*
* @inheritdoc TypeAndVersionInterface
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "OptimismCrossDomainForwarder 0.1.0";
}
/**
* @dev forwarded only if L2 Messenger calls with `xDomainMessageSender` beeing the L1 owner address
* @inheritdoc ForwarderInterface
*/
function forward(address target, bytes memory data) external override {
// 1. The call MUST come from the L1 Messenger
require(msg.sender == OVM_CROSS_DOMAIN_MESSENGER, "Sender is not the L2 messenger");
// 2. The L1 Messenger's caller MUST be the L1 Owner
require(
iOVM_CrossDomainMessenger(OVM_CROSS_DOMAIN_MESSENGER).xDomainMessageSender() == l1Owner(),
"xDomain sender is not the L1 owner"
);
// 3. Make the external call
(bool success, bytes memory res) = target.call(data);
require(success, string(abi.encode("xDomain call failed:", res)));
}
/**
* @notice This is always the address of the OVM_L2CrossDomainMessenger contract
* @inheritdoc CrossDomainForwarder
*/
function crossDomainMessenger() public view virtual override returns (address) {
return OVM_CROSS_DOMAIN_MESSENGER;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../ConfirmedOwner.sol";
import "./interfaces/ForwarderInterface.sol";
/**
* @title CrossDomainForwarder - L1 xDomain account representation
* @notice L2 Contract which receives messages from a specific L1 address and transparently forwards them to the destination.
* @dev Any other L2 contract which uses this contract's address as a privileged position,
* can be considered to be owned by the `l1Owner`
*/
abstract contract CrossDomainForwarder is ForwarderInterface, ConfirmedOwner {
address private s_l1Owner;
event L1OwnershipTransferred(address indexed from, address indexed to);
/**
* @notice creates a new xDomain Forwarder contract
* @dev Forwarding can be disabled by setting the L1 owner as `address(0)`.
* @param l1OwnerAddr the L1 owner address that will be allowed to call the forward fn
*/
constructor(address l1OwnerAddr) ConfirmedOwner(msg.sender) {
_setL1Owner(l1OwnerAddr);
}
/// @return xDomain messenger address (L2 `msg.sender`)
function crossDomainMessenger() public view virtual returns (address);
/// @return L1 owner address
function l1Owner() public view virtual returns (address) {
return s_l1Owner;
}
/**
* @notice transfer ownership of this account to a new L1 owner
* @dev Forwarding can be disabled by setting the L1 owner as `address(0)`. Accessible only by owner.
* @param to new L1 owner that will be allowed to call the forward fn
*/
function transferL1Ownership(address to) external virtual onlyOwner {
_setL1Owner(to);
}
/// @notice internal method that stores the L1 owner
function _setL1Owner(address to) internal {
address from = s_l1Owner;
if (from != to) {
s_l1Owner = to;
emit L1OwnershipTransferred(from, to);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/AggregatorValidatorInterface.sol";
import "../interfaces/TypeAndVersionInterface.sol";
import "../interfaces/AccessControllerInterface.sol";
import "../interfaces/AggregatorV3Interface.sol";
import "../SimpleWriteAccessController.sol";
/* ./dev dependencies - to be moved from ./dev after audit */
import "./interfaces/ForwarderInterface.sol";
import "./interfaces/FlagsInterface.sol";
import "./vendor/arb-bridge-eth/v0.8.0-custom/contracts/bridge/interfaces/IInbox.sol";
import "./vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol";
import "./vendor/arb-os/e8d9696f21/contracts/arbos/builtin/ArbSys.sol";
import "./vendor/openzeppelin-solidity/v4.3.1/contracts/utils/Address.sol";
/**
* @title ArbitrumValidator - makes xDomain L2 Flags contract call (using L2 xDomain Forwarder contract)
* @notice Allows to raise and lower Flags on the Arbitrum L2 network through L1 bridge
* - The internal AccessController controls the access of the validate method
* - Gas configuration is controlled by a configurable external SimpleWriteAccessController
* - Funds on the contract are managed by the owner
*/
contract ArbitrumValidator is TypeAndVersionInterface, AggregatorValidatorInterface, SimpleWriteAccessController {
enum PaymentStrategy {
L1,
L2
}
// Config for L1 -> L2 Arbitrum retryable ticket message
struct GasConfig {
uint256 maxGas;
uint256 gasPriceBid;
address gasPriceL1FeedAddr;
}
/// @dev Precompiled contract that exists in every Arbitrum chain at address(100). Exposes a variety of system-level functionality.
address constant ARBSYS_ADDR = address(0x0000000000000000000000000000000000000064);
/// @dev Follows: https://eips.ethereum.org/EIPS/eip-1967
address public constant FLAG_ARBITRUM_SEQ_OFFLINE =
address(bytes20(bytes32(uint256(keccak256("chainlink.flags.arbitrum-seq-offline")) - 1)));
// Encode underlying Flags call/s
bytes private constant CALL_RAISE_FLAG =
abi.encodeWithSelector(FlagsInterface.raiseFlag.selector, FLAG_ARBITRUM_SEQ_OFFLINE);
bytes private constant CALL_LOWER_FLAG =
abi.encodeWithSelector(FlagsInterface.lowerFlag.selector, FLAG_ARBITRUM_SEQ_OFFLINE);
int256 private constant ANSWER_SEQ_OFFLINE = 1;
address public immutable CROSS_DOMAIN_MESSENGER;
address public immutable L2_CROSS_DOMAIN_FORWARDER;
address public immutable L2_FLAGS;
// L2 xDomain alias address of this contract
address public immutable L2_ALIAS = AddressAliasHelper.applyL1ToL2Alias(address(this));
PaymentStrategy private s_paymentStrategy;
GasConfig private s_gasConfig;
AccessControllerInterface private s_configAC;
/**
* @notice emitted when a new payment strategy is set
* @param paymentStrategy strategy describing how the contract pays for xDomain calls
*/
event PaymentStrategySet(PaymentStrategy indexed paymentStrategy);
/**
* @notice emitted when a new gas configuration is set
* @param maxGas gas limit for immediate L2 execution attempt.
* @param gasPriceBid maximum L2 gas price to pay
* @param gasPriceL1FeedAddr address of the L1 gas price feed (used to approximate Arbitrum retryable ticket submission cost)
*/
event GasConfigSet(uint256 maxGas, uint256 gasPriceBid, address indexed gasPriceL1FeedAddr);
/**
* @notice emitted when a new gas access-control contract is set
* @param previous the address prior to the current setting
* @param current the address of the new access-control contract
*/
event ConfigACSet(address indexed previous, address indexed current);
/**
* @notice emitted when a new ETH withdrawal from L2 was requested
* @param id unique id of the published retryable transaction (keccak256(requestID, uint(0))
* @param amount of funds to withdraw
*/
event L2WithdrawalRequested(uint256 indexed id, uint256 amount, address indexed refundAddr);
/**
* @param crossDomainMessengerAddr address the xDomain bridge messenger (Arbitrum Inbox L1) contract address
* @param l2CrossDomainForwarderAddr the L2 Forwarder contract address
* @param l2FlagsAddr the L2 Flags contract address
* @param configACAddr address of the access controller for managing gas price on Arbitrum
* @param maxGas gas limit for immediate L2 execution attempt. A value around 1M should be sufficient
* @param gasPriceBid maximum L2 gas price to pay
* @param gasPriceL1FeedAddr address of the L1 gas price feed (used to approximate Arbitrum retryable ticket submission cost)
* @param paymentStrategy strategy describing how the contract pays for xDomain calls
*/
constructor(
address crossDomainMessengerAddr,
address l2CrossDomainForwarderAddr,
address l2FlagsAddr,
address configACAddr,
uint256 maxGas,
uint256 gasPriceBid,
address gasPriceL1FeedAddr,
PaymentStrategy paymentStrategy
) {
require(crossDomainMessengerAddr != address(0), "Invalid xDomain Messenger address");
require(l2CrossDomainForwarderAddr != address(0), "Invalid L2 xDomain Forwarder address");
require(l2FlagsAddr != address(0), "Invalid Flags contract address");
CROSS_DOMAIN_MESSENGER = crossDomainMessengerAddr;
L2_CROSS_DOMAIN_FORWARDER = l2CrossDomainForwarderAddr;
L2_FLAGS = l2FlagsAddr;
// Additional L2 payment configuration
_setConfigAC(configACAddr);
_setGasConfig(maxGas, gasPriceBid, gasPriceL1FeedAddr);
_setPaymentStrategy(paymentStrategy);
}
/**
* @notice versions:
*
* - ArbitrumValidator 0.1.0: initial release
* - ArbitrumValidator 0.2.0: critical Arbitrum network update
* - xDomain `msg.sender` backwards incompatible change (now an alias address)
* - new `withdrawFundsFromL2` fn that withdraws from L2 xDomain alias address
* - approximation of `maxSubmissionCost` using a L1 gas price feed
*
* @inheritdoc TypeAndVersionInterface
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "ArbitrumValidator 0.2.0";
}
/// @return stored PaymentStrategy
function paymentStrategy() external view virtual returns (PaymentStrategy) {
return s_paymentStrategy;
}
/// @return stored GasConfig
function gasConfig() external view virtual returns (GasConfig memory) {
return s_gasConfig;
}
/// @return config AccessControllerInterface contract address
function configAC() external view virtual returns (address) {
return address(s_configAC);
}
/**
* @notice makes this contract payable
* @dev receives funds:
* - to use them (if configured) to pay for L2 execution on L1
* - when withdrawing funds from L2 xDomain alias address (pay for L2 execution on L2)
*/
receive() external payable {}
/**
* @notice withdraws all funds available in this contract to the msg.sender
* @dev only owner can call this
*/
function withdrawFunds() external onlyOwner {
address payable recipient = payable(msg.sender);
uint256 amount = address(this).balance;
Address.sendValue(recipient, amount);
}
/**
* @notice withdraws all funds available in this contract to the address specified
* @dev only owner can call this
* @param recipient address where to send the funds
*/
function withdrawFundsTo(address payable recipient) external onlyOwner {
uint256 amount = address(this).balance;
Address.sendValue(recipient, amount);
}
/**
* @notice withdraws funds from L2 xDomain alias address (representing this L1 contract)
* @dev only owner can call this
* @param amount of funds to withdraws
* @param refundAddr address where gas excess on L2 will be sent
* WARNING: `refundAddr` is not aliased! Make sure you can recover the refunded funds on L2.
* @return id unique id of the published retryable transaction (keccak256(requestID, uint(0))
*/
function withdrawFundsFromL2(uint256 amount, address refundAddr) external onlyOwner returns (uint256 id) {
// Build an xDomain message to trigger the ArbSys precompile, which will create a L2 -> L1 tx transferring `amount`
bytes memory message = abi.encodeWithSelector(ArbSys.withdrawEth.selector, address(this));
// Make the xDomain call
// NOTICE: We approximate the max submission cost of sending a retryable tx with specific calldata length.
uint256 maxSubmissionCost = _approximateMaxSubmissionCost(message.length);
uint256 maxGas = 120_000; // static `maxGas` for L2 -> L1 transfer
uint256 gasPriceBid = s_gasConfig.gasPriceBid;
uint256 l1PaymentValue = s_paymentStrategy == PaymentStrategy.L1
? _maxRetryableTicketCost(maxSubmissionCost, maxGas, gasPriceBid)
: 0;
// NOTICE: In the case of PaymentStrategy.L2 the L2 xDomain alias address needs to be funded, as it will be paying the fee.
id = IInbox(CROSS_DOMAIN_MESSENGER).createRetryableTicketNoRefundAliasRewrite{value: l1PaymentValue}(
ARBSYS_ADDR, // target
amount, // L2 call value (requested)
maxSubmissionCost,
refundAddr, // excessFeeRefundAddress
refundAddr, // callValueRefundAddress
maxGas,
gasPriceBid,
message
);
emit L2WithdrawalRequested(id, amount, refundAddr);
}
/**
* @notice sets config AccessControllerInterface contract
* @dev only owner can call this
* @param accessController new AccessControllerInterface contract address
*/
function setConfigAC(address accessController) external onlyOwner {
_setConfigAC(accessController);
}
/**
* @notice sets Arbitrum gas configuration
* @dev access control provided by `configAC`
* @param maxGas gas limit for immediate L2 execution attempt. A value around 1M should be sufficient
* @param gasPriceBid maximum L2 gas price to pay
* @param gasPriceL1FeedAddr address of the L1 gas price feed (used to approximate Arbitrum retryable ticket submission cost)
*/
function setGasConfig(
uint256 maxGas,
uint256 gasPriceBid,
address gasPriceL1FeedAddr
) external onlyOwnerOrConfigAccess {
_setGasConfig(maxGas, gasPriceBid, gasPriceL1FeedAddr);
}
/**
* @notice sets the payment strategy
* @dev access control provided by `configAC`
* @param paymentStrategy strategy describing how the contract pays for xDomain calls
*/
function setPaymentStrategy(PaymentStrategy paymentStrategy) external onlyOwnerOrConfigAccess {
_setPaymentStrategy(paymentStrategy);
}
/**
* @notice validate method sends an xDomain L2 tx to update Flags contract, in case of change from `previousAnswer`.
* @dev A retryable ticket is created on the Arbitrum L1 Inbox contract. The tx gas fee can be paid from this
* contract providing a value, or if no L1 value is sent with the xDomain message the gas will be paid by
* the L2 xDomain alias account (generated from `address(this)`). This method is accessed controlled.
* @param previousAnswer previous aggregator answer
* @param currentAnswer new aggregator answer - value of 1 considers the service offline.
*/
function validate(
uint256, /* previousRoundId */
int256 previousAnswer,
uint256, /* currentRoundId */
int256 currentAnswer
) external override checkAccess returns (bool) {
// Avoids resending to L2 the same tx on every call
if (previousAnswer == currentAnswer) {
return true;
}
// Excess gas on L2 will be sent to the L2 xDomain alias address of this contract
address refundAddr = L2_ALIAS;
// Encode the Forwarder call
bytes4 selector = ForwarderInterface.forward.selector;
address target = L2_FLAGS;
// Choose and encode the underlying Flags call
bytes memory data = currentAnswer == ANSWER_SEQ_OFFLINE ? CALL_RAISE_FLAG : CALL_LOWER_FLAG;
bytes memory message = abi.encodeWithSelector(selector, target, data);
// Make the xDomain call
// NOTICE: We approximate the max submission cost of sending a retryable tx with specific calldata length.
uint256 maxSubmissionCost = _approximateMaxSubmissionCost(message.length);
uint256 maxGas = s_gasConfig.maxGas;
uint256 gasPriceBid = s_gasConfig.gasPriceBid;
uint256 l1PaymentValue = s_paymentStrategy == PaymentStrategy.L1
? _maxRetryableTicketCost(maxSubmissionCost, maxGas, gasPriceBid)
: 0;
// NOTICE: In the case of PaymentStrategy.L2 the L2 xDomain alias address needs to be funded, as it will be paying the fee.
// We also ignore the returned msg number, that can be queried via the `InboxMessageDelivered` event.
IInbox(CROSS_DOMAIN_MESSENGER).createRetryableTicketNoRefundAliasRewrite{value: l1PaymentValue}(
L2_CROSS_DOMAIN_FORWARDER, // target
0, // L2 call value
maxSubmissionCost,
refundAddr, // excessFeeRefundAddress
refundAddr, // callValueRefundAddress
maxGas,
gasPriceBid,
message
);
// return success
return true;
}
/// @notice internal method that stores the payment strategy
function _setPaymentStrategy(PaymentStrategy paymentStrategy) internal {
s_paymentStrategy = paymentStrategy;
emit PaymentStrategySet(paymentStrategy);
}
/// @notice internal method that stores the gas configuration
function _setGasConfig(
uint256 maxGas,
uint256 gasPriceBid,
address gasPriceL1FeedAddr
) internal {
require(maxGas > 0, "Max gas is zero");
require(gasPriceBid > 0, "Gas price bid is zero");
require(gasPriceL1FeedAddr != address(0), "Gas price Aggregator is zero address");
s_gasConfig = GasConfig(maxGas, gasPriceBid, gasPriceL1FeedAddr);
emit GasConfigSet(maxGas, gasPriceBid, gasPriceL1FeedAddr);
}
/// @notice Internal method that stores the configuration access controller
function _setConfigAC(address accessController) internal {
address previousAccessController = address(s_configAC);
if (accessController != previousAccessController) {
s_configAC = AccessControllerInterface(accessController);
emit ConfigACSet(previousAccessController, accessController);
}
}
/**
* @notice Internal method that approximates the `maxSubmissionCost` (using the L1 gas price feed)
* @dev On L2 this info is available via `ArbRetryableTx.getSubmissionPrice`.
* @param calldataSizeInBytes xDomain message size in bytes
*/
function _approximateMaxSubmissionCost(uint256 calldataSizeInBytes) internal view returns (uint256) {
(, int256 l1GasPriceInWei, , , ) = AggregatorV3Interface(s_gasConfig.gasPriceL1FeedAddr).latestRoundData();
uint256 l1GasPriceEstimate = uint256(l1GasPriceInWei) * 3; // add 200% buffer (price volatility error margin)
return (l1GasPriceEstimate * calldataSizeInBytes) / 256 + l1GasPriceEstimate;
}
/// @notice Internal helper method that calculates the total cost of the xDomain retryable ticket call
function _maxRetryableTicketCost(
uint256 maxSubmissionCost,
uint256 maxGas,
uint256 gasPriceBid
) internal pure returns (uint256) {
return maxSubmissionCost + maxGas * gasPriceBid;
}
/// @dev reverts if the caller does not have access to change the configuration
modifier onlyOwnerOrConfigAccess() {
require(
msg.sender == owner() || (address(s_configAC) != address(0) && s_configAC.hasAccess(msg.sender, msg.data)),
"No access"
);
_;
}
}
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// NOTICE: pragma change from original (^0.6.11)
pragma solidity ^0.8.0;
import "./IBridge.sol";
import "./IMessageProvider.sol";
interface IInbox is IMessageProvider {
function sendL2Message(bytes calldata messageData) external returns (uint256);
function sendUnsignedTransaction(
uint256 maxGas,
uint256 gasPriceBid,
uint256 nonce,
address destAddr,
uint256 amount,
bytes calldata data
) external returns (uint256);
function sendContractTransaction(
uint256 maxGas,
uint256 gasPriceBid,
address destAddr,
uint256 amount,
bytes calldata data
) external returns (uint256);
function sendL1FundedUnsignedTransaction(
uint256 maxGas,
uint256 gasPriceBid,
uint256 nonce,
address destAddr,
bytes calldata data
) external payable returns (uint256);
function sendL1FundedContractTransaction(
uint256 maxGas,
uint256 gasPriceBid,
address destAddr,
bytes calldata data
) external payable returns (uint256);
function createRetryableTicketNoRefundAliasRewrite(
address destAddr,
uint256 arbTxCallValue,
uint256 maxSubmissionCost,
address submissionRefundAddress,
address valueRefundAddress,
uint256 maxGas,
uint256 gasPriceBid,
bytes calldata data
) external payable returns (uint256);
function createRetryableTicket(
address destAddr,
uint256 arbTxCallValue,
uint256 maxSubmissionCost,
address submissionRefundAddress,
address valueRefundAddress,
uint256 maxGas,
uint256 gasPriceBid,
bytes calldata data
) external payable returns (uint256);
function depositEth(address destAddr) external payable returns (uint256);
function depositEthRetryable(
address destAddr,
uint256 maxSubmissionCost,
uint256 maxGas,
uint256 maxGasPrice
) external payable returns (uint256);
function bridge() external view returns (IBridge);
}
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2019-2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// NOTICE: pragma change from original (^0.6.11)
pragma solidity ^0.8.0;
library AddressAliasHelper {
uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);
/// @notice Utility function that converts the msg.sender viewed in the L2 to the
/// address in the L1 that submitted a tx to the inbox
/// @param l1Address L2 address as viewed in msg.sender
/// @return l2Address the address in the L1 that triggered the tx to L2
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
unchecked {
l2Address = address(uint160(l1Address) + offset);
}
}
/// @notice Utility function that converts the msg.sender viewed in the L2 to the
/// address in the L1 that submitted a tx to the inbox
/// @param l2Address L2 address as viewed in msg.sender
/// @return l1Address the address in the L1 that triggered the tx to L2
function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) {
unchecked {
l1Address = address(uint160(l2Address) - offset);
}
}
}
// NOTICE: pragma change from original (>=0.4.21 <0.7.0)
pragma solidity >=0.4.21 <0.9.0;
/**
* @title Precompiled contract that exists in every Arbitrum chain at address(100), 0x0000000000000000000000000000000000000064. Exposes a variety of system-level functionality.
*/
interface ArbSys {
/**
* @notice Get internal version number identifying an ArbOS build
* @return version number as int
*/
function arbOSVersion() external pure returns (uint256);
function arbChainID() external view returns (uint256);
/**
* @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0)
* @return block number as int
*/
function arbBlockNumber() external view returns (uint256);
/**
* @notice Send given amount of Eth to dest from sender.
* This is a convenience function, which is equivalent to calling sendTxToL1 with empty calldataForL1.
* @param destination recipient address on L1
* @return unique identifier for this L2-to-L1 transaction.
*/
function withdrawEth(address destination) external payable returns (uint256);
/**
* @notice Send a transaction to L1
* @param destination recipient address on L1
* @param calldataForL1 (optional) calldata for L1 contract call
* @return a unique identifier for this L2-to-L1 transaction.
*/
function sendTxToL1(address destination, bytes calldata calldataForL1) external payable returns (uint256);
/**
* @notice get the number of transactions issued by the given external account or the account sequence number of the given contract
* @param account target account
* @return the number of transactions issued by the given external account or the account sequence number of the given contract
*/
function getTransactionCount(address account) external view returns (uint256);
/**
* @notice get the value of target L2 storage slot
* This function is only callable from address 0 to prevent contracts from being able to call it
* @param account target account
* @param index target index of storage slot
* @return stotage value for the given account at the given index
*/
function getStorageAt(address account, uint256 index) external view returns (uint256);
/**
* @notice check if current call is coming from l1
* @return true if the caller of this was called directly from L1
*/
function isTopLevelCall() external view returns (bool);
event L2ToL1Transaction(
address caller,
address indexed destination,
uint256 indexed uniqueId,
uint256 indexed batchNumber,
uint256 indexInBatch,
uint256 arbBlockNum,
uint256 ethBlockNum,
uint256 timestamp,
uint256 callvalue,
bytes data
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @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
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 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://diligence.consensys.net/posts/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.5.11/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 functionCall(target, data, "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");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(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) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(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) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason 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 {
// 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
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// NOTICE: pragma change from original (^0.6.11)
pragma solidity ^0.8.0;
interface IBridge {
event MessageDelivered(
uint256 indexed messageIndex,
bytes32 indexed beforeInboxAcc,
address inbox,
uint8 kind,
address sender,
bytes32 messageDataHash
);
function deliverMessageToInbox(
uint8 kind,
address sender,
bytes32 messageDataHash
) external payable returns (uint256);
function executeCall(
address destAddr,
uint256 amount,
bytes calldata data
) external returns (bool success, bytes memory returnData);
// These are only callable by the admin
function setInbox(address inbox, bool enabled) external;
function setOutbox(address inbox, bool enabled) external;
// View functions
function activeOutbox() external view returns (address);
function allowedInboxes(address inbox) external view returns (bool);
function allowedOutboxes(address outbox) external view returns (bool);
function inboxAccs(uint256 index) external view returns (bytes32);
function messageCount() external view returns (uint256);
}
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// NOTICE: pragma change from original (^0.6.11)
pragma solidity ^0.8.0;
interface IMessageProvider {
event InboxMessageDelivered(uint256 indexed messageNum, bytes data);
event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/TypeAndVersionInterface.sol";
import "./vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol";
import "./CrossDomainForwarder.sol";
/**
* @title ArbitrumCrossDomainForwarder - L1 xDomain account representation
* @notice L2 Contract which receives messages from a specific L1 address and transparently forwards them to the destination.
* @dev Any other L2 contract which uses this contract's address as a privileged position,
* can be considered to be owned by the `l1Owner`
*/
contract ArbitrumCrossDomainForwarder is TypeAndVersionInterface, CrossDomainForwarder {
/**
* @notice creates a new Arbitrum xDomain Forwarder contract
* @param l1OwnerAddr the L1 owner address that will be allowed to call the forward fn
*/
constructor(address l1OwnerAddr) CrossDomainForwarder(l1OwnerAddr) {
// noop
}
/**
* @notice versions:
*
* - ArbitrumCrossDomainForwarder 0.1.0: initial release
*
* @inheritdoc TypeAndVersionInterface
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "ArbitrumCrossDomainForwarder 0.1.0";
}
/**
* @notice The L2 xDomain `msg.sender`, generated from L1 sender address
* @inheritdoc CrossDomainForwarder
*/
function crossDomainMessenger() public view virtual override returns (address) {
return AddressAliasHelper.applyL1ToL2Alias(l1Owner());
}
/**
* @dev forwarded only if L2 Messenger calls with `xDomainMessageSender` beeing the L1 owner address
* @inheritdoc ForwarderInterface
*/
function forward(address target, bytes memory data) external override {
// 1. The call MUST come from the L2 Messenger (deterministically generated from the L1 xDomain sender address)
require(msg.sender == crossDomainMessenger(), "Sender is not the L2 messenger");
// 2. Make the external call
(bool success, bytes memory res) = target.call(data);
require(success, string(abi.encode("xDomain call failed:", res)));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
import "../SimpleReadAccessController.sol";
import "../interfaces/AccessControllerInterface.sol";
import "../interfaces/TypeAndVersionInterface.sol";
/* dev dependencies - to be re/moved after audit */
import "./interfaces/FlagsInterface.sol";
/**
* @title The Flags contract
* @notice Allows flags to signal to any reader on the access control list.
* The owner can set flags, or designate other addresses to set flags.
* Raise flag actions are controlled by its own access controller.
* Lower flag actions are controlled by its own access controller.
* An expected pattern is to allow addresses to raise flags on themselves, so if you are subscribing to
* FlagOn events you should filter for addresses you care about.
*/
contract Flags is TypeAndVersionInterface, FlagsInterface, SimpleReadAccessController {
AccessControllerInterface public raisingAccessController;
AccessControllerInterface public loweringAccessController;
mapping(address => bool) private flags;
event FlagRaised(address indexed subject);
event FlagLowered(address indexed subject);
event RaisingAccessControllerUpdated(address indexed previous, address indexed current);
event LoweringAccessControllerUpdated(address indexed previous, address indexed current);
/**
* @param racAddress address for the raising access controller.
* @param lacAddress address for the lowering access controller.
*/
constructor(address racAddress, address lacAddress) {
setRaisingAccessController(racAddress);
setLoweringAccessController(lacAddress);
}
/**
* @notice versions:
*
* - Flags 1.1.0: upgraded to solc 0.8, added lowering access controller
* - Flags 1.0.0: initial release
*
* @inheritdoc TypeAndVersionInterface
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "Flags 1.1.0";
}
/**
* @notice read the warning flag status of a contract address.
* @param subject The contract address being checked for a flag.
* @return A true value indicates that a flag was raised and a
* false value indicates that no flag was raised.
*/
function getFlag(address subject) external view override checkAccess returns (bool) {
return flags[subject];
}
/**
* @notice read the warning flag status of a contract address.
* @param subjects An array of addresses being checked for a flag.
* @return An array of bools where a true value for any flag indicates that
* a flag was raised and a false value indicates that no flag was raised.
*/
function getFlags(address[] calldata subjects) external view override checkAccess returns (bool[] memory) {
bool[] memory responses = new bool[](subjects.length);
for (uint256 i = 0; i < subjects.length; i++) {
responses[i] = flags[subjects[i]];
}
return responses;
}
/**
* @notice enable the warning flag for an address.
* Access is controlled by raisingAccessController, except for owner
* who always has access.
* @param subject The contract address whose flag is being raised
*/
function raiseFlag(address subject) external override {
require(_allowedToRaiseFlags(), "Not allowed to raise flags");
_tryToRaiseFlag(subject);
}
/**
* @notice enable the warning flags for multiple addresses.
* Access is controlled by raisingAccessController, except for owner
* who always has access.
* @param subjects List of the contract addresses whose flag is being raised
*/
function raiseFlags(address[] calldata subjects) external override {
require(_allowedToRaiseFlags(), "Not allowed to raise flags");
for (uint256 i = 0; i < subjects.length; i++) {
_tryToRaiseFlag(subjects[i]);
}
}
/**
* @notice allows owner to disable the warning flags for an addresses.
* Access is controlled by loweringAccessController, except for owner
* who always has access.
* @param subject The contract address whose flag is being lowered
*/
function lowerFlag(address subject) external override {
require(_allowedToLowerFlags(), "Not allowed to lower flags");
_tryToLowerFlag(subject);
}
/**
* @notice allows owner to disable the warning flags for multiple addresses.
* Access is controlled by loweringAccessController, except for owner
* who always has access.
* @param subjects List of the contract addresses whose flag is being lowered
*/
function lowerFlags(address[] calldata subjects) external override {
require(_allowedToLowerFlags(), "Not allowed to lower flags");
for (uint256 i = 0; i < subjects.length; i++) {
address subject = subjects[i];
_tryToLowerFlag(subject);
}
}
/**
* @notice allows owner to change the access controller for raising flags.
* @param racAddress new address for the raising access controller.
*/
function setRaisingAccessController(address racAddress) public override onlyOwner {
address previous = address(raisingAccessController);
if (previous != racAddress) {
raisingAccessController = AccessControllerInterface(racAddress);
emit RaisingAccessControllerUpdated(previous, racAddress);
}
}
function setLoweringAccessController(address lacAddress) public override onlyOwner {
address previous = address(loweringAccessController);
if (previous != lacAddress) {
loweringAccessController = AccessControllerInterface(lacAddress);
emit LoweringAccessControllerUpdated(previous, lacAddress);
}
}
// PRIVATE
function _allowedToRaiseFlags() private view returns (bool) {
return msg.sender == owner() || raisingAccessController.hasAccess(msg.sender, msg.data);
}
function _allowedToLowerFlags() private view returns (bool) {
return msg.sender == owner() || loweringAccessController.hasAccess(msg.sender, msg.data);
}
function _tryToRaiseFlag(address subject) private {
if (!flags[subject]) {
flags[subject] = true;
emit FlagRaised(subject);
}
}
function _tryToLowerFlag(address subject) private {
if (flags[subject]) {
flags[subject] = false;
emit FlagLowered(subject);
}
}
}
// 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 "./SimpleReadAccessController.sol";
import "./interfaces/AccessControllerInterface.sol";
import "./interfaces/FlagsInterface.sol";
/**
* @title The Flags contract
* @notice Allows flags to signal to any reader on the access control list.
* The owner can set flags, or designate other addresses to set flags. The
* owner must turn the flags off, other setters cannot. An expected pattern is
* to allow addresses to raise flags on themselves, so if you are subscribing to
* FlagOn events you should filter for addresses you care about.
*/
contract Flags is FlagsInterface, SimpleReadAccessController {
AccessControllerInterface public raisingAccessController;
mapping(address => bool) private flags;
event FlagRaised(address indexed subject);
event FlagLowered(address indexed subject);
event RaisingAccessControllerUpdated(address indexed previous, address indexed current);
/**
* @param racAddress address for the raising access controller.
*/
constructor(address racAddress) {
setRaisingAccessController(racAddress);
}
/**
* @notice read the warning flag status of a contract address.
* @param subject The contract address being checked for a flag.
* @return A true value indicates that a flag was raised and a
* false value indicates that no flag was raised.
*/
function getFlag(address subject) external view override checkAccess returns (bool) {
return flags[subject];
}
/**
* @notice read the warning flag status of a contract address.
* @param subjects An array of addresses being checked for a flag.
* @return An array of bools where a true value for any flag indicates that
* a flag was raised and a false value indicates that no flag was raised.
*/
function getFlags(address[] calldata subjects) external view override checkAccess returns (bool[] memory) {
bool[] memory responses = new bool[](subjects.length);
for (uint256 i = 0; i < subjects.length; i++) {
responses[i] = flags[subjects[i]];
}
return responses;
}
/**
* @notice enable the warning flag for an address.
* Access is controlled by raisingAccessController, except for owner
* who always has access.
* @param subject The contract address whose flag is being raised
*/
function raiseFlag(address subject) external override {
require(allowedToRaiseFlags(), "Not allowed to raise flags");
tryToRaiseFlag(subject);
}
/**
* @notice enable the warning flags for multiple addresses.
* Access is controlled by raisingAccessController, except for owner
* who always has access.
* @param subjects List of the contract addresses whose flag is being raised
*/
function raiseFlags(address[] calldata subjects) external override {
require(allowedToRaiseFlags(), "Not allowed to raise flags");
for (uint256 i = 0; i < subjects.length; i++) {
tryToRaiseFlag(subjects[i]);
}
}
/**
* @notice allows owner to disable the warning flags for multiple addresses.
* @param subjects List of the contract addresses whose flag is being lowered
*/
function lowerFlags(address[] calldata subjects) external override onlyOwner {
for (uint256 i = 0; i < subjects.length; i++) {
address subject = subjects[i];
if (flags[subject]) {
flags[subject] = false;
emit FlagLowered(subject);
}
}
}
/**
* @notice allows owner to change the access controller for raising flags.
* @param racAddress new address for the raising access controller.
*/
function setRaisingAccessController(address racAddress) public override onlyOwner {
address previous = address(raisingAccessController);
if (previous != racAddress) {
raisingAccessController = AccessControllerInterface(racAddress);
emit RaisingAccessControllerUpdated(previous, racAddress);
}
}
// PRIVATE
function allowedToRaiseFlags() private view returns (bool) {
return msg.sender == owner() || raisingAccessController.hasAccess(msg.sender, msg.data);
}
function tryToRaiseFlag(address subject) private {
if (!flags[subject]) {
flags[subject] = true;
emit FlagRaised(subject);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface FlagsInterface {
function getFlag(address) external view returns (bool);
function getFlags(address[] calldata) external view returns (bool[] memory);
function raiseFlag(address) external;
function raiseFlags(address[] calldata) external;
function lowerFlags(address[] calldata) external;
function setRaisingAccessController(address) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../Flags.sol";
contract FlagsTestHelper {
Flags public flags;
constructor(address flagsContract) {
flags = Flags(flagsContract);
}
function getFlag(address subject) external view returns (bool) {
return flags.getFlag(subject);
}
function getFlags(address[] calldata subjects) external view returns (bool[] memory) {
return flags.getFlags(subjects);
}
}
// 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 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;
pragma abicoder v2;
import "./AggregatorV2V3Interface.sol";
interface FeedRegistryInterface {
struct Phase {
uint16 phaseId;
uint80 startingAggregatorRoundId;
uint80 endingAggregatorRoundId;
}
event FeedProposed(
address indexed asset,
address indexed denomination,
address indexed proposedAggregator,
address currentAggregator,
address sender
);
event FeedConfirmed(
address indexed asset,
address indexed denomination,
address indexed latestAggregator,
address previousAggregator,
uint16 nextPhaseId,
address sender
);
// V3 AggregatorV3Interface
function decimals(address base, address quote) external view returns (uint8);
function description(address base, address quote) external view returns (string memory);
function version(address base, address quote) external view returns (uint256);
function latestRoundData(address base, address quote)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function getRoundData(
address base,
address quote,
uint80 _roundId
)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
// V2 AggregatorInterface
function latestAnswer(address base, address quote) external view returns (int256 answer);
function latestTimestamp(address base, address quote) external view returns (uint256 timestamp);
function latestRound(address base, address quote) external view returns (uint256 roundId);
function getAnswer(
address base,
address quote,
uint256 roundId
) external view returns (int256 answer);
function getTimestamp(
address base,
address quote,
uint256 roundId
) external view returns (uint256 timestamp);
// Registry getters
function getFeed(address base, address quote) external view returns (AggregatorV2V3Interface aggregator);
function getPhaseFeed(
address base,
address quote,
uint16 phaseId
) external view returns (AggregatorV2V3Interface aggregator);
function isFeedEnabled(address aggregator) external view returns (bool);
function getPhase(
address base,
address quote,
uint16 phaseId
) external view returns (Phase memory phase);
// Round helpers
function getRoundFeed(
address base,
address quote,
uint80 roundId
) external view returns (AggregatorV2V3Interface aggregator);
function getPhaseRange(
address base,
address quote,
uint16 phaseId
) external view returns (uint80 startingRoundId, uint80 endingRoundId);
function getPreviousRoundId(
address base,
address quote,
uint80 roundId
) external view returns (uint80 previousRoundId);
function getNextRoundId(
address base,
address quote,
uint80 roundId
) external view returns (uint80 nextRoundId);
// Feed management
function proposeFeed(
address base,
address quote,
address aggregator
) external;
function confirmFeed(
address base,
address quote,
address aggregator
) external;
// Proposed aggregator
function getProposedFeed(address base, address quote)
external
view
returns (AggregatorV2V3Interface proposedAggregator);
function proposedGetRoundData(
address base,
address quote,
uint80 roundId
)
external
view
returns (
uint80 id,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function proposedLatestRoundData(address base, address quote)
external
view
returns (
uint80 id,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
// Phases
function getCurrentPhaseId(address base, address quote) external view returns (uint16 currentPhaseId);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/AggregatorValidatorInterface.sol";
contract MockAggregatorValidator is AggregatorValidatorInterface {
uint8 immutable id;
constructor(uint8 id_) {
id = id_;
}
event ValidateCalled(
uint8 id,
uint256 previousRoundId,
int256 previousAnswer,
uint256 currentRoundId,
int256 currentAnswer
);
function validate(
uint256 previousRoundId,
int256 previousAnswer,
uint256 currentRoundId,
int256 currentAnswer
) external override returns (bool) {
emit ValidateCalled(id, previousRoundId, previousAnswer, currentRoundId, currentAnswer);
return true;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../dev/VRF.sol";
/** ***********************************************************************
@notice Testing harness for VRF.sol, exposing its internal methods. Not to
@notice be used for production.
*/
contract VRFTestHelper is VRF {
function bigModExp_(uint256 base, uint256 exponent) public view returns (uint256) {
return super.bigModExp(base, exponent);
}
function squareRoot_(uint256 x) public view returns (uint256) {
return super.squareRoot(x);
}
function ySquared_(uint256 x) public pure returns (uint256) {
return super.ySquared(x);
}
function fieldHash_(bytes memory b) public pure returns (uint256) {
return super.fieldHash(b);
}
function hashToCurve_(uint256[2] memory pk, uint256 x) public view returns (uint256[2] memory) {
return super.hashToCurve(pk, x);
}
function ecmulVerify_(
uint256[2] memory x,
uint256 scalar,
uint256[2] memory q
) public pure returns (bool) {
return super.ecmulVerify(x, scalar, q);
}
function projectiveECAdd_(
uint256 px,
uint256 py,
uint256 qx,
uint256 qy
)
public
pure
returns (
uint256,
uint256,
uint256
)
{
return super.projectiveECAdd(px, py, qx, qy);
}
function affineECAdd_(
uint256[2] memory p1,
uint256[2] memory p2,
uint256 invZ
) public pure returns (uint256[2] memory) {
return super.affineECAdd(p1, p2, invZ);
}
function verifyLinearCombinationWithGenerator_(
uint256 c,
uint256[2] memory p,
uint256 s,
address lcWitness
) public pure returns (bool) {
return super.verifyLinearCombinationWithGenerator(c, p, s, lcWitness);
}
function linearCombination_(
uint256 c,
uint256[2] memory p1,
uint256[2] memory cp1Witness,
uint256 s,
uint256[2] memory p2,
uint256[2] memory sp2Witness,
uint256 zInv
) public pure returns (uint256[2] memory) {
return super.linearCombination(c, p1, cp1Witness, s, p2, sp2Witness, zInv);
}
function scalarFromCurvePoints_(
uint256[2] memory hash,
uint256[2] memory pk,
uint256[2] memory gamma,
address uWitness,
uint256[2] memory v
) public pure returns (uint256) {
return super.scalarFromCurvePoints(hash, pk, gamma, uWitness, v);
}
function isOnCurve_(uint256[2] memory p) public pure returns (bool) {
return super.isOnCurve(p);
}
function verifyVRFProof_(
uint256[2] memory pk,
uint256[2] memory gamma,
uint256 c,
uint256 s,
uint256 seed,
address uWitness,
uint256[2] memory cGammaWitness,
uint256[2] memory sHashWitness,
uint256 zInv
) public view {
super.verifyVRFProof(pk, gamma, c, s, seed, uWitness, cGammaWitness, sHashWitness, zInv);
}
function randomValueFromVRFProof_(Proof memory proof, uint256 seed) public view returns (uint256 output) {
return super.randomValueFromVRFProof(proof, seed);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./interfaces/LinkTokenInterface.sol";
import "./VRFRequestIDBase.sol";
/** ****************************************************************************
* @notice Interface for contracts using VRF randomness
* *****************************************************************************
* @dev PURPOSE
*
* @dev Reggie the Random Oracle (not his real job) wants to provide randomness
* @dev to Vera the verifier in such a way that Vera can be sure he's not
* @dev making his output up to suit himself. Reggie provides Vera a public key
* @dev to which he knows the secret key. Each time Vera provides a seed to
* @dev Reggie, he gives back a value which is computed completely
* @dev deterministically from the seed and the secret key.
*
* @dev Reggie provides a proof by which Vera can verify that the output was
* @dev correctly computed once Reggie tells it to her, but without that proof,
* @dev the output is indistinguishable to her from a uniform random sample
* @dev from the output space.
*
* @dev The purpose of this contract is to make it easy for unrelated contracts
* @dev to talk to Vera the verifier about the work Reggie is doing, to provide
* @dev simple access to a verifiable source of randomness.
* *****************************************************************************
* @dev USAGE
*
* @dev Calling contracts must inherit from VRFConsumerBase, and can
* @dev initialize VRFConsumerBase's attributes in their constructor as
* @dev shown:
*
* @dev contract VRFConsumer {
* @dev constuctor(<other arguments>, address _vrfCoordinator, address _link)
* @dev VRFConsumerBase(_vrfCoordinator, _link) public {
* @dev <initialization with other arguments goes here>
* @dev }
* @dev }
*
* @dev The oracle will have given you an ID for the VRF keypair they have
* @dev committed to (let's call it keyHash), and have told you the minimum LINK
* @dev price for VRF service. Make sure your contract has sufficient LINK, and
* @dev call requestRandomness(keyHash, fee, seed), where seed is the input you
* @dev want to generate randomness from.
*
* @dev Once the VRFCoordinator has received and validated the oracle's response
* @dev to your request, it will call your contract's fulfillRandomness method.
*
* @dev The randomness argument to fulfillRandomness is the actual random value
* @dev generated from your seed.
*
* @dev The requestId argument is generated from the keyHash and the seed by
* @dev makeRequestId(keyHash, seed). If your contract could have concurrent
* @dev requests open, you can use the requestId to track which seed is
* @dev associated with which randomness. See VRFRequestIDBase.sol for more
* @dev details. (See "SECURITY CONSIDERATIONS" for principles to keep in mind,
* @dev if your contract could have multiple requests in flight simultaneously.)
*
* @dev Colliding `requestId`s are cryptographically impossible as long as seeds
* @dev differ. (Which is critical to making unpredictable randomness! See the
* @dev next section.)
*
* *****************************************************************************
* @dev SECURITY CONSIDERATIONS
*
* @dev A method with the ability to call your fulfillRandomness method directly
* @dev could spoof a VRF response with any random value, so it's critical that
* @dev it cannot be directly called by anything other than this base contract
* @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
*
* @dev For your users to trust that your contract's random behavior is free
* @dev from malicious interference, it's best if you can write it so that all
* @dev behaviors implied by a VRF response are executed *during* your
* @dev fulfillRandomness method. If your contract must store the response (or
* @dev anything derived from it) and use it later, you must ensure that any
* @dev user-significant behavior which depends on that stored value cannot be
* @dev manipulated by a subsequent VRF request.
*
* @dev Similarly, both miners and the VRF oracle itself have some influence
* @dev over the order in which VRF responses appear on the blockchain, so if
* @dev your contract could have multiple VRF requests in flight simultaneously,
* @dev you must ensure that the order in which the VRF responses arrive cannot
* @dev be used to manipulate your contract's user-significant behavior.
*
* @dev Since the ultimate input to the VRF is mixed with the block hash of the
* @dev block in which the request is made, user-provided seeds have no impact
* @dev on its economic security properties. They are only included for API
* @dev compatability with previous versions of this contract.
*
* @dev Since the block hash of the block which contains the requestRandomness
* @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
* @dev miner could, in principle, fork the blockchain to evict the block
* @dev containing the request, forcing the request to be included in a
* @dev different block with a different hash, and therefore a different input
* @dev to the VRF. However, such an attack would incur a substantial economic
* @dev cost. This cost scales with the number of blocks the VRF oracle waits
* @dev until it calls responds to a request.
*/
abstract contract VRFConsumerBase is VRFRequestIDBase {
/**
* @notice fulfillRandomness handles the VRF response. Your contract must
* @notice implement it. See "SECURITY CONSIDERATIONS" above for important
* @notice principles to keep in mind when implementing your fulfillRandomness
* @notice method.
*
* @dev VRFConsumerBase expects its subcontracts to have a method with this
* @dev signature, and will call it once it has verified the proof
* @dev associated with the randomness. (It is triggered via a call to
* @dev rawFulfillRandomness, below.)
*
* @param requestId The Id initially returned by requestRandomness
* @param randomness the VRF output
*/
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal virtual;
/**
* @dev In order to keep backwards compatibility we have kept the user
* seed field around. We remove the use of it because given that the blockhash
* enters later, it overrides whatever randomness the used seed provides.
* Given that it adds no security, and can easily lead to misunderstandings,
* we have removed it from usage and can now provide a simpler API.
*/
uint256 private constant USER_SEED_PLACEHOLDER = 0;
/**
* @notice requestRandomness initiates a request for VRF output given _seed
*
* @dev The fulfillRandomness method receives the output, once it's provided
* @dev by the Oracle, and verified by the vrfCoordinator.
*
* @dev The _keyHash must already be registered with the VRFCoordinator, and
* @dev the _fee must exceed the fee specified during registration of the
* @dev _keyHash.
*
* @dev The _seed parameter is vestigial, and is kept only for API
* @dev compatibility with older versions. It can't *hurt* to mix in some of
* @dev your own randomness, here, but it's not necessary because the VRF
* @dev oracle will mix the hash of the block containing your request into the
* @dev VRF seed it ultimately uses.
*
* @param _keyHash ID of public key against which randomness is generated
* @param _fee The amount of LINK to send with the request
*
* @return requestId unique ID for this request
*
* @dev The returned requestId can be used to distinguish responses to
* @dev concurrent requests. It is passed as the first argument to
* @dev fulfillRandomness.
*/
function requestRandomness(bytes32 _keyHash, uint256 _fee) internal returns (bytes32 requestId) {
LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, USER_SEED_PLACEHOLDER));
// This is the seed passed to VRFCoordinator. The oracle will mix this with
// the hash of the block containing this request to obtain the seed/input
// which is finally passed to the VRF cryptographic machinery.
uint256 vRFSeed = makeVRFInputSeed(_keyHash, USER_SEED_PLACEHOLDER, address(this), nonces[_keyHash]);
// nonces[_keyHash] must stay in sync with
// VRFCoordinator.nonces[_keyHash][this], which was incremented by the above
// successful LINK.transferAndCall (in VRFCoordinator.randomnessRequest).
// This provides protection against the user repeating their input seed,
// which would result in a predictable/duplicate output, if multiple such
// requests appeared in the same block.
nonces[_keyHash] = nonces[_keyHash] + 1;
return makeRequestId(_keyHash, vRFSeed);
}
LinkTokenInterface internal immutable LINK;
address private immutable vrfCoordinator;
// Nonces for each VRF key from which randomness has been requested.
//
// Must stay in sync with VRFCoordinator[_keyHash][this]
mapping(bytes32 => uint256) /* keyHash */ /* nonce */
private nonces;
/**
* @param _vrfCoordinator address of VRFCoordinator contract
* @param _link address of LINK token contract
*
* @dev https://docs.chain.link/docs/link-token-contracts
*/
constructor(address _vrfCoordinator, address _link) {
vrfCoordinator = _vrfCoordinator;
LINK = LinkTokenInterface(_link);
}
// rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
// proof. rawFulfillRandomness then calls fulfillRandomness, after validating
// the origin of the call
function rawFulfillRandomness(bytes32 requestId, uint256 randomness) external {
require(msg.sender == vrfCoordinator, "Only VRFCoordinator can fulfill");
fulfillRandomness(requestId, randomness);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract VRFRequestIDBase {
/**
* @notice returns the seed which is actually input to the VRF coordinator
*
* @dev To prevent repetition of VRF output due to repetition of the
* @dev user-supplied seed, that seed is combined in a hash with the
* @dev user-specific nonce, and the address of the consuming contract. The
* @dev risk of repetition is mostly mitigated by inclusion of a blockhash in
* @dev the final seed, but the nonce does protect against repetition in
* @dev requests which are included in a single block.
*
* @param _userSeed VRF seed input provided by user
* @param _requester Address of the requesting contract
* @param _nonce User-specific nonce at the time of the request
*/
function makeVRFInputSeed(
bytes32 _keyHash,
uint256 _userSeed,
address _requester,
uint256 _nonce
) internal pure returns (uint256) {
return uint256(keccak256(abi.encode(_keyHash, _userSeed, _requester, _nonce)));
}
/**
* @notice Returns the id for this request
* @param _keyHash The serviceAgreement ID to be used for this request
* @param _vRFInputSeed The seed to be passed directly to the VRF
* @return The id for this request
*
* @dev Note that _vRFInputSeed is not the seed passed by the consuming
* @dev contract, but the one generated by makeVRFInputSeed
*/
function makeRequestId(bytes32 _keyHash, uint256 _vRFInputSeed) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(_keyHash, _vRFInputSeed));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../VRFRequestIDBase.sol";
contract VRFRequestIDBaseTestHelper is VRFRequestIDBase {
function makeVRFInputSeed_(
bytes32 _keyHash,
uint256 _userSeed,
address _requester,
uint256 _nonce
) public pure returns (uint256) {
return makeVRFInputSeed(_keyHash, _userSeed, _requester, _nonce);
}
function makeRequestId_(bytes32 _keyHash, uint256 _vRFInputSeed) public pure returns (bytes32) {
return makeRequestId(_keyHash, _vRFInputSeed);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/LinkTokenInterface.sol";
import "../VRFConsumerBase.sol";
contract VRFConsumer is VRFConsumerBase {
uint256 public randomnessOutput;
bytes32 public requestId;
constructor(address vrfCoordinator, address link)
// solhint-disable-next-line no-empty-blocks
VRFConsumerBase(vrfCoordinator, link)
{
/* empty */
}
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
randomnessOutput = randomness;
requestId = requestId;
}
function testRequestRandomness(bytes32 keyHash, uint256 fee) external returns (bytes32) {
return requestRandomness(keyHash, fee);
}
}