文件 1 的 1:ExchangeRates.sol
pragma solidity ^0.5.16;
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 {
_onlyOwner();
_;
}
function _onlyOwner() private view {
require(msg.sender == owner, "Only the contract owner may perform this action");
}
event OwnerNominated(address newOwner);
event OwnerChanged(address oldOwner, address newOwner);
}
interface IAddressResolver {
function getAddress(bytes32 name) external view returns (address);
function getSynth(bytes32 key) external view returns (address);
function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address);
}
interface ISynth {
function currencyKey() external view returns (bytes32);
function transferableSynths(address account) external view returns (uint);
function transferAndSettle(address to, uint value) external returns (bool);
function transferFromAndSettle(
address from,
address to,
uint value
) external returns (bool);
function burn(address account, uint amount) external;
function issue(address account, uint amount) external;
}
interface IIssuer {
function anySynthOrSNXRateIsInvalid() external view returns (bool anyRateInvalid);
function availableCurrencyKeys() external view returns (bytes32[] memory);
function availableSynthCount() external view returns (uint);
function availableSynths(uint index) external view returns (ISynth);
function canBurnSynths(address account) external view returns (bool);
function collateral(address account) external view returns (uint);
function collateralisationRatio(address issuer) external view returns (uint);
function collateralisationRatioAndAnyRatesInvalid(address _issuer)
external
view
returns (uint cratio, bool anyRateIsInvalid);
function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint debtBalance);
function issuanceRatio() external view returns (uint);
function lastIssueEvent(address account) external view returns (uint);
function maxIssuableSynths(address issuer) external view returns (uint maxIssuable);
function minimumStakeTime() external view returns (uint);
function remainingIssuableSynths(address issuer)
external
view
returns (
uint maxIssuable,
uint alreadyIssued,
uint totalSystemDebt
);
function synths(bytes32 currencyKey) external view returns (ISynth);
function getSynths(bytes32[] calldata currencyKeys) external view returns (ISynth[] memory);
function synthsByAddress(address synthAddress) external view returns (bytes32);
function totalIssuedSynths(bytes32 currencyKey, bool excludeEtherCollateral) external view returns (uint);
function transferableSynthetixAndAnyRateIsInvalid(address account, uint balance)
external
view
returns (uint transferable, bool anyRateIsInvalid);
function issueSynths(address from, uint amount) external;
function issueSynthsOnBehalf(
address issueFor,
address from,
uint amount
) external;
function issueMaxSynths(address from) external;
function issueMaxSynthsOnBehalf(address issueFor, address from) external;
function burnSynths(address from, uint amount) external;
function burnSynthsOnBehalf(
address burnForAddress,
address from,
uint amount
) external;
function burnSynthsToTarget(address from) external;
function burnSynthsToTargetOnBehalf(address burnForAddress, address from) external;
function liquidateDelinquentAccount(
address account,
uint susdAmount,
address liquidator
) external returns (uint totalRedeemed, uint amountToLiquidate);
}
contract AddressResolver is Owned, IAddressResolver {
mapping(bytes32 => address) public repository;
constructor(address _owner) public Owned(_owner) {}
function importAddresses(bytes32[] calldata names, address[] calldata destinations) external onlyOwner {
require(names.length == destinations.length, "Input lengths must match");
for (uint i = 0; i < names.length; i++) {
bytes32 name = names[i];
address destination = destinations[i];
repository[name] = destination;
emit AddressImported(name, destination);
}
}
function rebuildCaches(MixinResolver[] calldata destinations) external {
for (uint i = 0; i < destinations.length; i++) {
destinations[i].rebuildCache();
}
}
function areAddressesImported(bytes32[] calldata names, address[] calldata destinations) external view returns (bool) {
for (uint i = 0; i < names.length; i++) {
if (repository[names[i]] != destinations[i]) {
return false;
}
}
return true;
}
function getAddress(bytes32 name) external view returns (address) {
return repository[name];
}
function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address) {
address _foundAddress = repository[name];
require(_foundAddress != address(0), reason);
return _foundAddress;
}
function getSynth(bytes32 key) external view returns (address) {
IIssuer issuer = IIssuer(repository["Issuer"]);
require(address(issuer) != address(0), "Cannot find Issuer address");
return address(issuer.synths(key));
}
event AddressImported(bytes32 name, address destination);
}
contract ReadProxy is Owned {
address public target;
constructor(address _owner) public Owned(_owner) {}
function setTarget(address _target) external onlyOwner {
target = _target;
emit TargetUpdated(target);
}
function() external {
assembly {
calldatacopy(0, 0, calldatasize)
let result := staticcall(gas, sload(target_slot), 0, calldatasize, 0, 0)
returndatacopy(0, 0, returndatasize)
if iszero(result) {
revert(0, returndatasize)
}
return(0, returndatasize)
}
}
event TargetUpdated(address newTarget);
}
contract MixinResolver {
AddressResolver public resolver;
mapping(bytes32 => address) private addressCache;
constructor(address _resolver) internal {
resolver = AddressResolver(_resolver);
}
function combineArrays(bytes32[] memory first, bytes32[] memory second)
internal
pure
returns (bytes32[] memory combination)
{
combination = new bytes32[](first.length + second.length);
for (uint i = 0; i < first.length; i++) {
combination[i] = first[i];
}
for (uint j = 0; j < second.length; j++) {
combination[first.length + j] = second[j];
}
}
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {}
function rebuildCache() public {
bytes32[] memory requiredAddresses = resolverAddressesRequired();
for (uint i = 0; i < requiredAddresses.length; i++) {
bytes32 name = requiredAddresses[i];
address destination = resolver.requireAndGetAddress(
name,
string(abi.encodePacked("Resolver missing target: ", name))
);
addressCache[name] = destination;
emit CacheUpdated(name, destination);
}
}
function isResolverCached() external view returns (bool) {
bytes32[] memory requiredAddresses = resolverAddressesRequired();
for (uint i = 0; i < requiredAddresses.length; i++) {
bytes32 name = requiredAddresses[i];
if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) {
return false;
}
}
return true;
}
function requireAndGetAddress(bytes32 name) internal view returns (address) {
address _foundAddress = addressCache[name];
require(_foundAddress != address(0), string(abi.encodePacked("Missing address: ", name)));
return _foundAddress;
}
event CacheUpdated(bytes32 name, address destination);
}
interface IFlexibleStorage {
function getUIntValue(bytes32 contractName, bytes32 record) external view returns (uint);
function getUIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (uint[] memory);
function getIntValue(bytes32 contractName, bytes32 record) external view returns (int);
function getIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (int[] memory);
function getAddressValue(bytes32 contractName, bytes32 record) external view returns (address);
function getAddressValues(bytes32 contractName, bytes32[] calldata records) external view returns (address[] memory);
function getBoolValue(bytes32 contractName, bytes32 record) external view returns (bool);
function getBoolValues(bytes32 contractName, bytes32[] calldata records) external view returns (bool[] memory);
function getBytes32Value(bytes32 contractName, bytes32 record) external view returns (bytes32);
function getBytes32Values(bytes32 contractName, bytes32[] calldata records) external view returns (bytes32[] memory);
function deleteUIntValue(bytes32 contractName, bytes32 record) external;
function deleteIntValue(bytes32 contractName, bytes32 record) external;
function deleteAddressValue(bytes32 contractName, bytes32 record) external;
function deleteBoolValue(bytes32 contractName, bytes32 record) external;
function deleteBytes32Value(bytes32 contractName, bytes32 record) external;
function setUIntValue(
bytes32 contractName,
bytes32 record,
uint value
) external;
function setUIntValues(
bytes32 contractName,
bytes32[] calldata records,
uint[] calldata values
) external;
function setIntValue(
bytes32 contractName,
bytes32 record,
int value
) external;
function setIntValues(
bytes32 contractName,
bytes32[] calldata records,
int[] calldata values
) external;
function setAddressValue(
bytes32 contractName,
bytes32 record,
address value
) external;
function setAddressValues(
bytes32 contractName,
bytes32[] calldata records,
address[] calldata values
) external;
function setBoolValue(
bytes32 contractName,
bytes32 record,
bool value
) external;
function setBoolValues(
bytes32 contractName,
bytes32[] calldata records,
bool[] calldata values
) external;
function setBytes32Value(
bytes32 contractName,
bytes32 record,
bytes32 value
) external;
function setBytes32Values(
bytes32 contractName,
bytes32[] calldata records,
bytes32[] calldata values
) external;
}
contract MixinSystemSettings is MixinResolver {
bytes32 internal constant SETTING_CONTRACT_NAME = "SystemSettings";
bytes32 internal constant SETTING_WAITING_PERIOD_SECS = "waitingPeriodSecs";
bytes32 internal constant SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR = "priceDeviationThresholdFactor";
bytes32 internal constant SETTING_ISSUANCE_RATIO = "issuanceRatio";
bytes32 internal constant SETTING_FEE_PERIOD_DURATION = "feePeriodDuration";
bytes32 internal constant SETTING_TARGET_THRESHOLD = "targetThreshold";
bytes32 internal constant SETTING_LIQUIDATION_DELAY = "liquidationDelay";
bytes32 internal constant SETTING_LIQUIDATION_RATIO = "liquidationRatio";
bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty";
bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod";
bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate";
bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime";
bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags";
bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled";
bytes32 internal constant SETTING_DEBT_SNAPSHOT_STALE_TIME = "debtSnapshotStaleTime";
bytes32 internal constant SETTING_CROSS_DOMAIN_MESSAGE_GAS_LIMIT = "crossDomainMessageGasLimit";
bytes32 internal constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage";
constructor(address _resolver) internal MixinResolver(_resolver) {}
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
addresses = new bytes32[](1);
addresses[0] = CONTRACT_FLEXIBLESTORAGE;
}
function flexibleStorage() internal view returns (IFlexibleStorage) {
return IFlexibleStorage(requireAndGetAddress(CONTRACT_FLEXIBLESTORAGE));
}
function getCrossDomainMessageGasLimit() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_CROSS_DOMAIN_MESSAGE_GAS_LIMIT);
}
function getTradingRewardsEnabled() internal view returns (bool) {
return flexibleStorage().getBoolValue(SETTING_CONTRACT_NAME, SETTING_TRADING_REWARDS_ENABLED);
}
function getWaitingPeriodSecs() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_WAITING_PERIOD_SECS);
}
function getPriceDeviationThresholdFactor() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR);
}
function getIssuanceRatio() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ISSUANCE_RATIO);
}
function getFeePeriodDuration() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FEE_PERIOD_DURATION);
}
function getTargetThreshold() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_TARGET_THRESHOLD);
}
function getLiquidationDelay() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_DELAY);
}
function getLiquidationRatio() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_RATIO);
}
function getLiquidationPenalty() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_PENALTY);
}
function getRateStalePeriod() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD);
}
function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) {
return
flexibleStorage().getUIntValue(
SETTING_CONTRACT_NAME,
keccak256(abi.encodePacked(SETTING_EXCHANGE_FEE_RATE, currencyKey))
);
}
function getMinimumStakeTime() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME);
}
function getAggregatorWarningFlags() internal view returns (address) {
return flexibleStorage().getAddressValue(SETTING_CONTRACT_NAME, SETTING_AGGREGATOR_WARNING_FLAGS);
}
function getDebtSnapshotStaleTime() internal view returns (uint) {
return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DEBT_SNAPSHOT_STALE_TIME);
}
}
interface IExchangeRates {
struct RateAndUpdatedTime {
uint216 rate;
uint40 time;
}
struct InversePricing {
uint entryPoint;
uint upperLimit;
uint lowerLimit;
bool frozenAtUpperLimit;
bool frozenAtLowerLimit;
}
function aggregators(bytes32 currencyKey) external view returns (address);
function aggregatorWarningFlags() external view returns (address);
function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool);
function canFreezeRate(bytes32 currencyKey) external view returns (bool);
function currentRoundForRate(bytes32 currencyKey) external view returns (uint);
function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory);
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 frozenAtUpperLimit,
bool frozenAtLowerLimit
);
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 rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid);
function rateForCurrency(bytes32 currencyKey) external view returns (uint);
function rateIsFlagged(bytes32 currencyKey) external view returns (bool);
function rateIsFrozen(bytes32 currencyKey) external view returns (bool);
function rateIsInvalid(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 ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys)
external
view
returns (uint[] memory rates, bool anyRateInvalid);
function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory);
function freezeRate(bytes32 currencyKey) external;
}
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
return c;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
library SafeDecimalMath {
using SafeMath for uint;
uint8 public constant decimals = 18;
uint8 public constant highPrecisionDecimals = 27;
uint public constant UNIT = 10**uint(decimals);
uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals);
uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals);
function unit() external pure returns (uint) {
return UNIT;
}
function preciseUnit() external pure returns (uint) {
return PRECISE_UNIT;
}
function multiplyDecimal(uint x, uint y) internal pure returns (uint) {
return x.mul(y) / UNIT;
}
function _multiplyDecimalRound(
uint x,
uint y,
uint precisionUnit
) private pure returns (uint) {
uint quotientTimesTen = x.mul(y) / (precisionUnit / 10);
if (quotientTimesTen % 10 >= 5) {
quotientTimesTen += 10;
}
return quotientTimesTen / 10;
}
function multiplyDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
return _multiplyDecimalRound(x, y, PRECISE_UNIT);
}
function multiplyDecimalRound(uint x, uint y) internal pure returns (uint) {
return _multiplyDecimalRound(x, y, UNIT);
}
function divideDecimal(uint x, uint y) internal pure returns (uint) {
return x.mul(UNIT).div(y);
}
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;
}
function divideDecimalRound(uint x, uint y) internal pure returns (uint) {
return _divideDecimalRound(x, y, UNIT);
}
function divideDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
return _divideDecimalRound(x, y, PRECISE_UNIT);
}
function decimalToPreciseDecimal(uint i) internal pure returns (uint) {
return i.mul(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR);
}
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, uint256 startedAt);
}
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
interface AggregatorV2V3Interface {
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, uint256 startedAt);
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
interface FlagsInterface {
function getFlag(address) external view returns (bool);
function getFlags(address[] calldata) external view returns (bool[] memory);
function raiseFlag(address) external;
function raiseFlags(address[] calldata) external;
function lowerFlags(address[] calldata) external;
function setRaisingAccessController(address) external;
}
interface IVirtualSynth {
function balanceOfUnderlying(address account) external view returns (uint);
function rate() external view returns (uint);
function readyToSettle() external view returns (bool);
function secsLeftInWaitingPeriod() external view returns (uint);
function settled() external view returns (bool);
function synth() external view returns (ISynth);
function settle(address account) external;
}
interface IExchanger {
function calculateAmountAfterSettlement(
address from,
bytes32 currencyKey,
uint amount,
uint refunded
) external view returns (uint amountAfterSettlement);
function isSynthRateInvalid(bytes32 currencyKey) external view returns (bool);
function maxSecsLeftInWaitingPeriod(address account, bytes32 currencyKey) external view returns (uint);
function settlementOwing(address account, bytes32 currencyKey)
external
view
returns (
uint reclaimAmount,
uint rebateAmount,
uint numEntries
);
function hasWaitingPeriodOrSettlementOwing(address account, bytes32 currencyKey) external view returns (bool);
function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
external
view
returns (uint exchangeFeeRate);
function getAmountsForExchange(
uint sourceAmount,
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint amountReceived,
uint fee,
uint exchangeFeeRate
);
function priceDeviationThresholdFactor() external view returns (uint);
function waitingPeriodSecs() external view returns (uint);
function exchange(
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address destinationAddress
) external returns (uint amountReceived);
function exchangeOnBehalf(
address exchangeForAddress,
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external returns (uint amountReceived);
function exchangeWithTracking(
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address destinationAddress,
address originator,
bytes32 trackingCode
) external returns (uint amountReceived);
function exchangeOnBehalfWithTracking(
address exchangeForAddress,
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address originator,
bytes32 trackingCode
) external returns (uint amountReceived);
function exchangeWithVirtual(
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address destinationAddress,
bytes32 trackingCode
) external returns (uint amountReceived, IVirtualSynth vSynth);
function settle(address from, bytes32 currencyKey)
external
returns (
uint reclaimed,
uint refunded,
uint numEntries
);
function setLastExchangeRateForSynth(bytes32 currencyKey, uint rate) external;
function suspendSynthWithInvalidRate(bytes32 currencyKey) external;
}
contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
using SafeMath for uint;
using SafeDecimalMath for uint;
mapping(bytes32 => mapping(uint => RateAndUpdatedTime)) private _rates;
address public oracle;
mapping(bytes32 => AggregatorV2V3Interface) public aggregators;
mapping(bytes32 => uint8) public currencyKeyDecimals;
bytes32[] public aggregatorKeys;
uint private constant ORACLE_FUTURE_LIMIT = 10 minutes;
mapping(bytes32 => InversePricing) public inversePricing;
bytes32[] public invertedKeys;
mapping(bytes32 => uint) public currentRoundForRate;
mapping(bytes32 => uint) public roundFrozen;
bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
constructor(
address _owner,
address _oracle,
address _resolver,
bytes32[] memory _currencyKeys,
uint[] memory _newRates
) public Owned(_owner) MixinSystemSettings(_resolver) {
require(_currencyKeys.length == _newRates.length, "Currency key length and rate length must match.");
oracle = _oracle;
_setRate("sUSD", SafeDecimalMath.unit(), now);
internalUpdateRates(_currencyKeys, _newRates, now);
}
function setOracle(address _oracle) external onlyOwner {
oracle = _oracle;
emit OracleUpdated(oracle);
}
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 freezeAtUpperLimit,
bool freezeAtLowerLimit
) external onlyOwner {
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");
require(!(freezeAtUpperLimit && freezeAtLowerLimit), "Cannot freeze at both limits");
InversePricing storage inverse = inversePricing[currencyKey];
if (inverse.entryPoint == 0) {
invertedKeys.push(currencyKey);
}
inverse.entryPoint = entryPoint;
inverse.upperLimit = upperLimit;
inverse.lowerLimit = lowerLimit;
if (freezeAtUpperLimit || freezeAtLowerLimit) {
inverse.frozenAtUpperLimit = freezeAtUpperLimit;
inverse.frozenAtLowerLimit = freezeAtLowerLimit;
uint roundId = _getCurrentRoundId(currencyKey);
roundFrozen[currencyKey] = roundId;
emit InversePriceFrozen(currencyKey, freezeAtUpperLimit ? upperLimit : lowerLimit, roundId, msg.sender);
} else {
inverse.frozenAtUpperLimit = false;
inverse.frozenAtLowerLimit = false;
roundFrozen[currencyKey] = 0;
}
uint rate = _getRate(currencyKey);
if (rate > 0) {
exchanger().setLastExchangeRateForSynth(currencyKey, rate);
}
emit InversePriceConfigured(currencyKey, entryPoint, upperLimit, lowerLimit);
}
function removeInversePricing(bytes32 currencyKey) external onlyOwner {
require(inversePricing[currencyKey].entryPoint > 0, "No inverted price exists");
delete inversePricing[currencyKey];
bool wasRemoved = removeFromArray(currencyKey, invertedKeys);
if (wasRemoved) {
emit InversePriceConfigured(currencyKey, 0, 0, 0);
}
}
function addAggregator(bytes32 currencyKey, address aggregatorAddress) external onlyOwner {
AggregatorV2V3Interface aggregator = AggregatorV2V3Interface(aggregatorAddress);
require(aggregator.latestRound() >= 0, "Given Aggregator is invalid");
uint8 decimals = aggregator.decimals();
require(decimals <= 18, "Aggregator decimals should be lower or equal to 18");
if (address(aggregators[currencyKey]) == address(0)) {
aggregatorKeys.push(currencyKey);
}
aggregators[currencyKey] = aggregator;
currencyKeyDecimals[currencyKey] = decimals;
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];
delete currencyKeyDecimals[currencyKey];
bool wasRemoved = removeFromArray(currencyKey, aggregatorKeys);
if (wasRemoved) {
emit AggregatorRemoved(currencyKey, aggregator);
}
}
function freezeRate(bytes32 currencyKey) external {
InversePricing storage inverse = inversePricing[currencyKey];
require(inverse.entryPoint > 0, "Cannot freeze non-inverse rate");
require(!inverse.frozenAtUpperLimit && !inverse.frozenAtLowerLimit, "The rate is already frozen");
uint rate = _getRate(currencyKey);
if (rate > 0 && (rate >= inverse.upperLimit || rate <= inverse.lowerLimit)) {
inverse.frozenAtUpperLimit = (rate == inverse.upperLimit);
inverse.frozenAtLowerLimit = (rate == inverse.lowerLimit);
uint currentRoundId = _getCurrentRoundId(currencyKey);
roundFrozen[currencyKey] = currentRoundId;
emit InversePriceFrozen(currencyKey, rate, currentRoundId, msg.sender);
} else {
revert("Rate within bounds");
}
}
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_EXCHANGER;
addresses = combineArrays(existingAddresses, newAddresses);
}
function canFreezeRate(bytes32 currencyKey) external view returns (bool) {
InversePricing memory inverse = inversePricing[currencyKey];
if (inverse.entryPoint == 0 || inverse.frozenAtUpperLimit || inverse.frozenAtLowerLimit) {
return false;
} else {
uint rate = _getRate(currencyKey);
return (rate > 0 && (rate >= inverse.upperLimit || rate <= inverse.lowerLimit));
}
}
function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory currencies) {
uint count = 0;
currencies = new bytes32[](aggregatorKeys.length);
for (uint i = 0; i < aggregatorKeys.length; i++) {
bytes32 currencyKey = aggregatorKeys[i];
if (address(aggregators[currencyKey]) == aggregator) {
currencies[count++] = currencyKey;
}
}
}
function rateStalePeriod() external view returns (uint) {
return getRateStalePeriod();
}
function aggregatorWarningFlags() external view returns (address) {
return getAggregatorWarningFlags();
}
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 (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 (sourceCurrencyKey == destinationCurrencyKey) return sourceAmount;
(uint srcRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc);
(uint destRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest);
if (destRate == 0) {
return 0;
}
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) {
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 rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid) {
RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey);
if (currencyKey == "sUSD") {
return (rateAndTime.rate, false);
}
return (
rateAndTime.rate,
_rateIsStaleWithTime(getRateStalePeriod(), rateAndTime.time) ||
_rateIsFlagged(currencyKey, FlagsInterface(getAggregatorWarningFlags()))
);
}
function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys)
external
view
returns (uint[] memory rates, bool anyRateInvalid)
{
rates = new uint[](currencyKeys.length);
uint256 _rateStalePeriod = getRateStalePeriod();
bool[] memory flagList = getFlagsForRates(currencyKeys);
for (uint i = 0; i < currencyKeys.length; i++) {
RateAndUpdatedTime memory rateEntry = _getRateAndUpdatedTime(currencyKeys[i]);
rates[i] = rateEntry.rate;
if (!anyRateInvalid && currencyKeys[i] != "sUSD") {
anyRateInvalid = flagList[i] || _rateIsStaleWithTime(_rateStalePeriod, rateEntry.time);
}
}
}
function rateIsStale(bytes32 currencyKey) external view returns (bool) {
return _rateIsStale(currencyKey, getRateStalePeriod());
}
function rateIsFrozen(bytes32 currencyKey) external view returns (bool) {
return _rateIsFrozen(currencyKey);
}
function rateIsInvalid(bytes32 currencyKey) external view returns (bool) {
return
_rateIsStale(currencyKey, getRateStalePeriod()) ||
_rateIsFlagged(currencyKey, FlagsInterface(getAggregatorWarningFlags()));
}
function rateIsFlagged(bytes32 currencyKey) external view returns (bool) {
return _rateIsFlagged(currencyKey, FlagsInterface(getAggregatorWarningFlags()));
}
function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool) {
uint256 _rateStalePeriod = getRateStalePeriod();
bool[] memory flagList = getFlagsForRates(currencyKeys);
for (uint i = 0; i < currencyKeys.length; i++) {
if (flagList[i] || _rateIsStale(currencyKeys[i], _rateStalePeriod)) {
return true;
}
}
return false;
}
function exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
function getFlagsForRates(bytes32[] memory currencyKeys) internal view returns (bool[] memory flagList) {
FlagsInterface _flags = FlagsInterface(getAggregatorWarningFlags());
if (_flags != FlagsInterface(0)) {
address[] memory _aggregators = new address[](currencyKeys.length);
for (uint i = 0; i < currencyKeys.length; i++) {
_aggregators[i] = address(aggregators[currencyKeys[i]]);
}
flagList = _flags.getFlags(_aggregators);
} else {
flagList = new bool[](currencyKeys.length);
}
}
function _setRate(
bytes32 currencyKey,
uint256 rate,
uint256 time
) internal {
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");
for (uint i = 0; i < currencyKeys.length; i++) {
bytes32 currencyKey = currencyKeys[i];
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.");
if (timeSent < _getUpdatedTime(currencyKey)) {
continue;
}
_setRate(currencyKey, newRates[i], timeSent);
}
emit RatesUpdated(currencyKeys, newRates);
return true;
}
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];
array[i] = array[array.length - 1];
array.length--;
return true;
}
}
return false;
}
function _rateOrInverted(
bytes32 currencyKey,
uint rate,
uint roundId
) internal view returns (uint newRate) {
InversePricing memory inverse = inversePricing[currencyKey];
if (inverse.entryPoint == 0 || rate == 0) {
return rate;
}
newRate = rate;
uint roundWhenRateFrozen = roundFrozen[currencyKey];
if (roundId >= roundWhenRateFrozen && inverse.frozenAtUpperLimit) {
newRate = inverse.upperLimit;
} else if (roundId >= roundWhenRateFrozen && inverse.frozenAtLowerLimit) {
newRate = inverse.lowerLimit;
} else {
uint doubleEntryPoint = inverse.entryPoint.mul(2);
if (doubleEntryPoint <= rate) {
newRate = 0;
} else {
newRate = doubleEntryPoint.sub(rate);
}
if (newRate >= inverse.upperLimit) {
newRate = inverse.upperLimit;
} else if (newRate <= inverse.lowerLimit) {
newRate = inverse.lowerLimit;
}
}
}
function _formatAggregatorAnswer(bytes32 currencyKey, int256 rate) internal view returns (uint) {
require(rate >= 0, "Negative rate not supported");
if (currencyKeyDecimals[currencyKey] > 0) {
uint multiplier = 10**uint(SafeMath.sub(18, currencyKeyDecimals[currencyKey]));
return uint(uint(rate).mul(multiplier));
}
return uint(rate);
}
function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) {
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
if (aggregator != AggregatorV2V3Interface(0)) {
bytes memory payload = abi.encodeWithSignature("latestRoundData()");
(bool success, bytes memory returnData) = address(aggregator).staticcall(payload);
if (success) {
(uint80 roundId, int256 answer, , uint256 updatedAt, ) = abi.decode(
returnData,
(uint80, int256, uint256, uint256, uint80)
);
return
RateAndUpdatedTime({
rate: uint216(_rateOrInverted(currencyKey, _formatAggregatorAnswer(currencyKey, answer), roundId)),
time: uint40(updatedAt)
});
}
} else {
uint roundId = currentRoundForRate[currencyKey];
RateAndUpdatedTime memory entry = _rates[currencyKey][roundId];
return RateAndUpdatedTime({rate: uint216(_rateOrInverted(currencyKey, entry.rate, roundId)), time: entry.time});
}
}
function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) {
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
if (aggregator != AggregatorV2V3Interface(0)) {
return aggregator.latestRound();
} else {
return currentRoundForRate[currencyKey];
}
}
function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) {
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
if (aggregator != AggregatorV2V3Interface(0)) {
bytes memory payload = abi.encodeWithSignature("getRoundData(uint80)", roundId);
(bool success, bytes memory returnData) = address(aggregator).staticcall(payload);
if (success) {
(, int256 answer, , uint256 updatedAt, ) = abi.decode(
returnData,
(uint80, int256, uint256, uint256, uint80)
);
return (_rateOrInverted(currencyKey, _formatAggregatorAnswer(currencyKey, answer), roundId), updatedAt);
}
} else {
RateAndUpdatedTime memory update = _rates[currencyKey][roundId];
return (_rateOrInverted(currencyKey, update.rate, roundId), 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 (sourceCurrencyKey == destinationCurrencyKey) {
destinationRate = sourceRate;
value = sourceAmount;
} else {
destinationRate = _getRate(destinationCurrencyKey);
if (destinationRate > 0) {
value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate);
}
}
}
function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) {
if (currencyKey == "sUSD") return false;
return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey));
}
function _rateIsStaleWithTime(uint _rateStalePeriod, uint _time) internal view returns (bool) {
return _time.add(_rateStalePeriod) < now;
}
function _rateIsFrozen(bytes32 currencyKey) internal view returns (bool) {
InversePricing memory inverse = inversePricing[currencyKey];
return inverse.frozenAtUpperLimit || inverse.frozenAtLowerLimit;
}
function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) {
if (currencyKey == "sUSD") return false;
address aggregator = address(aggregators[currencyKey]);
if (aggregator == address(0) || flags == FlagsInterface(0)) {
return false;
}
return flags.getFlag(aggregator);
}
modifier onlyOracle {
_onlyOracle();
_;
}
function _onlyOracle() internal view {
require(msg.sender == oracle, "Only the oracle can perform this action");
}
event OracleUpdated(address newOracle);
event RatesUpdated(bytes32[] currencyKeys, uint[] newRates);
event RateDeleted(bytes32 currencyKey);
event InversePriceConfigured(bytes32 currencyKey, uint entryPoint, uint upperLimit, uint lowerLimit);
event InversePriceFrozen(bytes32 currencyKey, uint rate, uint roundId, address initiator);
event AggregatorAdded(bytes32 currencyKey, address aggregator);
event AggregatorRemoved(bytes32 currencyKey, address aggregator);
}