/*
____ __ __ __ _
/ __/__ __ ___ / /_ / / ___ / /_ (_)__ __
_\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ /
/___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\
/___/
* Synthetix: ExchangeRates.sol
*
* Latest source (may be newer): https://github.com/Synthetixio/synthetix/blob/master/contracts/ExchangeRates.sol
* Docs: https://docs.synthetix.io/contracts/ExchangeRates
*
* Contract Dependencies:
* - IExchangeRates
* - Owned
* - SelfDestructible
* Libraries:
* - SafeDecimalMath
* - SafeMath
*
* MIT License
* ===========
*
* Copyright (c) 2020 Synthetix
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*/
/* ===============================================
* Flattened with Solidifier by Coinage
*
* https://solidifier.coina.ge
* ===============================================
*/
pragma solidity ^0.5.16;
// https://docs.synthetix.io/contracts/Owned
contract Owned {
address public owner;
address public nominatedOwner;
constructor(address _owner) public {
require(_owner != address(0), "Owner address cannot be 0");
owner = _owner;
emit OwnerChanged(address(0), _owner);
}
function nominateNewOwner(address _owner) external onlyOwner {
nominatedOwner = _owner;
emit OwnerNominated(_owner);
}
function acceptOwnership() external {
require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
emit OwnerChanged(owner, nominatedOwner);
owner = nominatedOwner;
nominatedOwner = address(0);
}
modifier onlyOwner {
require(msg.sender == owner, "Only the contract owner may perform this action");
_;
}
event OwnerNominated(address newOwner);
event OwnerChanged(address oldOwner, address newOwner);
}
// Inheritance
// https://docs.synthetix.io/contracts/SelfDestructible
contract SelfDestructible is Owned {
uint public constant SELFDESTRUCT_DELAY = 4 weeks;
uint public initiationTime;
bool public selfDestructInitiated;
address public selfDestructBeneficiary;
constructor() internal {
// This contract is abstract, and thus cannot be instantiated directly
require(owner != address(0), "Owner must be set");
selfDestructBeneficiary = owner;
emit SelfDestructBeneficiaryUpdated(owner);
}
/**
* @notice Set the beneficiary address of this contract.
* @dev Only the contract owner may call this. The provided beneficiary must be non-null.
* @param _beneficiary The address to pay any eth contained in this contract to upon self-destruction.
*/
function setSelfDestructBeneficiary(address payable _beneficiary) external onlyOwner {
require(_beneficiary != address(0), "Beneficiary must not be zero");
selfDestructBeneficiary = _beneficiary;
emit SelfDestructBeneficiaryUpdated(_beneficiary);
}
/**
* @notice Begin the self-destruction counter of this contract.
* Once the delay has elapsed, the contract may be self-destructed.
* @dev Only the contract owner may call this.
*/
function initiateSelfDestruct() external onlyOwner {
initiationTime = now;
selfDestructInitiated = true;
emit SelfDestructInitiated(SELFDESTRUCT_DELAY);
}
/**
* @notice Terminate and reset the self-destruction timer.
* @dev Only the contract owner may call this.
*/
function terminateSelfDestruct() external onlyOwner {
initiationTime = 0;
selfDestructInitiated = false;
emit SelfDestructTerminated();
}
/**
* @notice If the self-destruction delay has elapsed, destroy this contract and
* remit any ether it owns to the beneficiary address.
* @dev Only the contract owner may call this.
*/
function selfDestruct() external onlyOwner {
require(selfDestructInitiated, "Self Destruct not yet initiated");
require(initiationTime + SELFDESTRUCT_DELAY < now, "Self destruct delay not met");
emit SelfDestructed(selfDestructBeneficiary);
selfdestruct(address(uint160(selfDestructBeneficiary)));
}
event SelfDestructTerminated();
event SelfDestructed(address beneficiary);
event SelfDestructInitiated(uint selfDestructDelay);
event SelfDestructBeneficiaryUpdated(address newBeneficiary);
}
// https://docs.synthetix.io/contracts/source/interfaces/IExchangeRates
interface IExchangeRates {
// Views
function aggregators(bytes32 currencyKey) external view returns (address);
function anyRateIsStale(bytes32[] calldata currencyKeys) external view returns (bool);
function currentRoundForRate(bytes32 currencyKey) external view returns (uint);
function effectiveValue(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external view returns (uint value);
function effectiveValueAndRates(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint value,
uint sourceRate,
uint destinationRate
);
function effectiveValueAtRound(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
uint roundIdForSrc,
uint roundIdForDest
) external view returns (uint value);
function getCurrentRoundId(bytes32 currencyKey) external view returns (uint);
function getLastRoundIdBeforeElapsedSecs(
bytes32 currencyKey,
uint startingRoundId,
uint startingTimestamp,
uint timediff
) external view returns (uint);
function inversePricing(bytes32 currencyKey)
external
view
returns (
uint entryPoint,
uint upperLimit,
uint lowerLimit,
bool frozen
);
function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256);
function oracle() external view returns (address);
function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time);
function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time);
function rateForCurrency(bytes32 currencyKey) external view returns (uint);
function rateIsFrozen(bytes32 currencyKey) external view returns (bool);
function rateIsStale(bytes32 currencyKey) external view returns (bool);
function rateStalePeriod() external view returns (uint);
function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds)
external
view
returns (uint[] memory rates, uint[] memory times);
function ratesAndStaleForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory, bool);
function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory);
}
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
// Libraries
// https://docs.synthetix.io/contracts/SafeDecimalMath
library SafeDecimalMath {
using SafeMath for uint;
/* Number of decimal places in the representations. */
uint8 public constant decimals = 18;
uint8 public constant highPrecisionDecimals = 27;
/* The number representing 1.0. */
uint public constant UNIT = 10**uint(decimals);
/* The number representing 1.0 for higher fidelity numbers. */
uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals);
uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals);
/**
* @return Provides an interface to UNIT.
*/
function unit() external pure returns (uint) {
return UNIT;
}
/**
* @return Provides an interface to PRECISE_UNIT.
*/
function preciseUnit() external pure returns (uint) {
return PRECISE_UNIT;
}
/**
* @return The result of multiplying x and y, interpreting the operands as fixed-point
* decimals.
*
* @dev A unit factor is divided out after the product of x and y is evaluated,
* so that product must be less than 2**256. As this is an integer division,
* the internal division always rounds down. This helps save on gas. Rounding
* is more expensive on gas.
*/
function multiplyDecimal(uint x, uint y) internal pure returns (uint) {
/* Divide by UNIT to remove the extra factor introduced by the product. */
return x.mul(y) / UNIT;
}
/**
* @return The result of safely multiplying x and y, interpreting the operands
* as fixed-point decimals of the specified precision unit.
*
* @dev The operands should be in the form of a the specified unit factor which will be
* divided out after the product of x and y is evaluated, so that product must be
* less than 2**256.
*
* Unlike multiplyDecimal, this function rounds the result to the nearest increment.
* Rounding is useful when you need to retain fidelity for small decimal numbers
* (eg. small fractions or percentages).
*/
function _multiplyDecimalRound(
uint x,
uint y,
uint precisionUnit
) private pure returns (uint) {
/* Divide by UNIT to remove the extra factor introduced by the product. */
uint quotientTimesTen = x.mul(y) / (precisionUnit / 10);
if (quotientTimesTen % 10 >= 5) {
quotientTimesTen += 10;
}
return quotientTimesTen / 10;
}
/**
* @return The result of safely multiplying x and y, interpreting the operands
* as fixed-point decimals of a precise unit.
*
* @dev The operands should be in the precise unit factor which will be
* divided out after the product of x and y is evaluated, so that product must be
* less than 2**256.
*
* Unlike multiplyDecimal, this function rounds the result to the nearest increment.
* Rounding is useful when you need to retain fidelity for small decimal numbers
* (eg. small fractions or percentages).
*/
function multiplyDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
return _multiplyDecimalRound(x, y, PRECISE_UNIT);
}
/**
* @return The result of safely multiplying x and y, interpreting the operands
* as fixed-point decimals of a standard unit.
*
* @dev The operands should be in the standard unit factor which will be
* divided out after the product of x and y is evaluated, so that product must be
* less than 2**256.
*
* Unlike multiplyDecimal, this function rounds the result to the nearest increment.
* Rounding is useful when you need to retain fidelity for small decimal numbers
* (eg. small fractions or percentages).
*/
function multiplyDecimalRound(uint x, uint y) internal pure returns (uint) {
return _multiplyDecimalRound(x, y, UNIT);
}
/**
* @return The result of safely dividing x and y. The return value is a high
* precision decimal.
*
* @dev y is divided after the product of x and the standard precision unit
* is evaluated, so the product of x and UNIT must be less than 2**256. As
* this is an integer division, the result is always rounded down.
* This helps save on gas. Rounding is more expensive on gas.
*/
function divideDecimal(uint x, uint y) internal pure returns (uint) {
/* Reintroduce the UNIT factor that will be divided out by y. */
return x.mul(UNIT).div(y);
}
/**
* @return The result of safely dividing x and y. The return value is as a rounded
* decimal in the precision unit specified in the parameter.
*
* @dev y is divided after the product of x and the specified precision unit
* is evaluated, so the product of x and the specified precision unit must
* be less than 2**256. The result is rounded to the nearest increment.
*/
function _divideDecimalRound(
uint x,
uint y,
uint precisionUnit
) private pure returns (uint) {
uint resultTimesTen = x.mul(precisionUnit * 10).div(y);
if (resultTimesTen % 10 >= 5) {
resultTimesTen += 10;
}
return resultTimesTen / 10;
}
/**
* @return The result of safely dividing x and y. The return value is as a rounded
* standard precision decimal.
*
* @dev y is divided after the product of x and the standard precision unit
* is evaluated, so the product of x and the standard precision unit must
* be less than 2**256. The result is rounded to the nearest increment.
*/
function divideDecimalRound(uint x, uint y) internal pure returns (uint) {
return _divideDecimalRound(x, y, UNIT);
}
/**
* @return The result of safely dividing x and y. The return value is as a rounded
* high precision decimal.
*
* @dev y is divided after the product of x and the high precision unit
* is evaluated, so the product of x and the high precision unit must
* be less than 2**256. The result is rounded to the nearest increment.
*/
function divideDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
return _divideDecimalRound(x, y, PRECISE_UNIT);
}
/**
* @dev Convert a standard decimal representation to a high precision one.
*/
function decimalToPreciseDecimal(uint i) internal pure returns (uint) {
return i.mul(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR);
}
/**
* @dev Convert a high precision decimal to a standard decimal representation.
*/
function preciseDecimalToDecimal(uint i) internal pure returns (uint) {
uint quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10);
if (quotientTimesTen % 10 >= 5) {
quotientTimesTen += 10;
}
return quotientTimesTen / 10;
}
}
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 timestamp);
event NewRound(uint256 indexed roundId, address indexed startedBy);
}
// Inheritance
// Libraries
// Internal references
// AggregatorInterface from Chainlink represents a decentralized pricing network for a single currency key
// https://docs.synthetix.io/contracts/source/contracts/ExchangeRates
contract ExchangeRates is Owned, SelfDestructible, IExchangeRates {
using SafeMath for uint;
using SafeDecimalMath for uint;
struct RateAndUpdatedTime {
uint216 rate;
uint40 time;
}
// Exchange rates and update times stored by currency code, e.g. 'SNX', or 'sUSD'
mapping(bytes32 => mapping(uint => RateAndUpdatedTime)) private _rates;
// The address of the oracle which pushes rate updates to this contract
address public oracle;
// Decentralized oracle networks that feed into pricing aggregators
mapping(bytes32 => AggregatorInterface) public aggregators;
// List of aggregator keys for convenient iteration
bytes32[] public aggregatorKeys;
// Do not allow the oracle to submit times any further forward into the future than this constant.
uint private constant ORACLE_FUTURE_LIMIT = 10 minutes;
// How long will the contract assume the rate of any asset is correct
uint public rateStalePeriod = 3 hours;
// For inverted prices, keep a mapping of their entry, limits and frozen status
struct InversePricing {
uint entryPoint;
uint upperLimit;
uint lowerLimit;
bool frozen;
}
mapping(bytes32 => InversePricing) public inversePricing;
bytes32[] public invertedKeys;
mapping(bytes32 => uint) public currentRoundForRate;
//
// ========== CONSTRUCTOR ==========
constructor(
address _owner,
address _oracle,
bytes32[] memory _currencyKeys,
uint[] memory _newRates
) public Owned(_owner) SelfDestructible() {
require(_currencyKeys.length == _newRates.length, "Currency key length and rate length must match.");
oracle = _oracle;
// The sUSD rate is always 1 and is never stale.
_setRate("sUSD", SafeDecimalMath.unit(), now);
internalUpdateRates(_currencyKeys, _newRates, now);
}
/* ========== SETTERS ========== */
function setOracle(address _oracle) external onlyOwner {
oracle = _oracle;
emit OracleUpdated(oracle);
}
function setRateStalePeriod(uint _time) external onlyOwner {
rateStalePeriod = _time;
emit RateStalePeriodUpdated(rateStalePeriod);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function updateRates(
bytes32[] calldata currencyKeys,
uint[] calldata newRates,
uint timeSent
) external onlyOracle returns (bool) {
return internalUpdateRates(currencyKeys, newRates, timeSent);
}
function deleteRate(bytes32 currencyKey) external onlyOracle {
require(_getRate(currencyKey) > 0, "Rate is zero");
delete _rates[currencyKey][currentRoundForRate[currencyKey]];
currentRoundForRate[currencyKey]--;
emit RateDeleted(currencyKey);
}
function setInversePricing(
bytes32 currencyKey,
uint entryPoint,
uint upperLimit,
uint lowerLimit,
bool freeze,
bool freezeAtUpperLimit
) external onlyOwner {
// 0 < lowerLimit < entryPoint => 0 < entryPoint
require(lowerLimit > 0, "lowerLimit must be above 0");
require(upperLimit > entryPoint, "upperLimit must be above the entryPoint");
require(upperLimit < entryPoint.mul(2), "upperLimit must be less than double entryPoint");
require(lowerLimit < entryPoint, "lowerLimit must be below the entryPoint");
if (inversePricing[currencyKey].entryPoint <= 0) {
// then we are adding a new inverse pricing, so add this
invertedKeys.push(currencyKey);
}
inversePricing[currencyKey].entryPoint = entryPoint;
inversePricing[currencyKey].upperLimit = upperLimit;
inversePricing[currencyKey].lowerLimit = lowerLimit;
inversePricing[currencyKey].frozen = freeze;
emit InversePriceConfigured(currencyKey, entryPoint, upperLimit, lowerLimit);
// When indicating to freeze, we need to know the rate to freeze it at - either upper or lower
// this is useful in situations where ExchangeRates is updated and there are existing inverted
// rates already frozen in the current contract that need persisting across the upgrade
if (freeze) {
emit InversePriceFrozen(currencyKey);
_setRate(currencyKey, freezeAtUpperLimit ? upperLimit : lowerLimit, now);
}
}
function removeInversePricing(bytes32 currencyKey) external onlyOwner {
require(inversePricing[currencyKey].entryPoint > 0, "No inverted price exists");
inversePricing[currencyKey].entryPoint = 0;
inversePricing[currencyKey].upperLimit = 0;
inversePricing[currencyKey].lowerLimit = 0;
inversePricing[currencyKey].frozen = false;
// now remove inverted key from array
bool wasRemoved = removeFromArray(currencyKey, invertedKeys);
if (wasRemoved) {
emit InversePriceConfigured(currencyKey, 0, 0, 0);
}
}
function addAggregator(bytes32 currencyKey, address aggregatorAddress) external onlyOwner {
AggregatorInterface aggregator = AggregatorInterface(aggregatorAddress);
// This check tries to make sure that a valid aggregator is being added.
// It checks if the aggregator is an existing smart contract that has implemented `latestTimestamp` function.
require(aggregator.latestTimestamp() >= 0, "Given Aggregator is invalid");
if (address(aggregators[currencyKey]) == address(0)) {
aggregatorKeys.push(currencyKey);
}
aggregators[currencyKey] = aggregator;
emit AggregatorAdded(currencyKey, address(aggregator));
}
function removeAggregator(bytes32 currencyKey) external onlyOwner {
address aggregator = address(aggregators[currencyKey]);
require(aggregator != address(0), "No aggregator exists for key");
delete aggregators[currencyKey];
bool wasRemoved = removeFromArray(currencyKey, aggregatorKeys);
if (wasRemoved) {
emit AggregatorRemoved(currencyKey, aggregator);
}
}
/* ========== VIEWS ========== */
function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time) {
RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey);
return (rateAndTime.rate, rateAndTime.time);
}
function getLastRoundIdBeforeElapsedSecs(
bytes32 currencyKey,
uint startingRoundId,
uint startingTimestamp,
uint timediff
) external view returns (uint) {
uint roundId = startingRoundId;
uint nextTimestamp = 0;
while (true) {
(, nextTimestamp) = _getRateAndTimestampAtRound(currencyKey, roundId + 1);
// if there's no new round, then the previous roundId was the latest
if (nextTimestamp == 0 || nextTimestamp > startingTimestamp + timediff) {
return roundId;
}
roundId++;
}
return roundId;
}
function getCurrentRoundId(bytes32 currencyKey) external view returns (uint) {
return _getCurrentRoundId(currencyKey);
}
function effectiveValueAtRound(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
uint roundIdForSrc,
uint roundIdForDest
) external view returns (uint value) {
// If there's no change in the currency, then just return the amount they gave us
if (sourceCurrencyKey == destinationCurrencyKey) return sourceAmount;
(uint srcRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc);
(uint destRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest);
// Calculate the effective value by going from source -> USD -> destination
value = sourceAmount.multiplyDecimalRound(srcRate).divideDecimalRound(destRate);
}
function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time) {
return _getRateAndTimestampAtRound(currencyKey, roundId);
}
function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256) {
return _getUpdatedTime(currencyKey);
}
function lastRateUpdateTimesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory) {
uint[] memory lastUpdateTimes = new uint[](currencyKeys.length);
for (uint i = 0; i < currencyKeys.length; i++) {
lastUpdateTimes[i] = _getUpdatedTime(currencyKeys[i]);
}
return lastUpdateTimes;
}
function effectiveValue(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external view returns (uint value) {
(value, , ) = _effectiveValueAndRates(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);
}
function effectiveValueAndRates(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint value,
uint sourceRate,
uint destinationRate
)
{
return _effectiveValueAndRates(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);
}
function rateForCurrency(bytes32 currencyKey) external view returns (uint) {
return _getRateAndUpdatedTime(currencyKey).rate;
}
function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds)
external
view
returns (uint[] memory rates, uint[] memory times)
{
rates = new uint[](numRounds);
times = new uint[](numRounds);
uint roundId = _getCurrentRoundId(currencyKey);
for (uint i = 0; i < numRounds; i++) {
(rates[i], times[i]) = _getRateAndTimestampAtRound(currencyKey, roundId);
if (roundId == 0) {
// if we hit the last round, then return what we have
return (rates, times);
} else {
roundId--;
}
}
}
function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory) {
uint[] memory _localRates = new uint[](currencyKeys.length);
for (uint i = 0; i < currencyKeys.length; i++) {
_localRates[i] = _getRate(currencyKeys[i]);
}
return _localRates;
}
function ratesAndStaleForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory, bool) {
uint[] memory _localRates = new uint[](currencyKeys.length);
bool anyRateStale = false;
uint period = rateStalePeriod;
for (uint i = 0; i < currencyKeys.length; i++) {
RateAndUpdatedTime memory rateAndUpdateTime = _getRateAndUpdatedTime(currencyKeys[i]);
_localRates[i] = uint256(rateAndUpdateTime.rate);
if (!anyRateStale) {
anyRateStale = (currencyKeys[i] != "sUSD" && uint256(rateAndUpdateTime.time).add(period) < now);
}
}
return (_localRates, anyRateStale);
}
function rateIsStale(bytes32 currencyKey) external view returns (bool) {
// sUSD is a special case and is never stale.
if (currencyKey == "sUSD") return false;
return _getUpdatedTime(currencyKey).add(rateStalePeriod) < now;
}
function rateIsFrozen(bytes32 currencyKey) external view returns (bool) {
return inversePricing[currencyKey].frozen;
}
function anyRateIsStale(bytes32[] calldata currencyKeys) external view returns (bool) {
// Loop through each key and check whether the data point is stale.
uint256 i = 0;
while (i < currencyKeys.length) {
// sUSD is a special case and is never false
if (currencyKeys[i] != "sUSD" && _getUpdatedTime(currencyKeys[i]).add(rateStalePeriod) < now) {
return true;
}
i += 1;
}
return false;
}
/* ========== INTERNAL FUNCTIONS ========== */
function _setRate(
bytes32 currencyKey,
uint256 rate,
uint256 time
) internal {
// Note: this will effectively start the rounds at 1, which matches Chainlink's Agggregators
currentRoundForRate[currencyKey]++;
_rates[currencyKey][currentRoundForRate[currencyKey]] = RateAndUpdatedTime({
rate: uint216(rate),
time: uint40(time)
});
}
function internalUpdateRates(
bytes32[] memory currencyKeys,
uint[] memory newRates,
uint timeSent
) internal returns (bool) {
require(currencyKeys.length == newRates.length, "Currency key array length must match rates array length.");
require(timeSent < (now + ORACLE_FUTURE_LIMIT), "Time is too far into the future");
// Loop through each key and perform update.
for (uint i = 0; i < currencyKeys.length; i++) {
bytes32 currencyKey = currencyKeys[i];
// Should not set any rate to zero ever, as no asset will ever be
// truely worthless and still valid. In this scenario, we should
// delete the rate and remove it from the system.
require(newRates[i] != 0, "Zero is not a valid rate, please call deleteRate instead.");
require(currencyKey != "sUSD", "Rate of sUSD cannot be updated, it's always UNIT.");
// We should only update the rate if it's at least the same age as the last rate we've got.
if (timeSent < _getUpdatedTime(currencyKey)) {
continue;
}
newRates[i] = rateOrInverted(currencyKey, newRates[i]);
// Ok, go ahead with the update.
_setRate(currencyKey, newRates[i], timeSent);
}
emit RatesUpdated(currencyKeys, newRates);
return true;
}
function rateOrInverted(bytes32 currencyKey, uint rate) internal returns (uint) {
// if an inverse mapping exists, adjust the price accordingly
InversePricing storage inverse = inversePricing[currencyKey];
if (inverse.entryPoint <= 0) {
return rate;
}
// set the rate to the current rate initially (if it's frozen, this is what will be returned)
uint newInverseRate = _getRate(currencyKey);
// get the new inverted rate if not frozen
if (!inverse.frozen) {
uint doubleEntryPoint = inverse.entryPoint.mul(2);
if (doubleEntryPoint <= rate) {
// avoid negative numbers for unsigned ints, so set this to 0
// which by the requirement that lowerLimit be > 0 will
// cause this to freeze the price to the lowerLimit
newInverseRate = 0;
} else {
newInverseRate = doubleEntryPoint.sub(rate);
}
// now if new rate hits our limits, set it to the limit and freeze
if (newInverseRate >= inverse.upperLimit) {
newInverseRate = inverse.upperLimit;
} else if (newInverseRate <= inverse.lowerLimit) {
newInverseRate = inverse.lowerLimit;
}
if (newInverseRate == inverse.upperLimit || newInverseRate == inverse.lowerLimit) {
inverse.frozen = true;
emit InversePriceFrozen(currencyKey);
}
}
return newInverseRate;
}
function removeFromArray(bytes32 entry, bytes32[] storage array) internal returns (bool) {
for (uint i = 0; i < array.length; i++) {
if (array[i] == entry) {
delete array[i];
// Copy the last key into the place of the one we just deleted
// If there's only one key, this is array[0] = array[0].
// If we're deleting the last one, it's also a NOOP in the same way.
array[i] = array[array.length - 1];
// Decrease the size of the array by one.
array.length--;
return true;
}
}
return false;
}
function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) {
if (address(aggregators[currencyKey]) != address(0)) {
return
RateAndUpdatedTime({
rate: uint216(aggregators[currencyKey].latestAnswer() * 1e10),
time: uint40(aggregators[currencyKey].latestTimestamp())
});
} else {
return _rates[currencyKey][currentRoundForRate[currencyKey]];
}
}
function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) {
if (address(aggregators[currencyKey]) != address(0)) {
AggregatorInterface aggregator = aggregators[currencyKey];
return aggregator.latestRound();
} else {
return currentRoundForRate[currencyKey];
}
}
function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) {
if (address(aggregators[currencyKey]) != address(0)) {
AggregatorInterface aggregator = aggregators[currencyKey];
return (uint(aggregator.getAnswer(roundId) * 1e10), aggregator.getTimestamp(roundId));
} else {
RateAndUpdatedTime storage update = _rates[currencyKey][roundId];
return (update.rate, update.time);
}
}
function _getRate(bytes32 currencyKey) internal view returns (uint256) {
return _getRateAndUpdatedTime(currencyKey).rate;
}
function _getUpdatedTime(bytes32 currencyKey) internal view returns (uint256) {
return _getRateAndUpdatedTime(currencyKey).time;
}
function _effectiveValueAndRates(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
)
internal
view
returns (
uint value,
uint sourceRate,
uint destinationRate
)
{
sourceRate = _getRate(sourceCurrencyKey);
// If there's no change in the currency, then just return the amount they gave us
if (sourceCurrencyKey == destinationCurrencyKey) {
destinationRate = sourceRate;
value = sourceAmount;
} else {
// Calculate the effective value by going from source -> USD -> destination
destinationRate = _getRate(destinationCurrencyKey);
value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate);
}
}
/* ========== MODIFIERS ========== */
modifier onlyOracle {
require(msg.sender == oracle, "Only the oracle can perform this action");
_;
}
/* ========== EVENTS ========== */
event OracleUpdated(address newOracle);
event RateStalePeriodUpdated(uint rateStalePeriod);
event RatesUpdated(bytes32[] currencyKeys, uint[] newRates);
event RateDeleted(bytes32 currencyKey);
event InversePriceConfigured(bytes32 currencyKey, uint entryPoint, uint upperLimit, uint lowerLimit);
event InversePriceFrozen(bytes32 currencyKey);
event AggregatorAdded(bytes32 currencyKey, address aggregator);
event AggregatorRemoved(bytes32 currencyKey, address aggregator);
}
{
"compilationTarget": {
"ExchangeRates.sol": "ExchangeRates"
},
"evmVersion": "istanbul",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 20000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_oracle","type":"address"},{"internalType":"bytes32[]","name":"_currencyKeys","type":"bytes32[]"},{"internalType":"uint256[]","name":"_newRates","type":"uint256[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"indexed":false,"internalType":"address","name":"aggregator","type":"address"}],"name":"AggregatorAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"indexed":false,"internalType":"address","name":"aggregator","type":"address"}],"name":"AggregatorRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"entryPoint","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"upperLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lowerLimit","type":"uint256"}],"name":"InversePriceConfigured","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"InversePriceFrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOracle","type":"address"}],"name":"OracleUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerNominated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"RateDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"rateStalePeriod","type":"uint256"}],"name":"RateStalePeriodUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32[]","name":"currencyKeys","type":"bytes32[]"},{"indexed":false,"internalType":"uint256[]","name":"newRates","type":"uint256[]"}],"name":"RatesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newBeneficiary","type":"address"}],"name":"SelfDestructBeneficiaryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"selfDestructDelay","type":"uint256"}],"name":"SelfDestructInitiated","type":"event"},{"anonymous":false,"inputs":[],"name":"SelfDestructTerminated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"beneficiary","type":"address"}],"name":"SelfDestructed","type":"event"},{"constant":true,"inputs":[],"name":"SELFDESTRUCT_DELAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"internalType":"address","name":"aggregatorAddress","type":"address"}],"name":"addAggregator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"aggregatorKeys","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"aggregators","outputs":[{"internalType":"contract AggregatorInterface","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32[]","name":"currencyKeys","type":"bytes32[]"}],"name":"anyRateIsStale","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"currentRoundForRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"deleteRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"uint256","name":"sourceAmount","type":"uint256"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"}],"name":"effectiveValue","outputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"uint256","name":"sourceAmount","type":"uint256"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"}],"name":"effectiveValueAndRates","outputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"sourceRate","type":"uint256"},{"internalType":"uint256","name":"destinationRate","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"uint256","name":"sourceAmount","type":"uint256"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"},{"internalType":"uint256","name":"roundIdForSrc","type":"uint256"},{"internalType":"uint256","name":"roundIdForDest","type":"uint256"}],"name":"effectiveValueAtRound","outputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"getCurrentRoundId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"internalType":"uint256","name":"startingRoundId","type":"uint256"},{"internalType":"uint256","name":"startingTimestamp","type":"uint256"},{"internalType":"uint256","name":"timediff","type":"uint256"}],"name":"getLastRoundIdBeforeElapsedSecs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"initiateSelfDestruct","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"initiationTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"inversePricing","outputs":[{"internalType":"uint256","name":"entryPoint","type":"uint256"},{"internalType":"uint256","name":"upperLimit","type":"uint256"},{"internalType":"uint256","name":"lowerLimit","type":"uint256"},{"internalType":"bool","name":"frozen","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"invertedKeys","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"lastRateUpdateTimes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32[]","name":"currencyKeys","type":"bytes32[]"}],"name":"lastRateUpdateTimesForCurrencies","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"nominateNewOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"nominatedOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"oracle","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"internalType":"uint256","name":"roundId","type":"uint256"}],"name":"rateAndTimestampAtRound","outputs":[{"internalType":"uint256","name":"rate","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"rateAndUpdatedTime","outputs":[{"internalType":"uint256","name":"rate","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"rateForCurrency","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"rateIsFrozen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"rateIsStale","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"rateStalePeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32[]","name":"currencyKeys","type":"bytes32[]"}],"name":"ratesAndStaleForCurrencies","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"internalType":"uint256","name":"numRounds","type":"uint256"}],"name":"ratesAndUpdatedTimeForCurrencyLastNRounds","outputs":[{"internalType":"uint256[]","name":"rates","type":"uint256[]"},{"internalType":"uint256[]","name":"times","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32[]","name":"currencyKeys","type":"bytes32[]"}],"name":"ratesForCurrencies","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"removeAggregator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"removeInversePricing","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"selfDestruct","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"selfDestructBeneficiary","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"selfDestructInitiated","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"internalType":"uint256","name":"entryPoint","type":"uint256"},{"internalType":"uint256","name":"upperLimit","type":"uint256"},{"internalType":"uint256","name":"lowerLimit","type":"uint256"},{"internalType":"bool","name":"freeze","type":"bool"},{"internalType":"bool","name":"freezeAtUpperLimit","type":"bool"}],"name":"setInversePricing","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_time","type":"uint256"}],"name":"setRateStalePeriod","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address payable","name":"_beneficiary","type":"address"}],"name":"setSelfDestructBeneficiary","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"terminateSelfDestruct","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32[]","name":"currencyKeys","type":"bytes32[]"},{"internalType":"uint256[]","name":"newRates","type":"uint256[]"},{"internalType":"uint256","name":"timeSent","type":"uint256"}],"name":"updateRates","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]