// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;interfaceAggregatorV3Interface{
functiondecimals() externalviewreturns (uint8);
functiondescription() externalviewreturns (stringmemory);
functionversion() externalviewreturns (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.functiongetRoundData(uint80 _roundId)
externalviewreturns (uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
functionlatestRoundData()
externalviewreturns (uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;import"./AccessControllerInterface.sol";
import"./AggregatorV2V3Interface.sol";
import"./AggregatorValidatorInterface.sol";
import"./LinkTokenInterface.sol";
import"./Owned.sol";
import"./OffchainAggregatorBilling.sol";
import"./TypeAndVersionInterface.sol";
/**
* @notice Onchain verification of reports from the offchain reporting protocol
* @dev For details on its operation, see the offchain reporting protocol design
* @dev doc, which refers to this contract as simply the "contract".
*/contractOffchainAggregatorisOwned, OffchainAggregatorBilling, AggregatorV2V3Interface, TypeAndVersionInterface{
uint256constantprivate maxUint32 = (1<<32) -1;
// Storing these fields used on the hot path in a HotVars variable reduces the// retrieval of all of them to a single SLOAD. If any further fields are// added, make sure that storage of the struct still takes at most 32 bytes.structHotVars {
// Provides 128 bits of security against 2nd pre-image attacks, but only// 64 bits against collisions. This is acceptable, since a malicious owner has// easier way of messing up the protocol than to find hash collisions.bytes16 latestConfigDigest;
uint40 latestEpochAndRound; // 32 most sig bits for epoch, 8 least sig bits for round// Current bound assumed on number of faulty/dishonest oracles participating// in the protocol, this value is referred to as f in the designuint8 threshold;
// Chainlink Aggregators expose a roundId to consumers. The offchain reporting// protocol does not use this id anywhere. We increment it whenever a new// transmission is made to provide callers with contiguous ids for successive// reports.uint32 latestAggregatorRoundId;
}
HotVars internal s_hotVars;
// Transmission records the median answer from the transmit transaction at// time timestampstructTransmission {
int192 answer; // 192 bits ought to be enough for anyoneuint64 timestamp;
}
mapping(uint32/* aggregator round ID */=> Transmission) internal s_transmissions;
// incremented each time a new config is posted. This count is incorporated// into the config digest, to prevent replay attacks.uint32internal s_configCount;
uint32internal s_latestConfigBlockNumber; // makes it easier for offchain systems// to extract config from logs.// Lowest answer the system is allowed to report in response to transmissionsint192immutablepublic minAnswer;
// Highest answer the system is allowed to report in response to transmissionsint192immutablepublic maxAnswer;
/*
* @param _maximumGasPrice highest gas price for which transmitter will be compensated
* @param _reasonableGasPrice transmitter will receive reward for gas prices under this value
* @param _microLinkPerEth reimbursement per ETH of gas cost, in 1e-6LINK units
* @param _linkGweiPerObservation reward to oracle for contributing an observation to a successfully transmitted report, in 1e-9LINK units
* @param _linkGweiPerTransmission reward to transmitter of a successful report, in 1e-9LINK units
* @param _link address of the LINK contract
* @param _minAnswer lowest answer the median of a report is allowed to be
* @param _maxAnswer highest answer the median of a report is allowed to be
* @param _billingAccessController access controller for billing admin functions
* @param _requesterAccessController access controller for requesting new rounds
* @param _decimals answers are stored in fixed-point format, with this many digits of precision
* @param _description short human-readable description of observable this contract's answers pertain to
*/constructor(uint32 _maximumGasPrice,
uint32 _reasonableGasPrice,
uint32 _microLinkPerEth,
uint32 _linkGweiPerObservation,
uint32 _linkGweiPerTransmission,
address _link,
int192 _minAnswer,
int192 _maxAnswer,
AccessControllerInterface _billingAccessController,
AccessControllerInterface _requesterAccessController,
uint8 _decimals,
stringmemory _description
)
OffchainAggregatorBilling(_maximumGasPrice, _reasonableGasPrice, _microLinkPerEth,
_linkGweiPerObservation, _linkGweiPerTransmission, _link,
_billingAccessController
)
{
decimals = _decimals;
s_description = _description;
setRequesterAccessController(_requesterAccessController);
setValidatorConfig(AggregatorValidatorInterface(0x0), 0);
minAnswer = _minAnswer;
maxAnswer = _maxAnswer;
}
/*
* Versioning
*/functiontypeAndVersion()
externaloverridepurevirtualreturns (stringmemory)
{
return"OffchainAggregator 2.0.0";
}
/*
* Config logic
*//**
* @notice triggers a new run of the offchain reporting protocol
* @param previousConfigBlockNumber block in which the previous config was set, to simplify historic analysis
* @param configCount ordinal number of this config setting among all config settings over the life of this contract
* @param signers ith element is address ith oracle uses to sign a report
* @param transmitters ith element is address ith oracle uses to transmit a report via the transmit method
* @param threshold maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly
* @param encodedConfigVersion version of the serialization format used for "encoded" parameter
* @param encoded serialized data used by oracles to configure their offchain operation
*/eventConfigSet(uint32 previousConfigBlockNumber,
uint64 configCount,
address[] signers,
address[] transmitters,
uint8 threshold,
uint64 encodedConfigVersion,
bytes encoded
);
// Reverts transaction if config args are invalidmodifiercheckConfigValid (uint256 _numSigners, uint256 _numTransmitters, uint256 _threshold
) {
require(_numSigners <= maxNumOracles, "too many signers");
require(_threshold >0, "threshold must be positive");
require(
_numSigners == _numTransmitters,
"oracle addresses out of registration"
);
require(_numSigners >3*_threshold, "faulty-oracle threshold too high");
_;
}
/**
* @notice sets offchain reporting protocol configuration incl. participating oracles
* @param _signers addresses with which oracles sign the reports
* @param _transmitters addresses oracles use to transmit the reports
* @param _threshold number of faulty oracles the system can tolerate
* @param _encodedConfigVersion version number for offchainEncoding schema
* @param _encoded encoded off-chain oracle configuration
*/functionsetConfig(address[] calldata _signers,
address[] calldata _transmitters,
uint8 _threshold,
uint64 _encodedConfigVersion,
bytescalldata _encoded
)
externalcheckConfigValid(_signers.length, _transmitters.length, _threshold)
onlyOwner()
{
while (s_signers.length!=0) { // remove any old signer/transmitter addressesuint lastIdx = s_signers.length-1;
address signer = s_signers[lastIdx];
address transmitter = s_transmitters[lastIdx];
payOracle(transmitter);
delete s_oracles[signer];
delete s_oracles[transmitter];
s_signers.pop();
s_transmitters.pop();
}
for (uint i =0; i < _signers.length; i++) { // add new signer/transmitter addressesrequire(
s_oracles[_signers[i]].role == Role.Unset,
"repeated signer address"
);
s_oracles[_signers[i]] = Oracle(uint8(i), Role.Signer);
require(s_payees[_transmitters[i]] !=address(0), "payee must be set");
require(
s_oracles[_transmitters[i]].role == Role.Unset,
"repeated transmitter address"
);
s_oracles[_transmitters[i]] = Oracle(uint8(i), Role.Transmitter);
s_signers.push(_signers[i]);
s_transmitters.push(_transmitters[i]);
}
s_hotVars.threshold = _threshold;
uint32 previousConfigBlockNumber = s_latestConfigBlockNumber;
s_latestConfigBlockNumber =uint32(block.number);
s_configCount +=1;
uint64 configCount = s_configCount;
{
s_hotVars.latestConfigDigest = configDigestFromConfigData(
address(this),
configCount,
_signers,
_transmitters,
_threshold,
_encodedConfigVersion,
_encoded
);
s_hotVars.latestEpochAndRound =0;
}
emit ConfigSet(
previousConfigBlockNumber,
configCount,
_signers,
_transmitters,
_threshold,
_encodedConfigVersion,
_encoded
);
}
functionconfigDigestFromConfigData(address _contractAddress,
uint64 _configCount,
address[] calldata _signers,
address[] calldata _transmitters,
uint8 _threshold,
uint64 _encodedConfigVersion,
bytescalldata _encodedConfig
) internalpurereturns (bytes16) {
returnbytes16(keccak256(abi.encode(_contractAddress, _configCount,
_signers, _transmitters, _threshold, _encodedConfigVersion, _encodedConfig
)));
}
/**
* @notice information about current offchain reporting protocol configuration
* @return configCount ordinal number of current config, out of all configs applied to this contract so far
* @return blockNumber block at which this config was set
* @return configDigest domain-separation tag for current config (see configDigestFromConfigData)
*/functionlatestConfigDetails()
externalviewreturns (uint32 configCount,
uint32 blockNumber,
bytes16 configDigest
)
{
return (s_configCount, s_latestConfigBlockNumber, s_hotVars.latestConfigDigest);
}
/**
* @return list of addresses permitted to transmit reports to this contract
* @dev The list will match the order used to specify the transmitter during setConfig
*/functiontransmitters()
externalviewreturns(address[] memory)
{
return s_transmitters;
}
/*
* On-chain validation logc
*/// Configuration for validatorstructValidatorConfig {
AggregatorValidatorInterface validator;
uint32 gasLimit;
}
ValidatorConfig private s_validatorConfig;
/**
* @notice indicates that the validator configuration has been set
* @param previousValidator previous validator contract
* @param previousGasLimit previous gas limit for validate calls
* @param currentValidator current validator contract
* @param currentGasLimit current gas limit for validate calls
*/eventValidatorConfigSet(
AggregatorValidatorInterface indexed previousValidator,
uint32 previousGasLimit,
AggregatorValidatorInterface indexed currentValidator,
uint32 currentGasLimit
);
/**
* @notice validator configuration
* @return validator validator contract
* @return gasLimit gas limit for validate calls
*/functionvalidatorConfig()
externalviewreturns (AggregatorValidatorInterface validator, uint32 gasLimit)
{
ValidatorConfig memory vc = s_validatorConfig;
return (vc.validator, vc.gasLimit);
}
/**
* @notice sets validator configuration
* @dev set _newValidator to 0x0 to disable validate calls
* @param _newValidator address of the new validator contract
* @param _newGasLimit new gas limit for validate calls
*/functionsetValidatorConfig(AggregatorValidatorInterface _newValidator, uint32 _newGasLimit)
publiconlyOwner()
{
ValidatorConfig memory previous = s_validatorConfig;
if (previous.validator != _newValidator || previous.gasLimit != _newGasLimit) {
s_validatorConfig = ValidatorConfig({
validator: _newValidator,
gasLimit: _newGasLimit
});
emit ValidatorConfigSet(previous.validator, previous.gasLimit, _newValidator, _newGasLimit);
}
}
functionvalidateAnswer(uint32 _aggregatorRoundId,
int256 _answer
)
private{
ValidatorConfig memory vc = s_validatorConfig;
if (address(vc.validator) ==address(0)) {
return;
}
uint32 prevAggregatorRoundId = _aggregatorRoundId -1;
int256 prevAggregatorRoundAnswer = s_transmissions[prevAggregatorRoundId].answer;
// We do not want the validator to ever prevent reporting, so we limit its// gas usage and catch any errors that may arise.try vc.validator.validate{gas: vc.gasLimit}(
prevAggregatorRoundId,
prevAggregatorRoundAnswer,
_aggregatorRoundId,
_answer
) {} catch {}
}
/*
* requestNewRound logic
*/
AccessControllerInterface internal s_requesterAccessController;
/**
* @notice emitted when a new requester access controller contract is set
* @param old the address prior to the current setting
* @param current the address of the new access controller contract
*/eventRequesterAccessControllerSet(AccessControllerInterface old, AccessControllerInterface current);
/**
* @notice emitted to immediately request a new round
* @param requester the address of the requester
* @param configDigest the latest transmission's configDigest
* @param epoch the latest transmission's epoch
* @param round the latest transmission's round
*/eventRoundRequested(addressindexed requester, bytes16 configDigest, uint32 epoch, uint8 round);
/**
* @notice address of the requester access controller contract
* @return requester access controller address
*/functionrequesterAccessController()
externalviewreturns (AccessControllerInterface)
{
return s_requesterAccessController;
}
/**
* @notice sets the requester access controller
* @param _requesterAccessController designates the address of the new requester access controller
*/functionsetRequesterAccessController(AccessControllerInterface _requesterAccessController)
publiconlyOwner()
{
AccessControllerInterface oldController = s_requesterAccessController;
if (_requesterAccessController != oldController) {
s_requesterAccessController = AccessControllerInterface(_requesterAccessController);
emit RequesterAccessControllerSet(oldController, _requesterAccessController);
}
}
/**
* @notice immediately requests a new round
* @return the aggregatorRoundId of the next round. Note: The report for this round may have been
* transmitted (but not yet mined) *before* requestNewRound() was even called. There is *no*
* guarantee of causality between the request and the report at aggregatorRoundId.
*/functionrequestNewRound() externalreturns (uint80) {
require(msg.sender== owner || s_requesterAccessController.hasAccess(msg.sender, msg.data),
"Only owner&requester can call");
HotVars memory hotVars = s_hotVars;
emit RoundRequested(
msg.sender,
hotVars.latestConfigDigest,
uint32(s_hotVars.latestEpochAndRound >>8),
uint8(s_hotVars.latestEpochAndRound)
);
return hotVars.latestAggregatorRoundId +1;
}
/*
* Transmission logic
*//**
* @notice indicates that a new report was transmitted
* @param aggregatorRoundId the round to which this report was assigned
* @param answer median of the observations attached this report
* @param transmitter address from which the report was transmitted
* @param observations observations transmitted with this report
* @param rawReportContext signature-replay-prevention domain-separation tag
*/eventNewTransmission(uint32indexed aggregatorRoundId,
int192 answer,
address transmitter,
int192[] observations,
bytes observers,
bytes32 rawReportContext
);
// decodeReport is used to check that the solidity and go code are using the// same format. See TestOffchainAggregator.testDecodeReport and TestReportParsingfunctiondecodeReport(bytesmemory _report)
internalpurereturns (bytes32 rawReportContext,
bytes32 rawObservers,
int192[] memory observations
)
{
(rawReportContext, rawObservers, observations) =abi.decode(_report,
(bytes32, bytes32, int192[]));
}
// Used to relieve stack pressure in transmitstructReportData {
HotVars hotVars; // Only read from storage oncebytes observers; // ith element is the index of the ith observerint192[] observations; // ith element is the ith observationbytes vs; // jth element is the v component of the jth signaturebytes32 rawReportContext;
}
/*
* @notice details about the most recent report
* @return configDigest domain separation tag for the latest report
* @return epoch epoch in which the latest report was generated
* @return round OCR round in which the latest report was generated
* @return latestAnswer median value from latest report
* @return latestTimestamp when the latest report was transmitted
*/functionlatestTransmissionDetails()
externalviewreturns (bytes16 configDigest,
uint32 epoch,
uint8 round,
int192 latestAnswer,
uint64 latestTimestamp
)
{
require(msg.sender==tx.origin, "Only callable by EOA");
return (
s_hotVars.latestConfigDigest,
uint32(s_hotVars.latestEpochAndRound >>8),
uint8(s_hotVars.latestEpochAndRound),
s_transmissions[s_hotVars.latestAggregatorRoundId].answer,
s_transmissions[s_hotVars.latestAggregatorRoundId].timestamp
);
}
// The constant-length components of the msg.data sent to transmit.// See the "If we wanted to call sam" example on for example reasoning// https://solidity.readthedocs.io/en/v0.7.2/abi-spec.htmluint16privateconstant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT =4+// function selector32+// word containing start location of abiencoded _report value32+// word containing location start of abiencoded _rs value32+// word containing start location of abiencoded _ss value32+// _rawVs value32+// word containing length of _report32+// word containing length _rs32+// word containing length of _ss0; // placeholderfunctionexpectedMsgDataLength(bytescalldata _report, bytes32[] calldata _rs, bytes32[] calldata _ss
) privatepurereturns (uint256 length)
{
// calldata will never be big enough to make this overflowreturnuint256(TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT) +
_report.length+// one byte pure entry in _report
_rs.length*32+// 32 bytes per entry in _rs
_ss.length*32+// 32 bytes per entry in _ss0; // placeholder
}
/**
* @notice transmit is called to post a new report to the contract
* @param _report serialized report, which the signatures are signing. See parsing code below for format. The ith element of the observers component must be the index in s_signers of the address for the ith signature
* @param _rs ith element is the R components of the ith signature on report. Must have at most maxNumOracles entries
* @param _ss ith element is the S components of the ith signature on report. Must have at most maxNumOracles entries
* @param _rawVs ith element is the the V component of the ith signature
*/functiontransmit(// NOTE: If these parameters are changed, expectedMsgDataLength and/or// TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordinglybytescalldata _report,
bytes32[] calldata _rs, bytes32[] calldata _ss, bytes32 _rawVs // signatures)
external{
uint256 initialGas =gasleft(); // This line must come first// Make sure the transmit message-length matches the inputs. Otherwise, the// transmitter could append an arbitrarily long (up to gas-block limit)// string of 0 bytes, which we would reimburse at a rate of 16 gas/byte, but// which would only cost the transmitter 4 gas/byte. (Appendix G of the// yellow paper, p. 25, for G_txdatazero and EIP 2028 for G_txdatanonzero.)// This could amount to reimbursement profit of 36 million gas, given a 3MB// zero tail.require(msg.data.length == expectedMsgDataLength(_report, _rs, _ss),
"transmit message too long");
ReportData memory r; // Relieves stack pressure
{
r.hotVars = s_hotVars; // cache read from storagebytes32 rawObservers;
(r.rawReportContext, rawObservers, r.observations) =abi.decode(
_report, (bytes32, bytes32, int192[])
);
// rawReportContext consists of:// 11-byte zero padding// 16-byte configDigest// 4-byte epoch// 1-byte roundbytes16 configDigest =bytes16(r.rawReportContext <<88);
require(
r.hotVars.latestConfigDigest == configDigest,
"configDigest mismatch"
);
uint40 epochAndRound =uint40(uint256(r.rawReportContext));
// direct numerical comparison works here, because//// ((e,r) <= (e',r')) implies (epochAndRound <= epochAndRound')//// because alphabetic ordering implies e <= e', and if e = e', then r<=r',// so e*256+r <= e'*256+r', because r, r' < 256require(r.hotVars.latestEpochAndRound < epochAndRound, "stale report");
require(_rs.length> r.hotVars.threshold, "not enough signatures");
require(_rs.length<= maxNumOracles, "too many signatures");
require(_ss.length== _rs.length, "signatures out of registration");
require(r.observations.length<= maxNumOracles,
"num observations out of bounds");
require(r.observations.length>2* r.hotVars.threshold,
"too few values to trust median");
// Copy signature parities in bytes32 _rawVs to bytes r.v
r.vs =newbytes(_rs.length);
for (uint8 i =0; i < _rs.length; i++) {
r.vs[i] = _rawVs[i];
}
// Copy observer identities in bytes32 rawObservers to bytes r.observers
r.observers =newbytes(r.observations.length);
bool[maxNumOracles] memory seen;
for (uint8 i =0; i < r.observations.length; i++) {
uint8 observerIdx =uint8(rawObservers[i]);
require(!seen[observerIdx], "observer index repeated");
seen[observerIdx] =true;
r.observers[i] = rawObservers[i];
}
Oracle memory transmitter = s_oracles[msg.sender];
require( // Check that sender is authorized to report
transmitter.role == Role.Transmitter &&msg.sender== s_transmitters[transmitter.index],
"unauthorized transmitter"
);
// record epochAndRound here, so that we don't have to carry the local// variable in transmit. The change is reverted if something fails later.
r.hotVars.latestEpochAndRound = epochAndRound;
}
{ // Verify signatures attached to reportbytes32 h =keccak256(_report);
bool[maxNumOracles] memory signed;
Oracle memory o;
for (uint i =0; i < _rs.length; i++) {
address signer =ecrecover(h, uint8(r.vs[i])+27, _rs[i], _ss[i]);
o = s_oracles[signer];
require(o.role == Role.Signer, "address not authorized to sign");
require(!signed[o.index], "non-unique signature");
signed[o.index] =true;
}
}
{ // Check the report contents, and record the resultfor (uint i =0; i < r.observations.length-1; i++) {
bool inOrder = r.observations[i] <= r.observations[i+1];
require(inOrder, "observations not sorted");
}
int192 median = r.observations[r.observations.length/2];
require(minAnswer <= median && median <= maxAnswer, "median is out of min-max range");
r.hotVars.latestAggregatorRoundId++;
s_transmissions[r.hotVars.latestAggregatorRoundId] =
Transmission(median, uint64(block.timestamp));
emit NewTransmission(
r.hotVars.latestAggregatorRoundId,
median,
msg.sender,
r.observations,
r.observers,
r.rawReportContext
);
// Emit these for backwards compatability with offchain consumers// that only support legacy eventsemit NewRound(
r.hotVars.latestAggregatorRoundId,
address(0x0), // use zero address since we don't have anybody "starting" the round hereblock.timestamp
);
emit AnswerUpdated(
median,
r.hotVars.latestAggregatorRoundId,
block.timestamp
);
validateAnswer(r.hotVars.latestAggregatorRoundId, median);
}
s_hotVars = r.hotVars;
assert(initialGas < maxUint32);
reimburseAndRewardOracles(uint32(initialGas), r.observers);
}
/*
* v2 Aggregator interface
*//**
* @notice median from the most recent report
*/functionlatestAnswer()
publicoverrideviewvirtualreturns (int256)
{
return s_transmissions[s_hotVars.latestAggregatorRoundId].answer;
}
/**
* @notice timestamp of block in which last report was transmitted
*/functionlatestTimestamp()
publicoverrideviewvirtualreturns (uint256)
{
return s_transmissions[s_hotVars.latestAggregatorRoundId].timestamp;
}
/**
* @notice Aggregator round (NOT OCR round) in which last report was transmitted
*/functionlatestRound()
publicoverrideviewvirtualreturns (uint256)
{
return s_hotVars.latestAggregatorRoundId;
}
/**
* @notice median of report from given aggregator round (NOT OCR round)
* @param _roundId the aggregator round of the target report
*/functiongetAnswer(uint256 _roundId)
publicoverrideviewvirtualreturns (int256)
{
if (_roundId >0xFFFFFFFF) { return0; }
return s_transmissions[uint32(_roundId)].answer;
}
/**
* @notice timestamp of block in which report from given aggregator round was transmitted
* @param _roundId aggregator round (NOT OCR round) of target report
*/functiongetTimestamp(uint256 _roundId)
publicoverrideviewvirtualreturns (uint256)
{
if (_roundId >0xFFFFFFFF) { return0; }
return s_transmissions[uint32(_roundId)].timestamp;
}
/*
* v3 Aggregator interface
*/stringconstantprivate V3_NO_DATA_ERROR ="No data present";
/**
* @return answers are stored in fixed-point format, with this many digits of precision
*/uint8immutablepublicoverride decimals;
/**
* @notice aggregator contract version
*/uint256constantpublicoverride version =4;
stringinternal s_description;
/**
* @notice human-readable description of observable this contract is reporting on
*/functiondescription()
publicoverrideviewvirtualreturns (stringmemory)
{
return s_description;
}
/**
* @notice details for the given aggregator round
* @param _roundId target aggregator round (NOT OCR round). Must fit in uint32
* @return roundId _roundId
* @return answer median of report from given _roundId
* @return startedAt timestamp of block in which report from given _roundId was transmitted
* @return updatedAt timestamp of block in which report from given _roundId was transmitted
* @return answeredInRound _roundId
*/functiongetRoundData(uint80 _roundId)
publicoverrideviewvirtualreturns (uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
require(_roundId <=0xFFFFFFFF, V3_NO_DATA_ERROR);
Transmission memory transmission = s_transmissions[uint32(_roundId)];
return (
_roundId,
transmission.answer,
transmission.timestamp,
transmission.timestamp,
_roundId
);
}
/**
* @notice aggregator details for the most recently transmitted report
* @return roundId aggregator round of latest report (NOT OCR round)
* @return answer median of latest report
* @return startedAt timestamp of block containing latest report
* @return updatedAt timestamp of block containing latest report
* @return answeredInRound aggregator round of latest report
*/functionlatestRoundData()
publicoverrideviewvirtualreturns (uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
roundId = s_hotVars.latestAggregatorRoundId;
// Skipped for compatability with existing FluxAggregator in which latestRoundData never reverts.// require(roundId != 0, V3_NO_DATA_ERROR);
Transmission memory transmission = s_transmissions[uint32(roundId)];
return (
roundId,
transmission.answer,
transmission.timestamp,
transmission.timestamp,
roundId
);
}
}
Contract Source Code
File 9 of 13: OffchainAggregatorBilling.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;import"./AccessControllerInterface.sol";
import"./LinkTokenInterface.sol";
import"./Owned.sol";
/**
* @notice tracks administration of oracle-reward and gas-reimbursement parameters.
* @dev
* If you read or change this, be sure to read or adjust the comments. They
* track the units of the values under consideration, and are crucial to
* the readability of the operations it specifies.
* @notice
* Trust Model:
* Nothing in this contract prevents a billing admin from setting insane
* values for the billing parameters in setBilling. Oracles
* participating in this contract should regularly check that the
* parameters make sense. Similarly, the outstanding obligations of this
* contract to the oracles can exceed the funds held by the contract.
* Oracles participating in this contract should regularly check that it
* holds sufficient funds and stop interacting with it if funding runs
* out.
* This still leaves oracles with some risk due to TOCTOU issues.
* However, since the sums involved are pretty small (Ethereum
* transactions aren't that expensive in the end) and an oracle would
* likely stop participating in a contract it repeatedly lost money on,
* this risk is deemed acceptable. Oracles should also regularly
* withdraw any funds in the contract to prevent issues where the
* contract becomes underfunded at a later time, and different oracles
* are competing for the left-over funds.
* Finally, note that any change to the set of oracles or to the billing
* parameters will trigger payout of all oracles first (using the old
* parameters), a billing admin cannot take away funds that are already
* marked for payment.
*/contractOffchainAggregatorBillingisOwned{
// Maximum number of oracles the offchain reporting protocol is designed foruint256constantinternal maxNumOracles =31;
// Parameters for oracle paymentsstructBilling {
// Highest compensated gas price, in ETH-gwei uintsuint32 maximumGasPrice;
// If gas price is less (in ETH-gwei units), transmitter gets half the savingsuint32 reasonableGasPrice;
// Pay transmitter back this much LINK per unit eth spent on gas// (1e-6LINK/ETH units)uint32 microLinkPerEth;
// Fixed LINK reward for each observer, in LINK-gwei unitsuint32 linkGweiPerObservation;
// Fixed reward for transmitter, in linkGweiPerObservation unitsuint32 linkGweiPerTransmission;
}
Billing internal s_billing;
/**
* @return LINK token contract used for billing
*/
LinkTokenInterface immutablepublic LINK;
AccessControllerInterface internal s_billingAccessController;
// ith element is number of observation rewards due to ith process, plus one.// This is expected to saturate after an oracle has submitted 65,535// observations, or about 65535/(3*24*20) = 45 days, given a transmission// every 3 minutes.//// This is always one greater than the actual value, so that when the value is// reset to zero, we don't end up with a zero value in storage (which would// result in a higher gas cost, the next time the value is incremented.)// Calculations using this variable need to take that offset into account.uint16[maxNumOracles] internal s_oracleObservationsCounts;
// Addresses at which oracles want to receive payments, by transmitter addressmapping (address/* transmitter */=>address/* payment address */)
internal
s_payees;
// Payee addresses which must be approved by the ownermapping (address/* transmitter */=>address/* payment address */)
internal
s_proposedPayees;
// LINK-wei-denominated reimbursements for gas used by transmitters.//// This is always one greater than the actual value, so that when the value is// reset to zero, we don't end up with a zero value in storage (which would// result in a higher gas cost, the next time the value is incremented.)// Calculations using this variable need to take that offset into account.//// Argument for overflow safety:// We have the following maximum intermediate values:// - 2**40 additions to this variable (epochAndRound is a uint40)// - 2**32 gas price in ethgwei/gas// - 1e9 ethwei/ethgwei// - 2**32 gas since the block gas limit is at ~20 million// - 2**32 (microlink/eth)// And we have 2**40 * 2**32 * 1e9 * 2**32 * 2**32 < 2**166// (we also divide in some places, but that only makes the value smaller)// We can thus safely use uint256 intermediate values for the computation// updating this variable.uint256[maxNumOracles] internal s_gasReimbursementsLinkWei;
// Used for s_oracles[a].role, where a is an address, to track the purpose// of the address, or to indicate that the address is unset.enumRole {
// No oracle role has been set for address a
Unset,
// Signing address for the s_oracles[a].index'th oracle. I.e., report// signatures from this oracle should ecrecover back to address a.
Signer,
// Transmission address for the s_oracles[a].index'th oracle. I.e., if a// report is received by OffchainAggregator.transmit in which msg.sender is// a, it is attributed to the s_oracles[a].index'th oracle.
Transmitter
}
structOracle {
uint8 index; // Index of oracle in s_signers/s_transmitters
Role role; // Role of the address which mapped to this struct
}
mapping (address/* signer OR transmitter address */=> Oracle)
internal s_oracles;
// s_signers contains the signing address of each oracleaddress[] internal s_signers;
// s_transmitters contains the transmission address of each oracle,// i.e. the address the oracle actually sends transactions to the contract fromaddress[] internal s_transmitters;
uint256constantprivate maxUint16 = (1<<16) -1;
uint256constantinternal maxUint128 = (1<<128) -1;
constructor(uint32 _maximumGasPrice,
uint32 _reasonableGasPrice,
uint32 _microLinkPerEth,
uint32 _linkGweiPerObservation,
uint32 _linkGweiPerTransmission,
address _link,
AccessControllerInterface _billingAccessController
)
{
setBillingInternal(_maximumGasPrice, _reasonableGasPrice, _microLinkPerEth,
_linkGweiPerObservation, _linkGweiPerTransmission);
setBillingAccessControllerInternal(_billingAccessController);
LINK = LinkTokenInterface(_link);
uint16[maxNumOracles] memory counts; // See s_oracleObservationsCounts docstringuint256[maxNumOracles] memory gas; // see s_gasReimbursementsLinkWei docstringfor (uint8 i =0; i < maxNumOracles; i++) {
counts[i] =1;
gas[i] =1;
}
s_oracleObservationsCounts = counts;
s_gasReimbursementsLinkWei = gas;
}
/**
* @notice emitted when billing parameters are set
* @param maximumGasPrice highest gas price for which transmitter will be compensated
* @param reasonableGasPrice transmitter will receive reward for gas prices under this value
* @param microLinkPerEth reimbursement per ETH of gas cost, in 1e-6LINK units
* @param linkGweiPerObservation reward to oracle for contributing an observation to a successfully transmitted report, in 1e-9LINK units
* @param linkGweiPerTransmission reward to transmitter of a successful report, in 1e-9LINK units
*/eventBillingSet(uint32 maximumGasPrice,
uint32 reasonableGasPrice,
uint32 microLinkPerEth,
uint32 linkGweiPerObservation,
uint32 linkGweiPerTransmission
);
functionsetBillingInternal(uint32 _maximumGasPrice,
uint32 _reasonableGasPrice,
uint32 _microLinkPerEth,
uint32 _linkGweiPerObservation,
uint32 _linkGweiPerTransmission
)
internal{
s_billing = Billing(_maximumGasPrice, _reasonableGasPrice, _microLinkPerEth,
_linkGweiPerObservation, _linkGweiPerTransmission);
emit BillingSet(_maximumGasPrice, _reasonableGasPrice, _microLinkPerEth,
_linkGweiPerObservation, _linkGweiPerTransmission);
}
/**
* @notice sets billing parameters
* @param _maximumGasPrice highest gas price for which transmitter will be compensated
* @param _reasonableGasPrice transmitter will receive reward for gas prices under this value
* @param _microLinkPerEth reimbursement per ETH of gas cost, in 1e-6LINK units
* @param _linkGweiPerObservation reward to oracle for contributing an observation to a successfully transmitted report, in 1e-9LINK units
* @param _linkGweiPerTransmission reward to transmitter of a successful report, in 1e-9LINK units
* @dev access control provided by billingAccessController
*/functionsetBilling(uint32 _maximumGasPrice,
uint32 _reasonableGasPrice,
uint32 _microLinkPerEth,
uint32 _linkGweiPerObservation,
uint32 _linkGweiPerTransmission
)
external{
AccessControllerInterface access = s_billingAccessController;
require(msg.sender== owner || access.hasAccess(msg.sender, msg.data),
"Only owner&billingAdmin can call");
payOracles();
setBillingInternal(_maximumGasPrice, _reasonableGasPrice, _microLinkPerEth,
_linkGweiPerObservation, _linkGweiPerTransmission);
}
/**
* @notice gets billing parameters
* @param maximumGasPrice highest gas price for which transmitter will be compensated
* @param reasonableGasPrice transmitter will receive reward for gas prices under this value
* @param microLinkPerEth reimbursement per ETH of gas cost, in 1e-6LINK units
* @param linkGweiPerObservation reward to oracle for contributing an observation to a successfully transmitted report, in 1e-9LINK units
* @param linkGweiPerTransmission reward to transmitter of a successful report, in 1e-9LINK units
*/functiongetBilling()
externalviewreturns (uint32 maximumGasPrice,
uint32 reasonableGasPrice,
uint32 microLinkPerEth,
uint32 linkGweiPerObservation,
uint32 linkGweiPerTransmission
)
{
Billing memory billing = s_billing;
return (
billing.maximumGasPrice,
billing.reasonableGasPrice,
billing.microLinkPerEth,
billing.linkGweiPerObservation,
billing.linkGweiPerTransmission
);
}
/**
* @notice emitted when a new access-control contract is set
* @param old the address prior to the current setting
* @param current the address of the new access-control contract
*/eventBillingAccessControllerSet(AccessControllerInterface old, AccessControllerInterface current);
functionsetBillingAccessControllerInternal(AccessControllerInterface _billingAccessController)
internal{
AccessControllerInterface oldController = s_billingAccessController;
if (_billingAccessController != oldController) {
s_billingAccessController = _billingAccessController;
emit BillingAccessControllerSet(
oldController,
_billingAccessController
);
}
}
/**
* @notice sets billingAccessController
* @param _billingAccessController new billingAccessController contract address
* @dev only owner can call this
*/functionsetBillingAccessController(AccessControllerInterface _billingAccessController)
externalonlyOwner{
setBillingAccessControllerInternal(_billingAccessController);
}
/**
* @notice gets billingAccessController
* @return address of billingAccessController contract
*/functionbillingAccessController()
externalviewreturns (AccessControllerInterface)
{
return s_billingAccessController;
}
/**
* @notice withdraws an oracle's payment from the contract
* @param _transmitter the transmitter address of the oracle
* @dev must be called by oracle's payee address
*/functionwithdrawPayment(address _transmitter)
external{
require(msg.sender== s_payees[_transmitter], "Only payee can withdraw");
payOracle(_transmitter);
}
/**
* @notice query an oracle's payment amount
* @param _transmitter the transmitter address of the oracle
*/functionowedPayment(address _transmitter)
publicviewreturns (uint256)
{
Oracle memory oracle = s_oracles[_transmitter];
if (oracle.role == Role.Unset) { return0; }
Billing memory billing = s_billing;
uint256 linkWeiAmount =uint256(s_oracleObservationsCounts[oracle.index] -1) *uint256(billing.linkGweiPerObservation) *
(1gwei);
linkWeiAmount += s_gasReimbursementsLinkWei[oracle.index] -1;
return linkWeiAmount;
}
/**
* @notice emitted when an oracle has been paid LINK
* @param transmitter address from which the oracle sends reports to the transmit method
* @param payee address to which the payment is sent
* @param amount amount of LINK sent
*/eventOraclePaid(address transmitter, address payee, uint256 amount);
// payOracle pays out _transmitter's balance to the corresponding payee, and zeros it outfunctionpayOracle(address _transmitter)
internal{
Oracle memory oracle = s_oracles[_transmitter];
uint256 linkWeiAmount = owedPayment(_transmitter);
if (linkWeiAmount >0) {
address payee = s_payees[_transmitter];
// Poses no re-entrancy issues, because LINK.transfer does not yield// control flow.require(LINK.transfer(payee, linkWeiAmount), "insufficient funds");
s_oracleObservationsCounts[oracle.index] =1; // "zero" the counts. see var's docstring
s_gasReimbursementsLinkWei[oracle.index] =1; // "zero" the counts. see var's docstringemit OraclePaid(_transmitter, payee, linkWeiAmount);
}
}
// payOracles pays out all transmitters, and zeros out their balances.//// It's much more gas-efficient to do this as a single operation, to avoid// hitting storage too much.functionpayOracles()
internal{
Billing memory billing = s_billing;
uint16[maxNumOracles] memory observationsCounts = s_oracleObservationsCounts;
uint256[maxNumOracles] memory gasReimbursementsLinkWei =
s_gasReimbursementsLinkWei;
address[] memory transmitters = s_transmitters;
for (uint transmitteridx =0; transmitteridx < transmitters.length; transmitteridx++) {
uint256 reimbursementAmountLinkWei = gasReimbursementsLinkWei[transmitteridx] -1;
uint256 obsCount = observationsCounts[transmitteridx] -1;
uint256 linkWeiAmount =
obsCount *uint256(billing.linkGweiPerObservation) * (1gwei) + reimbursementAmountLinkWei;
if (linkWeiAmount >0) {
address payee = s_payees[transmitters[transmitteridx]];
// Poses no re-entrancy issues, because LINK.transfer does not yield// control flow.require(LINK.transfer(payee, linkWeiAmount), "insufficient funds");
observationsCounts[transmitteridx] =1; // "zero" the counts.
gasReimbursementsLinkWei[transmitteridx] =1; // "zero" the counts.emit OraclePaid(transmitters[transmitteridx], payee, linkWeiAmount);
}
}
// "Zero" the accounting storage variables
s_oracleObservationsCounts = observationsCounts;
s_gasReimbursementsLinkWei = gasReimbursementsLinkWei;
}
functionoracleRewards(bytesmemory observers,
uint16[maxNumOracles] memory observations
)
internalpurereturns (uint16[maxNumOracles] memory)
{
// reward each observer-participant with the observer rewardfor (uint obsIdx =0; obsIdx < observers.length; obsIdx++) {
uint8 observer =uint8(observers[obsIdx]);
observations[observer] = saturatingAddUint16(observations[observer], 1);
}
return observations;
}
// This value needs to change if maxNumOracles is increased, or the accounting// calculations at the bottom of reimburseAndRewardOracles change.//// To recalculate it, run the profiler as described in// ../../profile/README.md, and add up the gas-usage values reported for the// lines in reimburseAndRewardOracles following the "gasLeft = gasleft()"// line. E.g., you will see output like this://// 7 uint256 gasLeft = gasleft();// 29 uint256 gasCostEthWei = transmitterGasCostEthWei(// 9 uint256(initialGas),// 3 gasPrice,// 3 callDataGasCost,// 3 gasLeft// .// .// .// 59 uint256 gasCostLinkWei = (gasCostEthWei * billing.microLinkPerEth)/ 1e6;// .// .// .// 5047 s_gasReimbursementsLinkWei[txOracle.index] =// 856 s_gasReimbursementsLinkWei[txOracle.index] + gasCostLinkWei +// 26 uint256(billing.linkGweiPerTransmission) * (1 gwei);//// If those were the only lines to be accounted for, you would add up// 29+9+3+3+3+59+5047+856+26=6035.uint256internalconstant accountingGasCost =6035;
// Uncomment the following declaration to compute the remaining gas cost after// above gasleft(). (This must exist in a base class to OffchainAggregator, so// it can't go in TestOffchainAggregator.)//// uint256 public gasUsedInAccounting;// Gas price at which the transmitter should be reimbursed, in ETH-gwei/gasfunctionimpliedGasPrice(uint256 txGasPrice, // ETH-gwei/gas unitsuint256 reasonableGasPrice, // ETH-gwei/gas unitsuint256 maximumGasPrice // ETH-gwei/gas units)
internalpurereturns (uint256)
{
// Reward the transmitter for choosing an efficient gas price: if they manage// to come in lower than considered reasonable, give them half the savings.//// The following calculations are all in units of gwei/gas, i.e. 1e-9ETH/gasuint256 gasPrice = txGasPrice;
if (txGasPrice < reasonableGasPrice) {
// Give transmitter half the savings for coming in under the reasonable gas price
gasPrice += (reasonableGasPrice - txGasPrice) /2;
}
// Don't reimburse a gas price higher than maximumGasPricereturn min(gasPrice, maximumGasPrice);
}
// gas reimbursement due the transmitter, in ETH-wei//// If this function is changed, accountingGasCost needs to change, too. See// its docstringfunctiontransmitterGasCostEthWei(uint256 initialGas,
uint256 gasPrice, // ETH-gwei/gas unitsuint256 callDataCost, // gas unitsuint256 gasLeft
)
internalpurereturns (uint128 gasCostEthWei)
{
require(initialGas >= gasLeft, "gasLeft cannot exceed initialGas");
uint256 gasUsed =// gas units
initialGas - gasLeft +// observed gas usage
callDataCost + accountingGasCost; // estimated gas usage// gasUsed is in gas units, gasPrice is in ETH-gwei/gas units; convert to ETH-weiuint256 fullGasCostEthWei = gasUsed * gasPrice * (1gwei);
assert(fullGasCostEthWei < maxUint128); // the entire ETH supply fits in a uint128...returnuint128(fullGasCostEthWei);
}
/**
* @notice withdraw any available funds left in the contract, up to _amount, after accounting for the funds due to participants in past reports
* @param _recipient address to send funds to
* @param _amount maximum amount to withdraw, denominated in LINK-wei.
* @dev access control provided by billingAccessController
*/functionwithdrawFunds(address _recipient, uint256 _amount)
external{
require(msg.sender== owner || s_billingAccessController.hasAccess(msg.sender, msg.data),
"Only owner&billingAdmin can call");
uint256 linkDue = totalLINKDue();
uint256 linkBalance = LINK.balanceOf(address(this));
require(linkBalance >= linkDue, "insufficient balance");
require(LINK.transfer(_recipient, min(linkBalance - linkDue, _amount)), "insufficient funds");
}
// Total LINK due to participants in past reports.functiontotalLINKDue()
internalviewreturns (uint256 linkDue)
{
// Argument for overflow safety: We do all computations in// uint256s. The inputs to linkDue are:// - the <= 31 observation rewards each of which has less than// 64 bits (32 bits for billing.linkGweiPerObservation, 32 bits// for wei/gwei conversion). Hence 69 bits are sufficient for this part.// - the <= 31 gas reimbursements, each of which consists of at most 166// bits (see s_gasReimbursementsLinkWei docstring). Hence 171 bits are// sufficient for this part// In total, 172 bits are enough.uint16[maxNumOracles] memory observationCounts = s_oracleObservationsCounts;
for (uint i =0; i < maxNumOracles; i++) {
linkDue += observationCounts[i] -1; // Stored value is one greater than actual value
}
Billing memory billing = s_billing;
// Convert linkGweiPerObservation to uint256, or this overflows!
linkDue *=uint256(billing.linkGweiPerObservation) * (1gwei);
address[] memory transmitters = s_transmitters;
uint256[maxNumOracles] memory gasReimbursementsLinkWei =
s_gasReimbursementsLinkWei;
for (uint i =0; i < transmitters.length; i++) {
linkDue +=uint256(gasReimbursementsLinkWei[i]-1); // Stored value is one greater than actual value
}
}
/**
* @notice allows oracles to check that sufficient LINK balance is available
* @return availableBalance LINK available on this contract, after accounting for outstanding obligations. can become negative
*/functionlinkAvailableForPayment()
externalviewreturns (int256 availableBalance)
{
// there are at most one billion LINK, so this cast is safeint256 balance =int256(LINK.balanceOf(address(this)));
// according to the argument in the definition of totalLINKDue,// totalLINKDue is never greater than 2**172, so this cast is safeint256 due =int256(totalLINKDue());
// safe from overflow according to above sizesreturnint256(balance) -int256(due);
}
/**
* @notice number of observations oracle is due to be reimbursed for
* @param _signerOrTransmitter address used by oracle for signing or transmitting reports
*/functionoracleObservationCount(address _signerOrTransmitter)
externalviewreturns (uint16)
{
Oracle memory oracle = s_oracles[_signerOrTransmitter];
if (oracle.role == Role.Unset) { return0; }
return s_oracleObservationsCounts[oracle.index] -1;
}
functionreimburseAndRewardOracles(uint32 initialGas,
bytesmemory observers
)
internal{
Oracle memory txOracle = s_oracles[msg.sender];
Billing memory billing = s_billing;
// Reward oracles for providing observations. Oracles are not rewarded// for providing signatures, because signing is essentially free.
s_oracleObservationsCounts =
oracleRewards(observers, s_oracleObservationsCounts);
// Reimburse transmitter of the report for gas usagerequire(txOracle.role == Role.Transmitter,
"sent by undesignated transmitter"
);
uint256 gasPrice = impliedGasPrice(
tx.gasprice/ (1gwei), // convert to ETH-gwei units
billing.reasonableGasPrice,
billing.maximumGasPrice
);
// The following is only an upper bound, as it ignores the cheaper cost for// 0 bytes. Safe from overflow, because calldata just isn't that long.uint256 callDataGasCost =16*msg.data.length;
// If any changes are made to subsequent calculations, accountingGasCost// needs to change, too.uint256 gasLeft =gasleft();
uint256 gasCostEthWei = transmitterGasCostEthWei(
uint256(initialGas),
gasPrice,
callDataGasCost,
gasLeft
);
// microLinkPerEth is 1e-6LINK/ETH units, gasCostEthWei is 1e-18ETH units// (ETH-wei), product is 1e-24LINK-wei units, dividing by 1e6 gives// 1e-18LINK units, i.e. LINK-wei units// Safe from over/underflow, since all components are non-negative,// gasCostEthWei will always fit into uint128 and microLinkPerEth is a// uint32 (128+32 < 256!).uint256 gasCostLinkWei = (gasCostEthWei * billing.microLinkPerEth)/1e6;
// Safe from overflow, because gasCostLinkWei < 2**160 and// billing.linkGweiPerTransmission * (1 gwei) < 2**64 and we increment// s_gasReimbursementsLinkWei[txOracle.index] at most 2**40 times.
s_gasReimbursementsLinkWei[txOracle.index] =
s_gasReimbursementsLinkWei[txOracle.index] + gasCostLinkWei +uint256(billing.linkGweiPerTransmission) * (1gwei); // convert from linkGwei to linkWei// Uncomment next line to compute the remaining gas cost after above gasleft().// See OffchainAggregatorBilling.accountingGasCost docstring for more information.//// gasUsedInAccounting = gasLeft - gasleft();
}
/*
* Payee management
*//**
* @notice emitted when a transfer of an oracle's payee address has been initiated
* @param transmitter address from which the oracle sends reports to the transmit method
* @param current the payeee address for the oracle, prior to this setting
* @param proposed the proposed new payee address for the oracle
*/eventPayeeshipTransferRequested(addressindexed transmitter,
addressindexed current,
addressindexed proposed
);
/**
* @notice emitted when a transfer of an oracle's payee address has been completed
* @param transmitter address from which the oracle sends reports to the transmit method
* @param current the payeee address for the oracle, prior to this setting
*/eventPayeeshipTransferred(addressindexed transmitter,
addressindexed previous,
addressindexed current
);
/**
* @notice sets the payees for transmitting addresses
* @param _transmitters addresses oracles use to transmit the reports
* @param _payees addresses of payees corresponding to list of transmitters
* @dev must be called by owner
* @dev cannot be used to change payee addresses, only to initially populate them
*/functionsetPayees(address[] calldata _transmitters,
address[] calldata _payees
)
externalonlyOwner()
{
require(_transmitters.length== _payees.length, "transmitters.size != payees.size");
for (uint i =0; i < _transmitters.length; i++) {
address transmitter = _transmitters[i];
address payee = _payees[i];
address currentPayee = s_payees[transmitter];
bool zeroedOut = currentPayee ==address(0);
require(zeroedOut || currentPayee == payee, "payee already set");
s_payees[transmitter] = payee;
if (currentPayee != payee) {
emit PayeeshipTransferred(transmitter, currentPayee, payee);
}
}
}
/**
* @notice first step of payeeship transfer (safe transfer pattern)
* @param _transmitter transmitter address of oracle whose payee is changing
* @param _proposed new payee address
* @dev can only be called by payee address
*/functiontransferPayeeship(address _transmitter,
address _proposed
)
external{
require(msg.sender== s_payees[_transmitter], "only current payee can update");
require(msg.sender!= _proposed, "cannot transfer to self");
address previousProposed = s_proposedPayees[_transmitter];
s_proposedPayees[_transmitter] = _proposed;
if (previousProposed != _proposed) {
emit PayeeshipTransferRequested(_transmitter, msg.sender, _proposed);
}
}
/**
* @notice second step of payeeship transfer (safe transfer pattern)
* @param _transmitter transmitter address of oracle whose payee is changing
* @dev can only be called by proposed new payee address
*/functionacceptPayeeship(address _transmitter
)
external{
require(msg.sender== s_proposedPayees[_transmitter], "only proposed payees can accept");
address currentPayee = s_payees[_transmitter];
s_payees[_transmitter] =msg.sender;
s_proposedPayees[_transmitter] =address(0);
emit PayeeshipTransferred(_transmitter, currentPayee, msg.sender);
}
/*
* Helper functions
*/functionsaturatingAddUint16(uint16 _x, uint16 _y)
internalpurereturns (uint16)
{
returnuint16(min(uint256(_x)+uint256(_y), maxUint16));
}
functionmin(uint256 a, uint256 b)
internalpurereturns (uint256)
{
if (a < b) { return a; }
return b;
}
}
Contract Source Code
File 10 of 13: Owned.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;/**
* @title The Owned contract
* @notice A contract with helpers for basic contract ownership.
*/contractOwned{
addresspayablepublic owner;
addressprivate pendingOwner;
eventOwnershipTransferRequested(addressindexedfrom,
addressindexed to
);
eventOwnershipTransferred(addressindexedfrom,
addressindexed to
);
constructor() {
owner =msg.sender;
}
/**
* @dev Allows an owner to begin transferring ownership to a new address,
* pending.
*/functiontransferOwnership(address _to)
externalonlyOwner()
{
pendingOwner = _to;
emit OwnershipTransferRequested(owner, _to);
}
/**
* @dev Allows an ownership transfer to be completed by the recipient.
*/functionacceptOwnership()
external{
require(msg.sender== pendingOwner, "Must be proposed owner");
address oldOwner = owner;
owner =msg.sender;
pendingOwner =address(0);
emit OwnershipTransferred(oldOwner, msg.sender);
}
/**
* @dev Reverts if called by anyone other than the contract owner.
*/modifieronlyOwner() {
require(msg.sender== owner, "Only callable by owner");
_;
}
}
Contract Source Code
File 11 of 13: SimpleReadAccessController.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.7.1;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.
*/contractSimpleReadAccessControllerisSimpleWriteAccessController{
/**
* @notice Returns the access of an address
* @param _user The address to query
*/functionhasAccess(address _user,
bytesmemory _calldata
)
publicviewvirtualoverridereturns (bool)
{
returnsuper.hasAccess(_user, _calldata) || _user ==tx.origin;
}
}
Contract Source Code
File 12 of 13: SimpleWriteAccessController.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.7.0;import"./Owned.sol";
import"./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.
*/contractSimpleWriteAccessControllerisAccessControllerInterface, Owned{
boolpublic checkEnabled;
mapping(address=>bool) internal accessList;
eventAddedAccess(address user);
eventRemovedAccess(address user);
eventCheckAccessEnabled();
eventCheckAccessDisabled();
constructor()
{
checkEnabled =true;
}
/**
* @notice Returns the access of an address
* @param _user The address to query
*/functionhasAccess(address _user,
bytesmemory)
publicviewvirtualoverridereturns (bool)
{
return accessList[_user] ||!checkEnabled;
}
/**
* @notice Adds an address to the access list
* @param _user The address to add
*/functionaddAccess(address _user) externalonlyOwner() {
addAccessInternal(_user);
}
functionaddAccessInternal(address _user) internal{
if (!accessList[_user]) {
accessList[_user] =true;
emit AddedAccess(_user);
}
}
/**
* @notice Removes an address from the access list
* @param _user The address to remove
*/functionremoveAccess(address _user)
externalonlyOwner()
{
if (accessList[_user]) {
accessList[_user] =false;
emit RemovedAccess(_user);
}
}
/**
* @notice makes the access check enforced
*/functionenableAccessCheck()
externalonlyOwner()
{
if (!checkEnabled) {
checkEnabled =true;
emit CheckAccessEnabled();
}
}
/**
* @notice makes the access check unenforced
*/functiondisableAccessCheck()
externalonlyOwner()
{
if (checkEnabled) {
checkEnabled =false;
emit CheckAccessDisabled();
}
}
/**
* @dev reverts if the caller does not have access
*/modifiercheckAccess() {
require(hasAccess(msg.sender, msg.data), "No access");
_;
}
}