diff --git a/contracts/ExchangeRates.sol b/contracts/ExchangeRates.sol index a2f37e0b73..03759d99c0 100644 --- a/contracts/ExchangeRates.sol +++ b/contracts/ExchangeRates.sol @@ -22,12 +22,8 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { using SafeDecimalMath for uint; bytes32 public constant CONTRACT_NAME = "ExchangeRates"; - - // 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; + //slither-disable-next-line naming-convention + bytes32 internal constant sUSD = "sUSD"; // Decentralized oracle networks that feed into pricing aggregators mapping(bytes32 => AggregatorV2V3Interface) public aggregators; @@ -37,58 +33,12 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // 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; - - mapping(bytes32 => uint) public currentRoundForRate; - - // // ========== CONSTRUCTOR ========== - 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; - - // 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); - } + constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {} /* ========== 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 addAggregator(bytes32 currencyKey, address aggregatorAddress) external onlyOwner { AggregatorV2V3Interface aggregator = AggregatorV2V3Interface(aggregatorAddress); // This check tries to make sure that a valid aggregator is being added. @@ -288,7 +238,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid) { RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey); - if (currencyKey == "sUSD") { + if (currencyKey == sUSD) { return (rateAndTime.rate, false); } return ( @@ -314,7 +264,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { // do one lookup of the rate & time to minimize gas RateAndUpdatedTime memory rateEntry = _getRateAndUpdatedTime(currencyKeys[i]); rates[i] = rateEntry.rate; - if (!anyRateInvalid && currencyKeys[i] != "sUSD") { + if (!anyRateInvalid && currencyKeys[i] != sUSD) { anyRateInvalid = flagList[i] || _rateIsStaleWithTime(_rateStalePeriod, rateEntry.time); } } @@ -372,52 +322,6 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } } - 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; - } - - // Ok, go ahead with the update. - _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) { @@ -447,60 +351,64 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) { - AggregatorV2V3Interface aggregator = aggregators[currencyKey]; - - if (aggregator != AggregatorV2V3Interface(0)) { - // this view from the aggregator is the most gas efficient but it can throw when there's no data, - // so let's call it low-level to suppress any reverts - bytes memory payload = abi.encodeWithSignature("latestRoundData()"); - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = address(aggregator).staticcall(payload); - - if (success) { - (, int256 answer, , uint256 updatedAt, ) = - abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); - return - RateAndUpdatedTime({ - rate: uint216(_formatAggregatorAnswer(currencyKey, answer)), - time: uint40(updatedAt) - }); - } + // sUSD rate is 1.0 + if (currencyKey == sUSD) { + return RateAndUpdatedTime({rate: uint216(SafeDecimalMath.unit()), time: 0}); } else { - uint roundId = currentRoundForRate[currencyKey]; - RateAndUpdatedTime memory entry = _rates[currencyKey][roundId]; - - return RateAndUpdatedTime({rate: uint216(entry.rate), time: entry.time}); + AggregatorV2V3Interface aggregator = aggregators[currencyKey]; + if (aggregator != AggregatorV2V3Interface(0)) { + // this view from the aggregator is the most gas efficient but it can throw when there's no data, + // so let's call it low-level to suppress any reverts + bytes memory payload = abi.encodeWithSignature("latestRoundData()"); + // solhint-disable avoid-low-level-calls + // slither-disable-next-line low-level-calls + (bool success, bytes memory returnData) = address(aggregator).staticcall(payload); + + if (success) { + (, int256 answer, , uint256 updatedAt, ) = + abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); + return + RateAndUpdatedTime({ + rate: uint216(_formatAggregatorAnswer(currencyKey, answer)), + time: uint40(updatedAt) + }); + } // else return defaults, to avoid reverting in views + } // else return defaults, to avoid reverting in views } } function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) { + if (currencyKey == sUSD) { + return 0; // no roundIds for sUSD + } AggregatorV2V3Interface aggregator = aggregators[currencyKey]; - if (aggregator != AggregatorV2V3Interface(0)) { return aggregator.latestRound(); - } else { - return currentRoundForRate[currencyKey]; - } + } // else return defaults, to avoid reverting in views } function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) { - AggregatorV2V3Interface aggregator = aggregators[currencyKey]; - - if (aggregator != AggregatorV2V3Interface(0)) { - // this view from the aggregator is the most gas efficient but it can throw when there's no data, - // so let's call it low-level to suppress any reverts - bytes memory payload = abi.encodeWithSignature("getRoundData(uint80)", roundId); - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = address(aggregator).staticcall(payload); - - if (success) { - (, int256 answer, , uint256 updatedAt, ) = - abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); - return (_formatAggregatorAnswer(currencyKey, answer), updatedAt); - } + // short circuit sUSD + if (currencyKey == sUSD) { + // sUSD has no rounds, and 0 time is preferrable for "volatility" heuristics + // which are used in atomic swaps and fee reclamation + return (SafeDecimalMath.unit(), 0); } else { - RateAndUpdatedTime memory update = _rates[currencyKey][roundId]; - return (update.rate, update.time); + AggregatorV2V3Interface aggregator = aggregators[currencyKey]; + + if (aggregator != AggregatorV2V3Interface(0)) { + // this view from the aggregator is the most gas efficient but it can throw when there's no data, + // so let's call it low-level to suppress any reverts + bytes memory payload = abi.encodeWithSignature("getRoundData(uint80)", roundId); + // solhint-disable avoid-low-level-calls + (bool success, bytes memory returnData) = address(aggregator).staticcall(payload); + + if (success) { + (, int256 answer, , uint256 updatedAt, ) = + abi.decode(returnData, (uint80, int256, uint256, uint256, uint80)); + return (_formatAggregatorAnswer(currencyKey, answer), updatedAt); + } // else return defaults, to avoid reverting in views + } // else return defaults, to avoid reverting in views } } @@ -542,7 +450,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) { // sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime) - if (currencyKey == "sUSD") return false; + if (currencyKey == sUSD) return false; return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey)); } @@ -553,7 +461,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) { // sUSD is a special case and is never invalid - if (currencyKey == "sUSD") return false; + if (currencyKey == sUSD) return false; address aggregator = address(aggregators[currencyKey]); // when no aggregator or when the flags haven't been setup if (aggregator == address(0) || flags == FlagsInterface(0)) { @@ -563,25 +471,12 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates { } function _notImplemented() internal pure { + // slither-disable-next-line dead-code revert("Cannot be run on this layer"); } - /* ========== MODIFIERS ========== */ - - modifier onlyOracle { - _onlyOracle(); - _; - } - - function _onlyOracle() internal view { - require(msg.sender == oracle, "Only the oracle can perform this action"); - } - /* ========== EVENTS ========== */ - event OracleUpdated(address newOracle); - event RatesUpdated(bytes32[] currencyKeys, uint[] newRates); - event RateDeleted(bytes32 currencyKey); event AggregatorAdded(bytes32 currencyKey, address aggregator); event AggregatorRemoved(bytes32 currencyKey, address aggregator); } diff --git a/contracts/ExchangeRatesWithDexPricing.sol b/contracts/ExchangeRatesWithDexPricing.sol index 1548a61030..b6081da974 100644 --- a/contracts/ExchangeRatesWithDexPricing.sol +++ b/contracts/ExchangeRatesWithDexPricing.sol @@ -10,13 +10,7 @@ contract ExchangeRatesWithDexPricing is ExchangeRates { bytes32 internal constant SETTING_DEX_PRICE_AGGREGATOR = "dexPriceAggregator"; - constructor( - address _owner, - address _oracle, - address _resolver, - bytes32[] memory _currencyKeys, - uint[] memory _newRates - ) public ExchangeRates(_owner, _oracle, _resolver, _currencyKeys, _newRates) {} + constructor(address _owner, address _resolver) public ExchangeRates(_owner, _resolver) {} /* ========== SETTERS ========== */ diff --git a/contracts/interfaces/IExchangeRates.sol b/contracts/interfaces/IExchangeRates.sol index 800e868731..e55b1e87f0 100644 --- a/contracts/interfaces/IExchangeRates.sol +++ b/contracts/interfaces/IExchangeRates.sol @@ -15,8 +15,6 @@ interface IExchangeRates { function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool); - function currentRoundForRate(bytes32 currencyKey) external view returns (uint); - function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory); function effectiveValue( @@ -71,8 +69,6 @@ interface IExchangeRates { 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); diff --git a/hardhat.config.js b/hardhat.config.js index 8b769f2504..d349c6031f 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -65,5 +65,6 @@ module.exports = { }, mocha: { timeout: 120e3, // 120s + retries: 3, }, }; diff --git a/publish/releases.json b/publish/releases.json index 44892848bb..0c2f8e2e03 100644 --- a/publish/releases.json +++ b/publish/releases.json @@ -431,6 +431,11 @@ "layer": "ovm", "sources": ["CollateralEth"], "released": "ovm" + }, + { + "sip": 196, + "layer": "both", + "sources": ["ExchangeRates"] } ], "releases": [ diff --git a/publish/src/commands/deploy/deploy-core.js b/publish/src/commands/deploy/deploy-core.js index dfd6a6ce02..c6441dce1c 100644 --- a/publish/src/commands/deploy/deploy-core.js +++ b/publish/src/commands/deploy/deploy-core.js @@ -14,7 +14,6 @@ module.exports = async ({ currentSynthetixSupply, currentWeekOfInflation, deployer, - oracleAddress, useOvm, }) => { console.log(gray(`\n------ DEPLOY LIBRARIES ------\n`)); @@ -59,7 +58,7 @@ module.exports = async ({ await deployer.deployContract({ name: 'ExchangeRates', source: useOvm ? 'ExchangeRates' : 'ExchangeRatesWithDexPricing', - args: [account, oracleAddress, addressOf(readProxyForResolver), [], []], + args: [account, addressOf(readProxyForResolver)], }); await deployer.deployContract({ diff --git a/publish/src/commands/deploy/index.js b/publish/src/commands/deploy/index.js index 686982fa01..b24cd46e27 100644 --- a/publish/src/commands/deploy/index.js +++ b/publish/src/commands/deploy/index.js @@ -62,7 +62,6 @@ const deploy = async ({ ignoreSafetyChecks, manageNonces, network = DEFAULTS.network, - oracleExrates, privateKey, providerUrl, skipFeedChecks = false, @@ -212,7 +211,6 @@ const deploy = async ({ currentSynthetixSupply, currentLastMintEvent, currentWeekOfInflation, - oracleAddress, systemSuspended, } = await systemAndParameterCheck({ account, @@ -229,7 +227,6 @@ const deploy = async ({ maxPriorityFeePerGas, getDeployParameter, network, - oracleExrates, providerUrl, skipFeedChecks, standaloneFeeds, @@ -276,7 +273,6 @@ const deploy = async ({ currentSynthetixSupply, currentWeekOfInflation, deployer, - oracleAddress, useOvm, }); diff --git a/publish/src/commands/deploy/system-and-parameter-check.js b/publish/src/commands/deploy/system-and-parameter-check.js index ac835a43c2..c14394ab25 100644 --- a/publish/src/commands/deploy/system-and-parameter-check.js +++ b/publish/src/commands/deploy/system-and-parameter-check.js @@ -33,7 +33,6 @@ module.exports = async ({ maxPriorityFeePerGas, getDeployParameter, network, - oracleExrates, providerUrl, skipFeedChecks, standaloneFeeds, @@ -43,7 +42,6 @@ module.exports = async ({ yes, buildPath, }) => { - let oracleAddress; let currentSynthetixSupply; let oldExrates; let currentLastMintEvent; @@ -92,12 +90,8 @@ module.exports = async ({ try { oldExrates = deployer.getExistingContract({ contract: 'ExchangeRates' }); - if (!oracleExrates) { - oracleAddress = await oldExrates.oracle(); - } } catch (err) { if (freshDeploy) { - oracleAddress = oracleExrates || account; oldExrates = undefined; // unset to signify that a fresh one will be deployed } else { console.error( @@ -127,12 +121,10 @@ module.exports = async ({ } } - for (const address of [account, oracleAddress]) { - if (!isAddress(address)) { - console.error(red('Invalid address detected (please check your inputs):', address)); - process.exitCode = 1; - process.exit(); - } + if (!isAddress(account)) { + console.error(red('Invalid address detected (please check your inputs):', account)); + process.exitCode = 1; + process.exit(); } const newSynthsToAdd = synths @@ -206,7 +198,6 @@ module.exports = async ({ : yellow('⚠ NO'), 'Deployer account:': account, 'Synthetix totalSupply': `${Math.round(formatUnits(currentSynthetixSupply) / 1e6)}m`, - 'ExchangeRates Oracle': oracleAddress, 'Last Mint Event': `${currentLastMintEvent} (${new Date(currentLastMintEvent * 1000)})`, 'Current Weeks Of Inflation': currentWeekOfInflation, 'Aggregated Prices': aggregatedPriceResults, @@ -240,7 +231,6 @@ module.exports = async ({ currentLastMintEvent, currentWeekOfInflation, oldExrates, - oracleAddress, systemSuspended, }; }; diff --git a/test/contracts/BaseSynthetix.js b/test/contracts/BaseSynthetix.js index 0cfae0a64c..c9a78e39ca 100644 --- a/test/contracts/BaseSynthetix.js +++ b/test/contracts/BaseSynthetix.js @@ -10,11 +10,13 @@ require('./common'); // import common test scaffolding const { setupContract, setupAllContracts } = require('./setup'); -const { currentTime, fastForward, toUnit } = require('../utils')(); +const { fastForward, toUnit } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke, + setupPriceAggregators, + updateAggregatorRates, updateRatesWithDefaults, setStatus, } = require('./helpers'); @@ -33,8 +35,6 @@ contract('BaseSynthetix', async accounts => { exchangeRates, debtCache, escrow, - oracle, - timestamp, addressResolver, systemSettings, systemStatus; @@ -68,9 +68,7 @@ contract('BaseSynthetix', async accounts => { ], })); - // Send a price update to guarantee we're not stale. - oracle = account1; - timestamp = await currentTime(); + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sETH]); }); addSnapshotBeforeRestoreAfterEach(); @@ -406,7 +404,7 @@ contract('BaseSynthetix', async accounts => { }); describe('when a user has exchanged into sETH', () => { beforeEach(async () => { - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); await baseSynthetix.issueSynths(toUnit('100'), { from: owner }); await baseSynthetix.exchange(sUSD, toUnit('10'), sETH, { from: owner }); @@ -434,13 +432,10 @@ contract('BaseSynthetix', async accounts => { // fast forward to get past initial SNX setting await fastForward((await exchangeRates.rateStalePeriod()).add(web3.utils.toBN('300'))); - timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['0.5', '1.25', '100'].map(toUnit), - timestamp, - { from: oracle } + ['0.5', '1.25', '100'].map(toUnit) ); await debtCache.takeDebtSnapshot(); }); @@ -449,9 +444,7 @@ contract('BaseSynthetix', async accounts => { }); describe('when SNX is also set', () => { beforeEach(async () => { - timestamp = await currentTime(); - - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [SNX], ['1'].map(toUnit)); }); it('then no stale rates', async () => { assert.equal(await baseSynthetix.anySynthOrSNXRateIsInvalid(), false); @@ -461,11 +454,7 @@ contract('BaseSynthetix', async accounts => { beforeEach(async () => { await fastForward((await exchangeRates.rateStalePeriod()).add(web3.utils.toBN('300'))); - timestamp = await currentTime(); - - await exchangeRates.updateRates([SNX, sAUD], ['0.1', '0.78'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX, sAUD], ['0.1', '0.78'].map(toUnit)); }); it('then anySynthOrSNXRateIsInvalid() returns true', async () => { @@ -522,7 +511,7 @@ contract('BaseSynthetix', async accounts => { beforeEach(async () => { // Ensure all synths have rates to allow issuance - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); }); it('should transfer using the ERC20 transfer function @gasprofile', async () => { @@ -707,28 +696,21 @@ contract('BaseSynthetix', async accounts => { it('should not allow transfer if the exchange rate for SNX is stale', async () => { await ensureTransferReverts(); - const timestamp = await currentTime(); - // now give some synth rates - await exchangeRates.updateRates([sAUD, sEUR], ['0.5', '1.25'].map(toUnit), timestamp, { - from: oracle, - }); + + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['0.5', '1.25'].map(toUnit)); await debtCache.takeDebtSnapshot(); await ensureTransferReverts(); // the remainder of the synths have prices - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], ['100'].map(toUnit)); await debtCache.takeDebtSnapshot(); await ensureTransferReverts(); // now give SNX rate - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['1'].map(toUnit)); // now SNX transfer should work await baseSynthetix.transfer(account2, value, { from: account1 }); @@ -740,28 +722,20 @@ contract('BaseSynthetix', async accounts => { it('should not allow transfer if the exchange rate for any synth is stale', async () => { await ensureTransferReverts(); - const timestamp = await currentTime(); - // now give SNX rate - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['1'].map(toUnit)); await debtCache.takeDebtSnapshot(); await ensureTransferReverts(); // now give some synth rates - await exchangeRates.updateRates([sAUD, sEUR], ['0.5', '1.25'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['0.5', '1.25'].map(toUnit)); await debtCache.takeDebtSnapshot(); await ensureTransferReverts(); // now give the remainder of synths rates - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], ['100'].map(toUnit)); await debtCache.takeDebtSnapshot(); // now SNX transfer should work @@ -847,8 +821,7 @@ contract('BaseSynthetix', async accounts => { it("should lock newly received synthetix if the user's collaterisation is too high", async () => { // Set sEUR for purposes of this test - const timestamp1 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('0.75')], timestamp1, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sEUR], [toUnit('0.75')]); await debtCache.takeDebtSnapshot(); const issuedSynthetixs = web3.utils.toBN('200000'); @@ -876,8 +849,7 @@ contract('BaseSynthetix', async accounts => { }); // Increase the value of sEUR relative to synthetix - const timestamp2 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('2.10')], timestamp2, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sEUR], [toUnit('2.10')]); await debtCache.takeDebtSnapshot(); // Ensure that the new synthetix account1 receives cannot be transferred out. @@ -892,10 +864,9 @@ contract('BaseSynthetix', async accounts => { await systemSettings.setPriceDeviationThresholdFactor(toUnit('5'), { from: owner }); // Set sAUD for purposes of this test - const timestamp1 = await currentTime(); const aud2usdrate = toUnit('2'); - await exchangeRates.updateRates([sAUD], [aud2usdrate], timestamp1, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sAUD], [aud2usdrate]); await debtCache.takeDebtSnapshot(); const issuedSynthetixs = web3.utils.toBN('200000'); @@ -917,9 +888,8 @@ contract('BaseSynthetix', async accounts => { await baseSynthetix.exchange(sUSD, issuedSynths, sAUD, { from: account1 }); // Increase the value of sAUD relative to synthetix - const timestamp2 = await currentTime(); const newAUDExchangeRate = toUnit('1'); - await exchangeRates.updateRates([sAUD], [newAUDExchangeRate], timestamp2, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sAUD], [newAUDExchangeRate]); await debtCache.takeDebtSnapshot(); const transferable2 = await baseSynthetix.transferableSynthetix(account1); diff --git a/test/contracts/CollateralErc20.js b/test/contracts/CollateralErc20.js index 6121bdacad..5e1b4146b6 100644 --- a/test/contracts/CollateralErc20.js +++ b/test/contracts/CollateralErc20.js @@ -8,11 +8,16 @@ const BN = require('bn.js'); const PublicEST8Decimals = artifacts.require('PublicEST8Decimals'); -const { fastForward, toUnit, currentTime } = require('../utils')(); +const { fastForward, toUnit } = require('../utils')(); const { setupAllContracts, setupContract } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions, setStatus } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + setStatus, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32, @@ -47,7 +52,7 @@ contract('CollateralErc20', async accounts => { let id; let proxy, tokenState; - const [deployerAccount, owner, oracle, , account1, account2] = accounts; + const [deployerAccount, owner, , , account1, account2] = accounts; let cerc20, managerState, @@ -85,17 +90,7 @@ contract('CollateralErc20', async accounts => { }; const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); }; const fastForwardAndUpdateRates = async seconds => { @@ -147,6 +142,8 @@ contract('CollateralErc20', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + managerState = await CollateralManagerState.new(owner, ZERO_ADDRESS, { from: deployerAccount }); const maxDebt = toUnit(10000000); @@ -304,28 +301,20 @@ contract('CollateralErc20', async accounts => { }); it('when the price falls by 25% our c ratio is 150%', async () => { - await exchangeRates.updateRates([sBTC], ['7500'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [7500].map(toUnit)); const ratio = await cerc20.collateralRatio(id); assert.bnEqual(ratio, toUnit(1.5)); }); it('when the price increases by 100% our c ratio is 400%', async () => { - await exchangeRates.updateRates([sBTC], ['20000'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sBTC], [20000].map(toUnit)); const ratio = await cerc20.collateralRatio(id); assert.bnEqual(ratio, toUnit(4)); }); it('when the price fallsby 50% our cratio is 100%', async () => { - await exchangeRates.updateRates([sBTC], ['5000'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sBTC], [5000].map(toUnit)); const ratio = await cerc20.collateralRatio(id); assert.bnEqual(ratio, toUnit(1)); }); @@ -346,10 +335,7 @@ contract('CollateralErc20', async accounts => { }); it('price changes should not change the cratio', async () => { - await exchangeRates.updateRates([sBTC], ['75'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sBTC], [75].map(toUnit)); const ratio = await cerc20.collateralRatio(id); assert.bnEqual(ratio, toUnit(2)); }); @@ -909,10 +895,7 @@ contract('CollateralErc20', async accounts => { let liquidationAmount; beforeEach(async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['7000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [7000].map(toUnit)); await issuesUSDToAccount(toUnit(5000), account2); @@ -962,10 +945,7 @@ contract('CollateralErc20', async accounts => { describe('when a loan needs to be completely liquidated', async () => { beforeEach(async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['5000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [5000].map(toUnit)); loan = await cerc20.loans(id); diff --git a/test/contracts/CollateralEth.js b/test/contracts/CollateralEth.js index 122337bf79..99635dba55 100644 --- a/test/contracts/CollateralEth.js +++ b/test/contracts/CollateralEth.js @@ -6,11 +6,16 @@ const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const BN = require('bn.js'); -const { fastForward, getEthBalance, toUnit, fromUnit, currentTime } = require('../utils')(); +const { fastForward, getEthBalance, toUnit, fromUnit } = require('../utils')(); const { setupAllContracts } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions, setStatus } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + setStatus, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32 } = require('../..'); @@ -20,6 +25,7 @@ contract('CollateralEth', async accounts => { const sUSD = toBytes32('sUSD'); const sETH = toBytes32('sETH'); + const sBTC = toBytes32('sBTC'); const oneETH = toUnit(1); const twoETH = toUnit(2); @@ -36,7 +42,7 @@ contract('CollateralEth', async accounts => { let loan; let id; - const [, owner, oracle, account1, account2] = accounts; + const [, owner, , account1, account2] = accounts; let ceth, managerState, @@ -101,6 +107,8 @@ contract('CollateralEth', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + await managerState.setAssociatedContract(manager.address, { from: owner }); FEE_ADDRESS = await feePool.FEE_ADDRESS(); @@ -139,17 +147,7 @@ contract('CollateralEth', async accounts => { }; const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); }; const fastForwardAndUpdateRates = async seconds => { @@ -219,28 +217,19 @@ contract('CollateralEth', async accounts => { }); it('when the price falls by 25% our c ratio is 150%', async () => { - await exchangeRates.updateRates([sETH], ['75'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(75)]); const ratio = await ceth.collateralRatio(id); assert.bnEqual(ratio, toUnit(1.5)); }); it('when the price increases by 100% our c ratio is 400%', async () => { - await exchangeRates.updateRates([sETH], ['200'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(200)]); const ratio = await ceth.collateralRatio(id); assert.bnEqual(ratio, toUnit(4)); }); it('when the price falls by 50% our cratio is 100%', async () => { - await exchangeRates.updateRates([sETH], ['50'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(50)]); const ratio = await ceth.collateralRatio(id); assert.bnEqual(ratio, toUnit(1)); }); @@ -261,10 +250,7 @@ contract('CollateralEth', async accounts => { }); it('price changes should not change the cratio', async () => { - await exchangeRates.updateRates([sETH], ['75'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(75)]); const ratio = await ceth.collateralRatio(id); assert.bnEqual(ratio, toUnit(2)); }); @@ -279,7 +265,7 @@ contract('CollateralEth', async accounts => { assert.bnClose(sUSDAmount, toUnit('200'), '100'); // $260 worth of eth should allow $200 (0.02) of sBTC to be issued. - const sBTCAmount = await ceth.maxLoan(toUnit('2.6'), toBytes32('sBTC')); + const sBTCAmount = await ceth.maxLoan(toUnit('2.6'), sBTC); assert.bnEqual(sBTCAmount, toUnit('0.02')); }); @@ -791,11 +777,7 @@ contract('CollateralEth', async accounts => { let liquidationAmount; beforeEach(async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['90'].map(toUnit), timestamp, { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(90)]); await issuesUSDToAccount(toUnit(1000), account2); liquidatorEthBalBefore = new BN(await getEthBalance(account2)); @@ -856,11 +838,7 @@ contract('CollateralEth', async accounts => { let liquidatorEthBalBefore; beforeEach(async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['50'].map(toUnit), timestamp, { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(50)]); loan = await ceth.loans(id); await issuesUSDToAccount(toUnit(1000), account2); diff --git a/test/contracts/CollateralManager.js b/test/contracts/CollateralManager.js index 9b6db8a531..ffa1634309 100644 --- a/test/contracts/CollateralManager.js +++ b/test/contracts/CollateralManager.js @@ -4,11 +4,16 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { toUnit, currentTime, fastForward } = require('../utils')(); +const { toUnit, fastForward } = require('../utils')(); const { setupAllContracts, setupContract, mockToken } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + onlyGivenAddressCanInvoke, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32, @@ -16,7 +21,7 @@ const { } = require('../..'); contract('CollateralManager', async accounts => { - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; const sETH = toBytes32('sETH'); const sUSD = toBytes32('sUSD'); @@ -58,17 +63,7 @@ contract('CollateralManager', async accounts => { }; const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); }; const fastForwardAndUpdateRates = async seconds => { @@ -127,6 +122,8 @@ contract('CollateralManager', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + maxDebt = toUnit(50000000); await managerState.setAssociatedContract(manager.address, { from: owner }); diff --git a/test/contracts/CollateralShort.js b/test/contracts/CollateralShort.js index 018893baad..3e9afb874a 100644 --- a/test/contracts/CollateralShort.js +++ b/test/contracts/CollateralShort.js @@ -4,11 +4,16 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { fastForward, toUnit, fromUnit, currentTime } = require('../utils')(); +const { fastForward, toUnit, fromUnit } = require('../utils')(); const { setupAllContracts } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions, setExchangeFeeRateForSynths } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + setExchangeFeeRateForSynths, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32 } = require('../..'); @@ -19,7 +24,7 @@ contract('CollateralShort', async accounts => { const sETH = toBytes32('sETH'); const sBTC = toBytes32('sBTC'); - const [, owner, oracle, , account1, account2] = accounts; + const [, owner, , , account1, account2] = accounts; let short, managerState, @@ -54,17 +59,7 @@ contract('CollateralShort', async accounts => { }; const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); }; const setupShort = async () => { @@ -103,6 +98,8 @@ contract('CollateralShort', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + await managerState.setAssociatedContract(manager.address, { from: owner }); FEE_ADDRESS = await feePool.FEE_ADDRESS(); @@ -506,10 +503,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['50'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(50)]); // simulate buying sETH for 50 susd. await sUSDSynth.transfer(owner, toUnit(50), { from: account1 }); @@ -531,10 +525,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['150'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(150)]); // simulate buying sETH for 150 susd. await sUSDSynth.transfer(owner, toUnit(150), { from: account1 }); @@ -566,10 +557,7 @@ contract('CollateralShort', async accounts => { }); it('liquidation should be capped to only fix the c ratio', async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['110'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(110)]); // When the ETH price increases 10% to $110, the short // which started at 130% should allow 0.18 ETH @@ -620,11 +608,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['150'].map(toUnit), timestamp, { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(150)]); await debtCache.takeDebtSnapshot(); result = await debtCache.cachedDebt(); assert.bnEqual(result, toUnit(111100)); @@ -665,10 +649,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['150'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(150)]); // 111100 + 50 - (2 * 50) = 111,050 @@ -712,10 +693,7 @@ contract('CollateralShort', async accounts => { await fastForwardAndUpdateRates(3600); - const timestamp = await currentTime(); - await exchangeRates.updateRates([sETH], ['50'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH], [toUnit(50)]); // 111100 - 50 + (2 * 50) = 111,150 diff --git a/test/contracts/CollateralUtil.js b/test/contracts/CollateralUtil.js index b3c1c848a8..767d17a8a6 100644 --- a/test/contracts/CollateralUtil.js +++ b/test/contracts/CollateralUtil.js @@ -4,11 +4,15 @@ const { contract, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { toUnit, currentTime } = require('../utils')(); +const { toUnit } = require('../utils')(); const { setupAllContracts, setupContract, mockToken } = require('./setup'); -const { ensureOnlyExpectedMutativeFunctions } = require('./helpers'); +const { + ensureOnlyExpectedMutativeFunctions, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32 } = require('../..'); @@ -27,7 +31,7 @@ contract('CollateralUtil', async accounts => { const name = 'Some name'; const symbol = 'TOKEN'; - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let cerc20, managerState, @@ -63,20 +67,6 @@ contract('CollateralUtil', async accounts => { await renBTC.transfer(receiver, issueAmount, { from: owner }); }; - const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); - }; - const deployCollateral = async ({ owner, manager, @@ -125,6 +115,8 @@ contract('CollateralUtil', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, sETH]); + await managerState.setAssociatedContract(manager.address, { from: owner }); ({ token: renBTC } = await mockToken({ @@ -186,7 +178,7 @@ contract('CollateralUtil', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - await updateRatesWithDefaults(); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); await issuesUSDToAccount(toUnit(1000), owner); await issuesBTCtoAccount(toUnit(10), owner); @@ -224,9 +216,7 @@ contract('CollateralUtil', async accounts => { }); it('when we start at 200%, we can take a 25% reduction in collateral prices', async () => { - await exchangeRates.updateRates([sBTC], ['7500'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(7500)]); amountToLiquidate = await cerc20.liquidationAmount(id); @@ -234,9 +224,7 @@ contract('CollateralUtil', async accounts => { }); it('when we start at 200%, a price shock of 30% in the collateral requires 25% of the loan to be liquidated', async () => { - await exchangeRates.updateRates([sBTC], ['7000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(7000)]); amountToLiquidate = await cerc20.liquidationAmount(id); @@ -244,9 +232,7 @@ contract('CollateralUtil', async accounts => { }); it('when we start at 200%, a price shock of 40% in the collateral requires 75% of the loan to be liquidated', async () => { - await exchangeRates.updateRates([sBTC], ['6000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(6000)]); amountToLiquidate = await cerc20.liquidationAmount(id); @@ -254,10 +240,7 @@ contract('CollateralUtil', async accounts => { }); it('when we start at 200%, a price shock of 45% in the collateral requires 100% of the loan to be liquidated', async () => { - await exchangeRates.updateRates([sBTC], ['5500'].map(toUnit), await currentTime(), { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(5500)]); amountToLiquidate = await cerc20.liquidationAmount(id); assert.bnClose(amountToLiquidate, toUnit(5000), '10000'); @@ -279,9 +262,7 @@ contract('CollateralUtil', async accounts => { }); it('when BTC is @ $20000 and we are liquidating 1000 sUSD, then redeem 0.055 BTC', async () => { - await exchangeRates.updateRates([sBTC], ['20000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(20000)]); collateralRedeemed = await util.collateralRedeemed(sUSD, oneThousandsUSD, collateralKey); @@ -289,9 +270,7 @@ contract('CollateralUtil', async accounts => { }); it('when BTC is @ $7000 and we are liquidating 2500 sUSD, then redeem 0.36666 ETH', async () => { - await exchangeRates.updateRates([sBTC], ['7000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(7000)]); collateralRedeemed = await util.collateralRedeemed(sUSD, toUnit(2500), collateralKey); @@ -303,9 +282,7 @@ contract('CollateralUtil', async accounts => { assert.bnEqual(collateralRedeemed, toUnit(1.1)); - await exchangeRates.updateRates([sBTC], ['1000'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], [toUnit(1000)]); collateralRedeemed = await util.collateralRedeemed(sBTC, toUnit(1), collateralKey); diff --git a/test/contracts/DebtCache.js b/test/contracts/DebtCache.js index 75de4c34fc..5694968ed8 100644 --- a/test/contracts/DebtCache.js +++ b/test/contracts/DebtCache.js @@ -15,6 +15,8 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { @@ -35,7 +37,7 @@ contract('DebtCache', async accounts => { ].map(toBytes32); const synthKeys = [sUSD, sAUD, sEUR, sETH, SNX]; - const [deployerAccount, owner, oracle, account1, account2] = accounts; + const [deployerAccount, owner, , account1, account2] = accounts; const oneETH = toUnit('1.0'); const twoETH = toUnit('2.0'); @@ -49,7 +51,6 @@ contract('DebtCache', async accounts => { sETHContract, sEURContract, sAUDContract, - timestamp, debtCache, issuer, synths, @@ -279,18 +280,17 @@ contract('DebtCache', async accounts => { 'WETH', ], })); + + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sETH, ETH, iETH]); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, SNX, sETH, ETH, iETH], - ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit), - timestamp, - { from: oracle } + ['0.5', '1.25', '10', '200', '200', '200'].map(toUnit) ); // set a 0.3% default exchange fee rate @@ -395,11 +395,10 @@ contract('DebtCache', async accounts => { // set default issuance ratio of 0.2 await systemSettings.setIssuanceRatio(toUnit('0.2'), { from: owner }); // set up initial prices - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['0.5', '2', '100'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100'].map(toUnit) ); await debtCache.takeDebtSnapshot(); @@ -451,9 +450,7 @@ contract('DebtCache', async accounts => { assert.bnEqual(result[0], toUnit(550)); assert.isFalse(result[1]); - await exchangeRates.updateRates([sAUD, sEUR], ['1', '3'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['1', '3'].map(toUnit)); await debtCache.takeDebtSnapshot(); assert.bnEqual((await debtCache.cacheInfo()).debt, toUnit(700)); result = await debtCache.currentDebt(); @@ -475,13 +472,10 @@ contract('DebtCache', async accounts => { }); it('updates the cached values for all individual synths', async () => { - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['1', '3', '200'].map(toUnit), - await currentTime(), - { - from: oracle, - } + ['1', '3', '200'].map(toUnit) ); await debtCache.takeDebtSnapshot(); let debts = await debtCache.currentSynthDebts([sUSD, sEUR, sAUD, sETH]); @@ -509,11 +503,10 @@ contract('DebtCache', async accounts => { assert.isTrue((await debtCache.cacheInfo()).isInvalid); // Revalidate the cache once rates are no longer stale - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, SNX, sETH, ETH, iETH], - ['0.5', '2', '100', '200', '200', '200'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100', '200', '200', '200'].map(toUnit) ); const tx2 = await debtCache.takeDebtSnapshot(); assert.isFalse((await debtCache.cacheInfo()).isInvalid); @@ -530,11 +523,10 @@ contract('DebtCache', async accounts => { await fastForward(snapshotStaleTime + 10); // ensure no actual rates are stale. - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH, SNX], - ['0.5', '2', '100', '1'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100', '1'].map(toUnit) ); const info = await debtCache.cacheInfo(); @@ -598,13 +590,11 @@ contract('DebtCache', async accounts => { const snapshotStaleTime = await systemSettings.debtSnapshotStaleTime(); await fastForward(snapshotStaleTime + 10); // ensure no actual rates are stale. - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH, SNX], - ['0.5', '2', '100', '1'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100', '1'].map(toUnit) ); - await assert.revert( synthetix.issueSynths(toUnit('10'), { from: account1 }), 'A synth or SNX rate is invalid' @@ -672,13 +662,10 @@ contract('DebtCache', async accounts => { it('allows resynchronisation of subsets of synths', async () => { await debtCache.takeDebtSnapshot(); - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['1', '3', '200'].map(toUnit), - await currentTime(), - { - from: oracle, - } + ['1', '3', '200'].map(toUnit) ); // First try a single currency, ensuring that the others have not been altered. @@ -712,11 +699,10 @@ contract('DebtCache', async accounts => { assert.isTrue((await debtCache.cacheInfo()).isInvalid); // But even if we update all rates, we can't revalidate the cache using the partial update function - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['0.5', '2', '100'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '2', '100'].map(toUnit) ); const tx2 = await debtCache.updateCachedSynthDebts([sAUD, sEUR, sETH]); assert.isTrue((await debtCache.cacheInfo()).isInvalid); @@ -727,13 +713,10 @@ contract('DebtCache', async accounts => { it('properly emits events', async () => { await debtCache.takeDebtSnapshot(); - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH], - ['1', '3', '200'].map(toUnit), - await currentTime(), - { - from: oracle, - } + ['1', '3', '200'].map(toUnit) ); const tx = await debtCache.updateCachedSynthDebts([sAUD]); @@ -998,9 +981,7 @@ contract('DebtCache', async accounts => { const debts = await debtCache.cachedSynthDebts([sAUD, sEUR]); - await exchangeRates.updateRates([sAUD, sEUR], ['1', '1'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['1', '1'].map(toUnit)); await synthetix.exchange(sEUR, toUnit(10), sAUD, { from: account1 }); const postDebts = await debtCache.cachedSynthDebts([sAUD, sEUR]); @@ -1031,9 +1012,7 @@ contract('DebtCache', async accounts => { // set a high price deviation threshold factor to be sure it doesn't trigger here await systemSettings.setPriceDeviationThresholdFactor(toUnit('99'), { from: owner }); - await exchangeRates.updateRates([sAUD, sEUR], ['2', '1'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, sEUR], ['2', '1'].map(toUnit)); await fastForward(100); diff --git a/test/contracts/Depot.js b/test/contracts/Depot.js index 1fafcec800..00ce2420c8 100644 --- a/test/contracts/Depot.js +++ b/test/contracts/Depot.js @@ -5,7 +5,6 @@ const { contract, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { - currentTime, fastForward, getEthBalance, toUnit, @@ -17,6 +16,8 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { mockToken, setupAllContracts } = require('./setup'); @@ -26,7 +27,7 @@ const { toBytes32 } = require('../..'); contract('Depot', async accounts => { let synthetix, synth, depot, addressResolver, systemStatus, exchangeRates, ethRate, snxRate; - const [, owner, oracle, fundsWallet, address1, address2, address3] = accounts; + const [, owner, , fundsWallet, address1, address2, address3] = accounts; const [SNX, ETH] = ['SNX', 'ETH'].map(toBytes32); @@ -71,19 +72,16 @@ contract('Depot', async accounts => { 'Issuer', ], })); + + await setupPriceAggregators(exchangeRates, owner, [ETH]); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - const timestamp = await currentTime(); - snxRate = toUnit('0.1'); ethRate = toUnit('172'); - - await exchangeRates.updateRates([SNX, ETH], [snxRate, ethRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX, ETH], [snxRate, ethRate]); }); it('should set constructor params on deployment', async () => { @@ -778,10 +776,7 @@ contract('Depot', async accounts => { ); }); it('when the purchaser supplies a rate and the rate is changed in by the oracle', async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX, ETH], ['0.1', '134'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX, ETH], ['0.1', '134'].map(toUnit)); await assert.revert( depot.exchangeEtherForSynthsAtRate(ethRate, payload), 'Guaranteed rate would not be received' @@ -830,10 +825,7 @@ contract('Depot', async accounts => { ); }); it('when the purchaser supplies a rate and the rate is changed in by the oracle', async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX, ETH], ['0.1', '134'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX, ETH], ['0.1', '134'].map(toUnit)); await assert.revert( depot.exchangeEtherForSNXAtRate(ethRate, snxRate, ethToSendFromPurchaser), 'Guaranteed ether rate would not be received' @@ -894,10 +886,7 @@ contract('Depot', async accounts => { ); }); it('when the purchaser supplies a rate and the rate is changed in by the oracle', async () => { - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], ['0.05'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['0.05'].map(toUnit)); await assert.revert( depot.exchangeSynthsForSNXAtRate(synthsToSend, snxRate, fromPurchaser), 'Guaranteed rate would not be received' diff --git a/test/contracts/EtherWrapper.js b/test/contracts/EtherWrapper.js index 70e1d612f0..c5b6b105a9 100644 --- a/test/contracts/EtherWrapper.js +++ b/test/contracts/EtherWrapper.js @@ -4,12 +4,14 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, toUnit, multiplyDecimal } = require('../utils')(); +const { toUnit, multiplyDecimal } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, getDecodedLogs, decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -23,7 +25,7 @@ contract('EtherWrapper', async accounts => { const ONE = toBN('1'); - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let systemSettings, feePool, @@ -35,8 +37,7 @@ contract('EtherWrapper', async accounts => { sUSDSynth, sETHSynth, etherWrapper, - weth, - timestamp; + weth; const calculateETHToUSD = async feesInETH => { // Ask the Depot how many sUSD I will get for this ETH @@ -93,12 +94,10 @@ contract('EtherWrapper', async accounts => { await systemSettings.setEtherWrapperBurnFeeRate(toUnit('0.005'), { from: owner }); FEE_ADDRESS = await feePool.FEE_ADDRESS(); - timestamp = await currentTime(); + await setupPriceAggregators(exchangeRates, owner, [sETH, ETH]); // Depot requires ETH rates - await exchangeRates.updateRates([sETH, ETH], ['1500', '1500'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, ETH], ['1500', '1500'].map(toUnit)); }); addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/ExchangeRates.js b/test/contracts/ExchangeRates.js index 15e7aae97d..c5a78a4cfb 100644 --- a/test/contracts/ExchangeRates.js +++ b/test/contracts/ExchangeRates.js @@ -17,9 +17,11 @@ const { ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke, convertToDecimals, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); -const { setupContract, setupAllContracts } = require('./setup'); +const { setupAllContracts } = require('./setup'); const { toBytes32, @@ -31,33 +33,9 @@ const { toBN } = require('web3-utils'); const MockAggregator = artifacts.require('MockAggregatorV2V3'); -const getRandomCurrencyKey = () => - Math.random() - .toString(36) - .substring(2, 6) - .toUpperCase(); - -const createRandomKeysAndRates = quantity => { - const uniqueCurrencyKeys = {}; - for (let i = 0; i < quantity; i++) { - const rate = Math.random() * 100; - const key = toBytes32(getRandomCurrencyKey()); - uniqueCurrencyKeys[key] = web3.utils.toWei(rate.toFixed(18), 'ether'); - } - - const rates = []; - const currencyKeys = []; - Object.entries(uniqueCurrencyKeys).forEach(([key, rate]) => { - currencyKeys.push(key); - rates.push(rate); - }); - - return { currencyKeys, rates }; -}; - contract('Exchange Rates', async accounts => { const [deployerAccount, owner, oracle, dexPriceAggregator, accountOne, accountTwo] = accounts; - const [SNX, sJPY, sETH, sXTZ, sBNB, sUSD, sEUR, sAUD, fastGasPrice] = [ + const [SNX, sJPY, sETH, sXTZ, sBNB, sUSD, sEUR, sAUD, GOLD, fastGasPrice] = [ 'SNX', 'sJPY', 'sETH', @@ -66,6 +44,7 @@ contract('Exchange Rates', async accounts => { 'sUSD', 'sEUR', 'sAUD', + 'GOLD', 'fastGasPrice', ].map(toBytes32); let instance; @@ -73,19 +52,10 @@ contract('Exchange Rates', async accounts => { let aggregatorJPY; let aggregatorXTZ; let aggregatorFastGasPrice; - let initialTime; - let timeSent; - let resolver; let mockFlagsInterface; const itIncludesCorrectMutativeFunctions = contract => { - const baseFunctions = [ - 'addAggregator', - 'deleteRate', - 'removeAggregator', - 'setOracle', - 'updateRates', - ]; + const baseFunctions = ['addAggregator', 'removeAggregator']; const withDexPricingFunctions = baseFunctions.concat(['setDexPriceAggregator']); it('only expected functions should be mutative', () => { @@ -102,497 +72,11 @@ contract('Exchange Rates', async accounts => { describe('constructor', () => { it('should set constructor params on deployment', async () => { assert.equal(await instance.owner(), owner); - assert.equal(await instance.oracle(), oracle); - - assert.etherEqual(await instance.rateForCurrency(sUSD), '1'); - assert.etherEqual(await instance.rateForCurrency(SNX), '0.2'); - - // Ensure that when the rate isn't found, 0 is returned as the exchange rate. - assert.etherEqual(await instance.rateForCurrency(toBytes32('OTHER')), '0'); - - const lastUpdatedTimeSUSD = await instance.lastRateUpdateTimes.call(sUSD); - assert.isAtLeast(lastUpdatedTimeSUSD.toNumber(), initialTime); - - const lastUpdatedTimeOTHER = await instance.lastRateUpdateTimes.call(toBytes32('OTHER')); - assert.equal(lastUpdatedTimeOTHER.toNumber(), 0); - - const lastUpdatedTimeSNX = await instance.lastRateUpdateTimes.call(SNX); - assert.isAtLeast(lastUpdatedTimeSNX.toNumber(), initialTime); - - const sUSDRate = await instance.rateForCurrency(sUSD); - assert.bnEqual(sUSDRate, toUnit('1')); - }); - - it('two different currencies in same array should mean that the second one overrides', async () => { - const creationTime = await currentTime(); - const firstAmount = '4.33'; - const secondAmount = firstAmount + 10; - const instance = await setupContract({ - accounts, - contract, - args: [ - owner, - oracle, - resolver.address, - [toBytes32('CARTER'), toBytes32('CARTOON')], - [web3.utils.toWei(firstAmount, 'ether'), web3.utils.toWei(secondAmount, 'ether')], - ], - }); - - assert.etherEqual(await instance.rateForCurrency(toBytes32('CARTER')), firstAmount); - assert.etherEqual(await instance.rateForCurrency(toBytes32('CARTOON')), secondAmount); - - const lastUpdatedTime = await instance.lastRateUpdateTimes.call(toBytes32('CARTER')); - assert.isAtLeast(lastUpdatedTime.toNumber(), creationTime); - }); - - it('should revert when number of currency keys > new rates length on create', async () => { - await assert.revert( - setupContract({ - accounts, - contract, - args: [ - owner, - oracle, - resolver.address, - [SNX, toBytes32('GOLD')], - [web3.utils.toWei('0.2', 'ether')], - ], - }), - 'Currency key length and rate length must match' - ); - }); - - it('should limit to 32 bytes if currency key > 32 bytes on create', async () => { - const creationTime = await currentTime(); - const amount = '4.33'; - const instance = await setupContract({ - accounts, - contract, - args: [ - owner, - oracle, - resolver.address, - [toBytes32('ABCDEFGHIJKLMNOPQRSTUVXYZ1234567')], - [web3.utils.toWei(amount, 'ether')], - ], - }); - - assert.etherEqual( - await instance.rateForCurrency(toBytes32('ABCDEFGHIJKLMNOPQRSTUVXYZ1234567')), - amount - ); - assert.etherNotEqual( - await instance.rateForCurrency(toBytes32('ABCDEFGHIJKLMNOPQRSTUVXYZ123456')), - amount - ); - - const lastUpdatedTime = await instance.lastRateUpdateTimes.call( - toBytes32('ABCDEFGHIJKLMNOPQRSTUVXYZ1234567') - ); - assert.isAtLeast(lastUpdatedTime.toNumber(), creationTime); - }); - - it("shouldn't be able to set exchange rate to 0 on create", async () => { - await assert.revert( - setupContract({ - accounts, - contract, - args: [owner, oracle, resolver.address, [SNX], ['0']], - }), - 'Zero is not a valid rate, please call deleteRate instead' - ); - }); - - it('should be able to handle lots of currencies on creation', async () => { - const creationTime = await currentTime(); - const numberOfCurrencies = 80; - const { currencyKeys, rates } = createRandomKeysAndRates(numberOfCurrencies); - - const instance = await setupContract({ - accounts, - contract, - args: [owner, oracle, resolver.address, currencyKeys, rates], - }); - - for (let i = 0; i < currencyKeys.length; i++) { - assert.bnEqual(await instance.rateForCurrency(currencyKeys[i]), rates[i]); - const lastUpdatedTime = await instance.lastRateUpdateTimes.call(currencyKeys[i]); - assert.isAtLeast(lastUpdatedTime.toNumber(), creationTime); - } - }); - }); - }; - - // Oracle rates - - const itUpdatesRates = () => { - describe('updateRates()', () => { - it('should be able to update rates of only one currency without affecting other rates', async () => { - await fastForward(1); - - await instance.updateRates( - [toBytes32('lABC'), toBytes32('lDEF'), toBytes32('lGHI')], - [ - web3.utils.toWei('1.3', 'ether'), - web3.utils.toWei('2.4', 'ether'), - web3.utils.toWei('3.5', 'ether'), - ], - timeSent, - { from: oracle } - ); - - await fastForward(10); - const updatedTime = timeSent + 10; - - const updatedRate = '64.33'; - await instance.updateRates( - [toBytes32('lABC')], - [web3.utils.toWei(updatedRate, 'ether')], - updatedTime, - { from: oracle } - ); - - const updatedTimelDEF = await instance.lastRateUpdateTimes.call(toBytes32('lDEF')); - const updatedTimelGHI = await instance.lastRateUpdateTimes.call(toBytes32('lGHI')); - - assert.etherEqual(await instance.rateForCurrency(toBytes32('lABC')), updatedRate); - assert.etherEqual(await instance.rateForCurrency(toBytes32('lDEF')), '2.4'); - assert.etherEqual(await instance.rateForCurrency(toBytes32('lGHI')), '3.5'); - - const lastUpdatedTimeLABC = await instance.lastRateUpdateTimes.call(toBytes32('lABC')); - assert.equal(lastUpdatedTimeLABC.toNumber(), updatedTime); - const lastUpdatedTimeLDEF = await instance.lastRateUpdateTimes.call(toBytes32('lDEF')); - assert.equal(lastUpdatedTimeLDEF.toNumber(), updatedTimelDEF.toNumber()); - const lastUpdatedTimeLGHI = await instance.lastRateUpdateTimes.call(toBytes32('lGHI')); - assert.equal(lastUpdatedTimeLGHI.toNumber(), updatedTimelGHI.toNumber()); - }); - - it('should be able to update rates of all currencies', async () => { - await fastForward(1); - - await instance.updateRates( - [toBytes32('lABC'), toBytes32('lDEF'), toBytes32('lGHI')], - [ - web3.utils.toWei('1.3', 'ether'), - web3.utils.toWei('2.4', 'ether'), - web3.utils.toWei('3.5', 'ether'), - ], - timeSent, - { from: oracle } - ); - - await fastForward(5); - const updatedTime = timeSent + 5; - - const updatedRate1 = '64.33'; - const updatedRate2 = '2.54'; - const updatedRate3 = '10.99'; - await instance.updateRates( - [toBytes32('lABC'), toBytes32('lDEF'), toBytes32('lGHI')], - [ - web3.utils.toWei(updatedRate1, 'ether'), - web3.utils.toWei(updatedRate2, 'ether'), - web3.utils.toWei(updatedRate3, 'ether'), - ], - updatedTime, - { from: oracle } - ); - - assert.etherEqual(await instance.rateForCurrency(toBytes32('lABC')), updatedRate1); - assert.etherEqual(await instance.rateForCurrency(toBytes32('lDEF')), updatedRate2); - assert.etherEqual(await instance.rateForCurrency(toBytes32('lGHI')), updatedRate3); - - const lastUpdatedTimeLABC = await instance.lastRateUpdateTimes.call(toBytes32('lABC')); - assert.equal(lastUpdatedTimeLABC.toNumber(), updatedTime); - const lastUpdatedTimeLDEF = await instance.lastRateUpdateTimes.call(toBytes32('lDEF')); - assert.equal(lastUpdatedTimeLDEF.toNumber(), updatedTime); - const lastUpdatedTimeLGHI = await instance.lastRateUpdateTimes.call(toBytes32('lGHI')); - assert.equal(lastUpdatedTimeLGHI.toNumber(), updatedTime); - }); - - it('should revert when trying to set sUSD price', async () => { - await fastForward(1); - - await assert.revert( - instance.updateRates([sUSD], [web3.utils.toWei('1.0', 'ether')], timeSent, { - from: oracle, - }), - "Rate of sUSD cannot be updated, it's always UNIT" - ); - }); - - it('should emit RatesUpdated event when rate updated', async () => { - const rates = [ - web3.utils.toWei('1.3', 'ether'), - web3.utils.toWei('2.4', 'ether'), - web3.utils.toWei('3.5', 'ether'), - ]; - - const keys = ['lABC', 'lDEF', 'lGHI']; - const currencyKeys = keys.map(toBytes32); - const txn = await instance.updateRates(currencyKeys, rates, await currentTime(), { - from: oracle, - }); - - assert.eventEqual(txn, 'RatesUpdated', { - currencyKeys, - newRates: rates, - }); - }); - - it('should be able to handle lots of currency updates', async () => { - const numberOfCurrencies = 150; - const { currencyKeys, rates } = createRandomKeysAndRates(numberOfCurrencies); - - const updatedTime = await currentTime(); - await instance.updateRates(currencyKeys, rates, updatedTime, { from: oracle }); - - for (let i = 0; i < currencyKeys.length; i++) { - assert.equal(await instance.rateForCurrency(currencyKeys[i]), rates[i]); - const lastUpdatedTime = await instance.lastRateUpdateTimes.call(currencyKeys[i]); - assert.equal(lastUpdatedTime.toNumber(), updatedTime); - } - }); - - it('should revert when currency keys length != new rates length on update', async () => { - await assert.revert( - instance.updateRates( - [sUSD, SNX, toBytes32('GOLD')], - [web3.utils.toWei('1', 'ether'), web3.utils.toWei('0.2', 'ether')], - await currentTime(), - { from: oracle } - ), - 'Currency key array length must match rates array length' - ); - }); - - it('should not be able to set exchange rate to 0 on update', async () => { - await assert.revert( - instance.updateRates( - [toBytes32('ZERO')], - [web3.utils.toWei('0', 'ether')], - await currentTime(), - { from: oracle } - ), - 'Zero is not a valid rate, please call deleteRate instead' - ); - }); - - it('only oracle can update exchange rates', async () => { - await onlyGivenAddressCanInvoke({ - fnc: instance.updateRates, - args: [ - [toBytes32('GOLD'), toBytes32('FOOL')], - [web3.utils.toWei('10', 'ether'), web3.utils.toWei('0.9', 'ether')], - timeSent, - ], - address: oracle, - accounts, - skipPassCheck: true, - reason: 'Only the oracle can perform this action', - }); - - assert.etherNotEqual(await instance.rateForCurrency(toBytes32('GOLD')), '10'); - assert.etherNotEqual(await instance.rateForCurrency(toBytes32('FOOL')), '0.9'); - - const updatedTime = await currentTime(); - - await instance.updateRates( - [toBytes32('GOLD'), toBytes32('FOOL')], - [web3.utils.toWei('10', 'ether'), web3.utils.toWei('0.9', 'ether')], - updatedTime, - { from: oracle } - ); - assert.etherEqual(await instance.rateForCurrency(toBytes32('GOLD')), '10'); - assert.etherEqual(await instance.rateForCurrency(toBytes32('FOOL')), '0.9'); - - const lastUpdatedTimeGOLD = await instance.lastRateUpdateTimes.call(toBytes32('GOLD')); - assert.equal(lastUpdatedTimeGOLD.toNumber(), updatedTime); - const lastUpdatedTimeFOOL = await instance.lastRateUpdateTimes.call(toBytes32('FOOL')); - assert.equal(lastUpdatedTimeFOOL.toNumber(), updatedTime); - }); - - it('should not be able to update rates if they are too far in the future', async () => { - const timeTooFarInFuture = (await currentTime()) + 10 * 61; - await assert.revert( - instance.updateRates( - [toBytes32('GOLD')], - [web3.utils.toWei('1', 'ether')], - timeTooFarInFuture, - { from: oracle } - ), - 'Time is too far into the future' - ); - }); - }); - }; - - const itSetsOracle = () => { - describe('setOracle()', () => { - it("only the owner should be able to change the oracle's address", async () => { - await onlyGivenAddressCanInvoke({ - fnc: instance.setOracle, - args: [oracle], - address: owner, - accounts, - skipPassCheck: true, - }); - - await instance.setOracle(accountOne, { from: owner }); - - assert.equal(await instance.oracle.call(), accountOne); - assert.notEqual(await instance.oracle.call(), oracle); - }); - - it('should emit event on successful oracle address update', async () => { - // Ensure oracle is set to oracle address originally - await instance.setOracle(oracle, { from: owner }); - assert.equal(await instance.oracle.call(), oracle); - - const txn = await instance.setOracle(accountOne, { from: owner }); - assert.eventEqual(txn, 'OracleUpdated', { - newOracle: accountOne, - }); - }); - }); - }; - - const itDeletesRates = () => { - describe('deleteRate()', () => { - it('should be able to remove specific rate', async () => { - const foolsRate = '0.002'; - const encodedRateGOLD = toBytes32('GOLD'); - - await instance.updateRates( - [encodedRateGOLD, toBytes32('FOOL')], - [web3.utils.toWei('10.123', 'ether'), web3.utils.toWei(foolsRate, 'ether')], - timeSent, - { from: oracle } - ); - - const beforeRate = await instance.rateForCurrency(encodedRateGOLD); - const beforeRateUpdatedTime = await instance.lastRateUpdateTimes.call(encodedRateGOLD); - - await instance.deleteRate(encodedRateGOLD, { from: oracle }); - - const afterRate = await instance.rateForCurrency(encodedRateGOLD); - const afterRateUpdatedTime = await instance.lastRateUpdateTimes.call(encodedRateGOLD); - assert.notEqual(afterRate, beforeRate); - assert.equal(afterRate, '0'); - assert.notEqual(afterRateUpdatedTime, beforeRateUpdatedTime); - assert.equal(afterRateUpdatedTime, '0'); - - // Other rates are unaffected - assert.etherEqual(await instance.rateForCurrency(toBytes32('FOOL')), foolsRate); - }); - - it('only oracle can delete a rate', async () => { - // Assume that the contract is already set up with a valid oracle account called 'oracle' - - const encodedRateName = toBytes32('COOL'); - await instance.updateRates( - [encodedRateName], - [web3.utils.toWei('10.123', 'ether')], - await currentTime(), - { from: oracle } - ); - - await onlyGivenAddressCanInvoke({ - fnc: instance.deleteRate, - args: [encodedRateName], - accounts, - address: oracle, - reason: 'Only the oracle can perform this action', - }); - }); - - it("deleting rate that doesn't exist causes revert", async () => { - // This key shouldn't exist but let's do the best we can to ensure that it doesn't - const encodedCurrencyKey = toBytes32('7NEQ'); - const currentRate = await instance.rateForCurrency(encodedCurrencyKey); - if (currentRate > 0) { - await instance.deleteRate(encodedCurrencyKey, { from: oracle }); - } - - // Ensure rate deletion attempt results in revert - await assert.revert( - instance.deleteRate(encodedCurrencyKey, { from: oracle }), - 'Rate is zero' - ); - assert.etherEqual(await instance.rateForCurrency(encodedCurrencyKey), '0'); }); - it('should emit RateDeleted event when rate deleted', async () => { - const updatedTime = await currentTime(); - const rate = 'GOLD'; - const encodedRate = toBytes32(rate); - await instance.updateRates( - [encodedRate], - [web3.utils.toWei('10.123', 'ether')], - updatedTime, - { - from: oracle, - } - ); - - const txn = await instance.deleteRate(encodedRate, { from: oracle }); - assert.eventEqual(txn, 'RateDeleted', { currencyKey: encodedRate }); - }); - }); - }; - - const itReturnsRates = () => { - describe('getting rates', () => { - it('should be able to get exchange rate with key', async () => { - const updatedTime = await currentTime(); - const encodedRate = toBytes32('GOLD'); - const rateValueEncodedStr = web3.utils.toWei('10.123', 'ether'); - await instance.updateRates([encodedRate], [rateValueEncodedStr], updatedTime, { - from: oracle, - }); - - const rate = await instance.rateForCurrency(encodedRate); - assert.equal(rate, rateValueEncodedStr); - }); - - it('all users should be able to get exchange rate with key', async () => { - const updatedTime = await currentTime(); - const encodedRate = toBytes32('FETC'); - const rateValueEncodedStr = web3.utils.toWei('910.6661293879', 'ether'); - await instance.updateRates([encodedRate], [rateValueEncodedStr], updatedTime, { - from: oracle, - }); - - await instance.rateForCurrency(encodedRate, { from: accountOne }); - await instance.rateForCurrency(encodedRate, { from: accountTwo }); - await instance.rateForCurrency(encodedRate, { from: oracle }); - await instance.rateForCurrency(encodedRate, { from: owner }); - await instance.rateForCurrency(encodedRate, { from: deployerAccount }); - }); - - it('Fetching non-existent rate returns 0', async () => { - const encodedRateKey = toBytes32('GOLD'); - const currentRate = await instance.rateForCurrency(encodedRateKey); - if (currentRate > 0) { - await instance.deleteRate(encodedRateKey, { from: oracle }); - } - - const rate = await instance.rateForCurrency(encodedRateKey); - assert.equal(rate.toString(), '0'); - }); - - it('should be able to get the latest exchange rate and updated time', async () => { - const updatedTime = await currentTime(); - const encodedRate = toBytes32('GOLD'); - const rateValueEncodedStr = web3.utils.toWei('10.123', 'ether'); - await instance.updateRates([encodedRate], [rateValueEncodedStr], updatedTime, { - from: oracle, - }); - - const rateAndTime = await instance.rateAndUpdatedTime(encodedRate); - assert.equal(rateAndTime.rate, rateValueEncodedStr); - assert.bnEqual(rateAndTime.time, updatedTime); + it('returns correct values for sUSD after deployment ', async () => { + assert.bnEqual(await instance.rateForCurrency(sUSD), toUnit('1')); + assert.equal(await instance.lastRateUpdateTimes(sUSD), 0); }); }); }; @@ -620,62 +104,18 @@ contract('Exchange Rates', async accounts => { assert.equal(rateIsStale, false); }); - it('check if a single rate is stale', async () => { - // Set up rates for test - await systemSettings.setRateStalePeriod(30, { from: owner }); - const updatedTime = await currentTime(); - await instance.updateRates( - [toBytes32('ABC')], - [web3.utils.toWei('2', 'ether')], - updatedTime, - { - from: oracle, - } - ); - await fastForward(31); - - const rateIsStale = await instance.rateIsStale(toBytes32('ABC')); - assert.equal(rateIsStale, true); - }); - - it('check if a single rate is not stale', async () => { - // Set up rates for test - await systemSettings.setRateStalePeriod(30, { from: owner }); - const updatedTime = await currentTime(); - await instance.updateRates( - [toBytes32('ABC')], - [web3.utils.toWei('2', 'ether')], - updatedTime, - { - from: oracle, - } - ); - await fastForward(28); - - const rateIsStale = await instance.rateIsStale(toBytes32('ABC')); - assert.equal(rateIsStale, false); - }); - - it('ensure rate is considered stale if not set', async () => { + it('ensure stale if not set', async () => { // Set up rates for test await systemSettings.setRateStalePeriod(30, { from: owner }); - const encodedRateKey = toBytes32('GOLD'); - const currentRate = await instance.rateForCurrency(encodedRateKey); - if (currentRate > 0) { - await instance.deleteRate(encodedRateKey, { from: oracle }); - } - - const rateIsStale = await instance.rateIsStale(encodedRateKey); - assert.equal(rateIsStale, true); + assert.equal(await instance.rateIsStale(toBytes32('GOLD')), true); }); it('make sure anyone can check if rate is stale', async () => { - const rateKey = toBytes32('ABC'); - await instance.rateIsStale(rateKey, { from: oracle }); - await instance.rateIsStale(rateKey, { from: owner }); - await instance.rateIsStale(rateKey, { from: deployerAccount }); - await instance.rateIsStale(rateKey, { from: accountOne }); - await instance.rateIsStale(rateKey, { from: accountTwo }); + await instance.rateIsStale(sUSD, { from: oracle }); + await instance.rateIsStale(sUSD, { from: owner }); + await instance.rateIsStale(sUSD, { from: deployerAccount }); + await instance.rateIsStale(sUSD, { from: accountOne }); + await instance.rateIsStale(sUSD, { from: accountTwo }); }); }); }; @@ -683,29 +123,18 @@ contract('Exchange Rates', async accounts => { const itCalculatesInvalidRates = () => { describe('anyRateIsInvalid()', () => { describe('stale scenarios', () => { - it('should never allow sUSD to go stale via anyRateIsInvalid', async () => { - const keysArray = [SNX, toBytes32('GOLD')]; - - await instance.updateRates( - keysArray, - [web3.utils.toWei('0.1', 'ether'), web3.utils.toWei('0.2', 'ether')], - await currentTime(), - { from: oracle } - ); - assert.equal(await instance.anyRateIsInvalid(keysArray), false); + it('anyRateIsInvalid conforms to rateStalePeriod', async () => { + await setupAggregators([SNX, GOLD]); - await fastForward(await instance.rateStalePeriod()); + await updateRates([SNX, GOLD], [toUnit(0.1), toUnit(0.2)]); - await instance.updateRates( - [SNX, toBytes32('GOLD')], - [web3.utils.toWei('0.1', 'ether'), web3.utils.toWei('0.2', 'ether')], - await currentTime(), - { from: oracle } - ); + assert.equal(await instance.anyRateIsInvalid([SNX, GOLD]), false); - // Even though sUSD hasn't been updated since the stale rate period has expired, - // we expect that sUSD remains "not stale" - assert.equal(await instance.anyRateIsInvalid(keysArray), false); + await fastForward(await instance.rateStalePeriod()); + assert.equal(await instance.anyRateIsInvalid([SNX, GOLD]), true); + + await updateRates([SNX, GOLD], [toUnit(0.1), toUnit(0.2)]); + assert.equal(await instance.anyRateIsInvalid([SNX, GOLD]), false); }); it('should be able to confirm no rates are stale from a subset', async () => { @@ -741,20 +170,19 @@ contract('Exchange Rates', async accounts => { web3.utils.toWei('10', 'ether'), web3.utils.toWei('11', 'ether'), ]; + + await setupAggregators([...encodedRateKeys1, ...encodedRateKeys2, ...encodedRateKeys3]); + const updatedTime1 = await currentTime(); - await instance.updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1, { - from: oracle, - }); + await updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1); + await fastForward(5); const updatedTime2 = await currentTime(); - await instance.updateRates(encodedRateKeys2, encodedRateValues2, updatedTime2, { - from: oracle, - }); + await updateRates(encodedRateKeys2, encodedRateValues2, updatedTime2); + await fastForward(5); const updatedTime3 = await currentTime(); - await instance.updateRates(encodedRateKeys3, encodedRateValues3, updatedTime3, { - from: oracle, - }); + await updateRates(encodedRateKeys3, encodedRateValues3, updatedTime3); await fastForward(12); const rateIsInvalid = await instance.anyRateIsInvalid([ @@ -788,21 +216,18 @@ contract('Exchange Rates', async accounts => { web3.utils.toWei('8', 'ether'), ]; + await setupAggregators([...encodedRateKeys1, ...encodedRateKeys2, ...encodedRateKeys3]); + const updatedTime2 = await currentTime(); - await instance.updateRates(encodedRateKeys2, encodedRateValues2, updatedTime2, { - from: oracle, - }); + await updateRates(encodedRateKeys2, encodedRateValues2, updatedTime2); await fastForward(20); const updatedTime1 = await currentTime(); - await instance.updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1, { - from: oracle, - }); + await updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1); await fastForward(15); + const updatedTime3 = await currentTime(); - await instance.updateRates(encodedRateKeys3, encodedRateValues3, updatedTime3, { - from: oracle, - }); + await updateRates(encodedRateKeys3, encodedRateValues3, updatedTime3); await fastForward(6); const rateIsInvalid = await instance.anyRateIsInvalid([ @@ -815,23 +240,18 @@ contract('Exchange Rates', async accounts => { it('should be able to confirm a single rate (from a set of 1) is stale', async () => { // Set up rates for test await systemSettings.setRateStalePeriod(40, { from: owner }); - const updatedTime = await currentTime(); - await instance.updateRates( - [toBytes32('ABC')], - [web3.utils.toWei('2', 'ether')], - updatedTime, - { - from: oracle, - } - ); + const key = toBytes32('ABC'); + await setupAggregators([key]); + await updateRates([key], [web3.utils.toWei('2', 'ether')]); await fastForward(41); - const rateIsInvalid = await instance.anyRateIsInvalid([toBytes32('ABC')]); + const rateIsInvalid = await instance.anyRateIsInvalid([key]); assert.equal(rateIsInvalid, true); }); it('make sure anyone can check if any rates are stale', async () => { const rateKey = toBytes32('ABC'); + await setupAggregators([rateKey]); await instance.anyRateIsInvalid([rateKey], { from: oracle }); await instance.anyRateIsInvalid([rateKey], { from: owner }); await instance.anyRateIsInvalid([rateKey], { from: deployerAccount }); @@ -855,14 +275,13 @@ contract('Exchange Rates', async accounts => { web3.utils.toWei('4', 'ether'), ]; - const updatedTime1 = await currentTime(); - await instance.updateRates(encodedRateKeys1, encodedRateValues1, updatedTime1, { - from: oracle, - }); - const rateIsInvalid = await instance.anyRateIsInvalid([ - ...encodedRateKeys1, - toBytes32('RST'), - ]); + const staleKey = toBytes32('RST'); + const allKeys = [...encodedRateKeys1, staleKey]; + + await setupAggregators(allKeys); + await updateRates(encodedRateKeys1, encodedRateValues1); + + const rateIsInvalid = await instance.anyRateIsInvalid(allKeys); assert.equal(rateIsInvalid, true); }); }); @@ -874,20 +293,13 @@ contract('Exchange Rates', async accounts => { from: owner, }); }); - describe('when a regular and aggregated synth have rates', () => { + describe('when aggregated synth has rates', () => { beforeEach(async () => { const timestamp = await currentTime(); - await instance.updateRates([toBytes32('sGOLD')], [web3.utils.toWei('1')], timestamp, { - from: oracle, - }); await aggregatorJPY.setLatestAnswer(convertToDecimals(100, 8), timestamp); }); - it('then rateIsInvalid for both is false', async () => { - const rateIsInvalid = await instance.anyRateIsInvalid([ - toBytes32('sGOLD'), - sJPY, - sUSD, - ]); + it('then rateIsInvalid is false', async () => { + const rateIsInvalid = await instance.anyRateIsInvalid([sJPY, sUSD]); assert.equal(rateIsInvalid, false); }); @@ -901,12 +313,8 @@ contract('Exchange Rates', async accounts => { }); }); - it('then rateIsInvalid for both is still false', async () => { - const rateIsInvalid = await instance.anyRateIsInvalid([ - toBytes32('sGOLD'), - sJPY, - sUSD, - ]); + it('then rateIsInvalid is still false', async () => { + const rateIsInvalid = await instance.anyRateIsInvalid([sJPY, sUSD]); assert.equal(rateIsInvalid, false); }); @@ -914,12 +322,8 @@ contract('Exchange Rates', async accounts => { beforeEach(async () => { await mockFlagsInterface.flagAggregator(aggregatorJPY.address); }); - it('then rateIsInvalid for both is true', async () => { - const rateIsInvalid = await instance.anyRateIsInvalid([ - toBytes32('sGOLD'), - sJPY, - sUSD, - ]); + it('then rateIsInvalid is true', async () => { + const rateIsInvalid = await instance.anyRateIsInvalid([sJPY, sUSD]); assert.equal(rateIsInvalid, true); }); }); @@ -936,21 +340,13 @@ contract('Exchange Rates', async accounts => { const abc = toBytes32('lABC'); const timeSent = await currentTime(); const listOfKeys = [abc, toBytes32('lDEF'), toBytes32('lGHI')]; - await instance.updateRates( - listOfKeys.slice(0, 2), - [web3.utils.toWei('1.3', 'ether'), web3.utils.toWei('2.4', 'ether')], - timeSent, - { from: oracle } - ); + await setupAggregators(listOfKeys); + + await updateRates(listOfKeys.slice(0, 2), [toUnit('1.3'), toUnit('2.4')], timeSent); await fastForward(100); const newTimeSent = await currentTime(); - await instance.updateRates( - listOfKeys.slice(2), - [web3.utils.toWei('3.5', 'ether')], - newTimeSent, - { from: oracle } - ); + await updateRates(listOfKeys.slice(2), [toUnit('3.5')], newTimeSent); const lastUpdateTimes = await instance.lastRateUpdateTimesForCurrencies(listOfKeys); assert.notEqual(timeSent, newTimeSent); @@ -964,18 +360,14 @@ contract('Exchange Rates', async accounts => { const abc = toBytes32('lABC'); const def = toBytes32('lDEF'); const ghi = toBytes32('lGHI'); + await setupAggregators([abc, def, ghi]); + const timeSent = await currentTime(); - await instance.updateRates( - [abc, def], - [web3.utils.toWei('1.3', 'ether'), web3.utils.toWei('2.4', 'ether')], - timeSent, - { from: oracle } - ); + await updateRates([abc, def], [toUnit('1.3'), toUnit('2.4')], timeSent); + await fastForward(10000); const timeSent2 = await currentTime(); - await instance.updateRates([ghi], [web3.utils.toWei('2.4', 'ether')], timeSent2, { - from: oracle, - }); + await updateRates([ghi], [toUnit('2.4')], timeSent2); const [firstTS, secondTS] = await Promise.all([ instance.lastRateUpdateTimes(abc), @@ -989,21 +381,14 @@ contract('Exchange Rates', async accounts => { const itCalculatesEffectiveValue = () => { describe('effectiveValue() and effectiveValueAndRates()', () => { - let timestamp; - beforeEach(async () => { - timestamp = await currentTime(); - }); - describe('when a price is sent to the oracle', () => { beforeEach(async () => { // Send a price update to guarantee we're not depending on values from outside this test. - await instance.updateRates( - ['sAUD', 'sEUR', 'SNX'].map(toBytes32), - ['0.5', '1.25', '0.1'].map(toUnit), - timestamp, - { from: oracle } - ); + const keys = [sAUD, sEUR, SNX]; + await setupAggregators(keys); + await updateRates(keys, ['0.5', '1.25', '0.1'].map(toUnit)); }); + it('should correctly calculate an exchange rate in effectiveValue()', async () => { // 1 sUSD should be worth 2 sAUD. assert.bnEqual(await instance.effectiveValue(sUSD, toUnit('1'), sAUD), toUnit('2')); @@ -1019,12 +404,8 @@ contract('Exchange Rates', async accounts => { // Add stale period to the time to ensure we go stale. await fastForward((await instance.rateStalePeriod()) + 1); - timestamp = await currentTime(); - // Update all rates except sUSD. - await instance.updateRates([sEUR, SNX], ['1.25', '0.1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR, SNX], ['1.25', '0.1'].map(toUnit)); const amountOfSynthetixs = toUnit('10'); const amountOfEur = toUnit('0.8'); @@ -1034,11 +415,11 @@ contract('Exchange Rates', async accounts => { }); it('should return 0 when relying on a non-existant dest exchange rate in effectiveValue()', async () => { - assert.equal(await instance.effectiveValue(SNX, toUnit('10'), toBytes32('XYZ')), '0'); + assert.equal(await instance.effectiveValue(SNX, toUnit('10'), toBytes32('XYZ')), 0); }); - it('should return 0 when relying on a non-existing src rate in effectiveValue', async () => { - assert.equal(await instance.effectiveValue(toBytes32('XYZ'), toUnit('10'), SNX), '0'); + it('should revert when relying on a non-existing src rate in effectiveValue', async () => { + assert.equal(await instance.effectiveValue(toBytes32('XYZ'), toUnit('10'), SNX), 0); }); it('effectiveValueAndRates() should return rates as well with sUSD on one side', async () => { @@ -1080,8 +461,6 @@ contract('Exchange Rates', async accounts => { }); }; - // Aggregator rates and flags - const itReadsFromAggregator = () => { describe('when the flags interface is set', () => { beforeEach(async () => { @@ -1410,45 +789,27 @@ contract('Exchange Rates', async accounts => { await assert.invalidOpcode(instance.aggregatorKeys(1)); }); }); - describe('when the ratesAndInvalidForCurrencies is queried', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sXTZ, sUSD]); - }); - - it('then the rates are invalid again', () => { - assert.equal(response[1], true); - }); - - it('and JPY is 0 while the other is fine', () => { - assert.equal(response[0][0], '0'); - assert.bnEqual(response[0][1], toUnit(newRateXTZ.toString())); - }); + it('when the ratesAndInvalidForCurrencies is queried it returns 0', async () => { + assert.deepEqual( + await instance.ratesAndInvalidForCurrencies([sJPY, sXTZ, sUSD]), + [[0, toUnit(newRateXTZ), toUnit(1)], true] + ); }); describe('when rateAndInvalid is queried', () => { - let responseJPY; - let responseXTZ; - let responseUSD; - beforeEach(async () => { - responseJPY = await instance.rateAndInvalid(sJPY); - responseXTZ = await instance.rateAndInvalid(sXTZ); - responseUSD = await instance.rateAndInvalid(sUSD); + it('then JPY returns true', async () => { + assert.deepEqual(await instance.rateAndInvalid(sJPY), [0, true]); }); - it('then the rates are invalid again', () => { - assert.equal(responseJPY[1], true); + it('other rates are fine', async () => { + const responseXTZ = await instance.rateAndInvalid(sXTZ); + const responseUSD = await instance.rateAndInvalid(sUSD); + assert.equal(responseXTZ[1], false); assert.equal(responseUSD[1], false); - }); - - it('and JPY is 0 while the other is fine', () => { - assert.bnEqual(responseJPY[0], toUnit('0')); assert.bnEqual(responseXTZ[0], toUnit(newRateXTZ.toString())); assert.bnEqual(responseUSD[0], toUnit('1')); }); }); - - describe('when sJPY has a non-aggregated rate', () => {}); }); }); }); @@ -1511,295 +872,6 @@ contract('Exchange Rates', async accounts => { }); }); - describe('when a price already exists for sJPY', () => { - const oldPrice = 100; - let timeOldSent; - beforeEach(async () => { - timeOldSent = await currentTime(); - - await instance.updateRates( - [sJPY], - [web3.utils.toWei(oldPrice.toString())], - timeOldSent, - { - from: oracle, - } - ); - }); - describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sUSD]); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the value', () => { - assert.bnEqual(response[0][0], web3.utils.toWei(oldPrice.toString())); - }); - }); - describe('when rateAndInvalid is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.rateAndInvalid(sJPY); - }); - - it('then the rate is NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the value', () => { - assert.bnEqual(response[0], web3.utils.toWei(oldPrice.toString())); - }); - }); - - describe('when the price is inspected for sJPY', () => { - it('then the price is returned as expected', async () => { - const result = await instance.rateForCurrency(sJPY, { - from: accountOne, - }); - assert.equal(result.toString(), toUnit(oldPrice)); - }); - it('then the timestamp is returned as expected', async () => { - const result = await instance.lastRateUpdateTimes(sJPY, { - from: accountOne, - }); - assert.equal(result.toNumber(), timeOldSent); - }); - }); - - describe('when sJPY added as an aggregator (replacing existing)', () => { - beforeEach(async () => { - await instance.addAggregator(sJPY, aggregatorJPY.address, { - from: owner, - }); - }); - describe('when the price is fetched for sJPY', () => { - it('0 is returned', async () => { - const result = await instance.rateForCurrency(sJPY, { - from: accountOne, - }); - assert.equal(result.toNumber(), 0); - }); - }); - describe('when the timestamp is fetched for sJPY', () => { - it('0 is returned', async () => { - const result = await instance.lastRateUpdateTimes(sJPY, { - from: accountOne, - }); - assert.equal(result.toNumber(), 0); - }); - }); - describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY]); - }); - - it('then the rates are invalid', () => { - assert.equal(response[1], true); - }); - - it('with no value', () => { - assert.bnEqual(response[0][0], '0'); - }); - }); - describe('when the rateAndInvalid is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.rateAndInvalid(sJPY); - }); - - it('then the rate is invalid', () => { - assert.equal(response[1], true); - }); - - it('with no value', () => { - assert.bnEqual(response[0], '0'); - }); - }); - - describe('when the aggregator price is set to set a specific number (with support for 8 decimals)', () => { - const newRate = 9.55; - let timestamp; - beforeEach(async () => { - await fastForward(50); - timestamp = await currentTime(); - await aggregatorJPY.setLatestAnswer(convertToDecimals(newRate, 8), timestamp); - }); - - describe('when the price is fetched for sJPY', () => { - it('the new aggregator rate is returned instead of the old price', async () => { - const result = await instance.rateForCurrency(sJPY, { - from: accountOne, - }); - assert.bnEqual(result, toUnit(newRate.toString())); - }); - it('and the timestamp is the new one', async () => { - const result = await instance.lastRateUpdateTimes(sJPY, { - from: accountOne, - }); - assert.bnEqual(result.toNumber(), timestamp); - }); - }); - - describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sUSD]); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the value', () => { - assert.bnEqual(response[0][0], toUnit(newRate.toString())); - }); - }); - - describe('when rateAndInvalid is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.rateAndInvalid(sJPY); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the value', () => { - assert.bnEqual(response[0], toUnit(newRate.toString())); - }); - }); - - describe('when the aggregator is removed for sJPY', () => { - beforeEach(async () => { - await instance.removeAggregator(sJPY, { - from: owner, - }); - }); - describe('when a user queries the first entry in aggregatorKeys', () => { - it('then they are empty', async () => { - await assert.invalidOpcode(instance.aggregatorKeys(0)); - }); - }); - describe('when the price is inspected for sJPY', () => { - it('then the old price is returned', async () => { - const result = await instance.rateForCurrency(sJPY, { - from: accountOne, - }); - assert.equal(result.toString(), toUnit(oldPrice)); - }); - it('and the timestamp is returned as expected', async () => { - const result = await instance.lastRateUpdateTimes(sJPY, { - from: accountOne, - }); - assert.equal(result.toNumber(), timeOldSent); - }); - }); - describe('when the ratesAndInvalidForCurrencies is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sUSD]); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the old value', () => { - assert.bnEqual(response[0][0], web3.utils.toWei(oldPrice.toString())); - }); - }); - - describe('when the rateAndInvalid is queried with sJPY', () => { - let response; - beforeEach(async () => { - response = await instance.rateAndInvalid(sJPY); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the old value', () => { - assert.bnEqual(response[0], web3.utils.toWei(oldPrice.toString())); - }); - }); - }); - }); - }); - - describe('when sXTZ added as an aggregator', () => { - beforeEach(async () => { - await instance.addAggregator(sXTZ, aggregatorXTZ.address, { - from: owner, - }); - }); - describe('when the ratesAndInvalidForCurrencies is queried with sJPY and sXTZ', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sXTZ, sUSD]); - }); - - it('then the rates are invalid', () => { - assert.equal(response[1], true); - }); - - it('with sXTZ having no value', () => { - assert.bnEqual(response[0][0], web3.utils.toWei(oldPrice.toString())); - assert.bnEqual(response[0][1], '0'); - }); - }); - describe('when the rateAndInvalid is queried with sJPY and sXTZ', () => { - let responseJPY; - let responseXTZ; - beforeEach(async () => { - responseJPY = await instance.rateAndInvalid(sJPY); - responseXTZ = await instance.rateAndInvalid(sXTZ); - }); - - it('then the XTZ rate is invalid', () => { - assert.equal(responseJPY[1], false); - assert.equal(responseXTZ[1], true); - }); - - it('with sXTZ having no value', () => { - assert.bnEqual(responseJPY[0], web3.utils.toWei(oldPrice.toString())); - assert.bnEqual(responseXTZ[0], '0'); - }); - }); - - describe('when the aggregator price is set to set for sXTZ', () => { - const newRate = 99; - let timestamp; - beforeEach(async () => { - await fastForward(50); - timestamp = await currentTime(); - await aggregatorXTZ.setLatestAnswer(convertToDecimals(newRate, 8), timestamp); - }); - - describe('when the ratesAndInvalidForCurrencies is queried with sJPY and sXTZ', () => { - let response; - beforeEach(async () => { - response = await instance.ratesAndInvalidForCurrencies([sJPY, sXTZ, sUSD]); - }); - - it('then the rates are NOT invalid', () => { - assert.equal(response[1], false); - }); - - it('and equal to the values', () => { - assert.bnEqual(response[0][0], toUnit(oldPrice.toString())); - assert.bnEqual(response[0][1], toUnit(newRate.toString())); - }); - }); - }); - }); - }); describe('warning flags and invalid rates', () => { it('sUSD is never flagged / invalid.', async () => { assert.isFalse(await instance.rateIsFlagged(sUSD)); @@ -1861,24 +933,31 @@ contract('Exchange Rates', async accounts => { }); describe('roundIds for historical rates', () => { - it('getCurrentRoundId() by default is 0 for all synths except sUSD which is 1', async () => { - // Note: rates that were set in the truffle migration will be at 1, so we need to check - // other synths - assert.equal(await instance.getCurrentRoundId(sJPY), '0'); - assert.equal(await instance.getCurrentRoundId(sBNB), '0'); - assert.equal(await instance.getCurrentRoundId(sUSD), '1'); + it('getCurrentRoundId() returns 0 for unknown currencies', async () => { + assert.equal(await instance.getCurrentRoundId(sJPY), 0); + assert.equal(await instance.getCurrentRoundId(sBNB), 0); + }); + + it('getCurrentRoundId() is 0 for currencies with no updates', async () => { + await setupAggregators([sJPY, sBNB]); + assert.equal(await instance.getCurrentRoundId(sJPY), 0); + assert.equal(await instance.getCurrentRoundId(sBNB), 0); + }); + + it('getCurrentRoundId() is 0 for sUSD', async () => { + assert.equal(await instance.getCurrentRoundId(sUSD), 0); }); it('ratesAndUpdatedTimeForCurrencyLastNRounds() shows first entry for sUSD', async () => { - const timeOfsUSDRateSetOnInit = await instance.lastRateUpdateTimes(sUSD); assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sUSD, '3'), [ [toUnit('1'), '0', '0'], - [timeOfsUSDRateSetOnInit, '0', '0'], + [0, 0, 0], ]); }); - it('ratesAndUpdatedTimeForCurrencyLastNRounds() returns 0s for other currency keys', async () => { + it('ratesAndUpdatedTimeForCurrencyLastNRounds() returns 0s for other currencies without updates', async () => { const fiveZeros = new Array(5).fill('0'); - assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5'), [ + await setupAggregators([sJPY]); + assert.deepEqual(await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5'), [ fiveZeros, fiveZeros, ]); @@ -1899,145 +978,83 @@ contract('Exchange Rates', async accounts => { } }); - describe('and the sBNB rate (non-aggregator) has been set three times directly also', () => { - let timestamp; - - beforeEach(async () => { - for (let i = 0; i < 3; i++) { - timestamp = 10000; - await instance.updateRates([sBNB], [toUnit((1000 + i).toString())], timestamp + i, { - from: oracle, - }); - } - }); - describe('getCurrentRoundId())', () => { - describe('when invoked for an aggregator', () => { - it('getCurrentRound() returns the last entry', async () => { - await assert.equal((await instance.getCurrentRoundId(sJPY)).toString(), '3'); - }); - }); - describe('when invoked for a regular price', () => { - it('getCurrentRound() returns the last entry', async () => { - await assert.equal((await instance.getCurrentRoundId(sBNB)).toString(), '3'); - }); + describe('getCurrentRoundId())', () => { + describe('when invoked for an aggregator', () => { + it('getCurrentRound() returns the last entry', async () => { + assert.equal((await instance.getCurrentRoundId(sJPY)).toString(), '3'); }); }); - describe('rateAndTimestampAtRound()', () => { - it('when invoked for no price, returns no rate and no tme', async () => { + }); + describe('rateAndTimestampAtRound()', () => { + it('when invoked for no price returns 0', async () => { + assert.deepEqual(await instance.rateAndTimestampAtRound(toBytes32('TEST'), '0'), [ + 0, + 0, + ]); + }); + it('when invoked for an aggregator', async () => { + const assertRound = async ({ roundId }) => { const { rate, time } = await instance.rateAndTimestampAtRound( - toBytes32('TEST'), - '0' + sJPY, + roundId.toString() + ); + assert.bnEqual(rate, toUnit((100 + roundId - 1).toString())); + assert.bnEqual(time, toBN(1000 + roundId - 1)); + }; + await assertRound({ roundId: 1 }); + await assertRound({ roundId: 2 }); + await assertRound({ roundId: 3 }); + }); + }); + + describe('ratesAndUpdatedTimeForCurrencyLastNRounds()', () => { + describe('when invoked for a non-existant currency', () => { + it('then it returns zeros', async () => { + const fiveZeros = new Array(5).fill('0'); + assert.deepEqual( + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5'), + [fiveZeros, fiveZeros] ); - assert.equal(rate, '0'); - assert.equal(time, '0'); - }); - it('when invoked for an aggregator', async () => { - const assertRound = async ({ roundId }) => { - const { rate, time } = await instance.rateAndTimestampAtRound( - sJPY, - roundId.toString() - ); - assert.bnEqual(rate, toUnit((100 + roundId - 1).toString())); - assert.bnEqual(time, toBN(1000 + roundId - 1)); - }; - await assertRound({ roundId: 1 }); - await assertRound({ roundId: 2 }); - await assertRound({ roundId: 3 }); - }); - it('when invoked for a regular price', async () => { - const assertRound = async ({ roundId }) => { - const { rate, time } = await instance.rateAndTimestampAtRound( - sBNB, - roundId.toString() - ); - assert.bnEqual(rate, toUnit((1000 + roundId - 1).toString())); - assert.bnEqual(time, toBN(10000 + roundId - 1)); - }; - await assertRound({ roundId: 1 }); - await assertRound({ roundId: 2 }); - await assertRound({ roundId: 3 }); }); }); - - describe('ratesAndUpdatedTimeForCurrencyLastNRounds()', () => { - describe('when invoked for a non-existant currency', () => { - it('then it returns 0s', async () => { - const fiveZeros = new Array(5).fill('0'); - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sAUD, '5'), - [fiveZeros, fiveZeros] - ); - }); - }); - describe('when invoked for an aggregated price', () => { - it('then it returns the rates as expected', async () => { - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '3'), - [ - [toUnit('102'), toUnit('101'), toUnit('100')], - ['1002', '1001', '1000'], - ] - ); - }); - - it('then it returns the rates as expected, even over the edge', async () => { - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5'), - [ - [toUnit('102'), toUnit('101'), toUnit('100'), '0', '0'], - ['1002', '1001', '1000', '0', '0'], - ] - ); - }); + describe('when invoked for an aggregated price', () => { + it('then it returns the rates as expected', async () => { + assert.deepEqual( + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '3'), + [ + [toUnit('102'), toUnit('101'), toUnit('100')], + ['1002', '1001', '1000'], + ] + ); }); - describe('when invoked for a regular price', () => { - it('then it returns the rates as expected', async () => { - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sBNB, '3'), - [ - [toUnit('1002'), toUnit('1001'), toUnit('1000')], - ['10002', '10001', '10000'], - ] - ); - }); - it('then it returns the rates as expected, even over the edge', async () => { - assert.deepEqual( - await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sBNB, '5'), - [ - [toUnit('1002'), toUnit('1001'), toUnit('1000'), '0', '0'], - ['10002', '10001', '10000', '0', '0'], - ] - ); - }); + it('then it returns the rates as expected, even over the edge', async () => { + assert.deepEqual( + await instance.ratesAndUpdatedTimeForCurrencyLastNRounds(sJPY, '5'), + [ + [toUnit('102'), toUnit('101'), toUnit('100'), '0', '0'], + ['1002', '1001', '1000', '0', '0'], + ] + ); }); }); }); }); - describe('and both the aggregator and regular prices have been given three rates, 30seconds apart', () => { + describe('and the aggregator has been given three rates, 30seconds apart', () => { beforeEach(async () => { await aggregatorJPY.setLatestAnswer(convertToDecimals(100, 8), 30); // round 1 for sJPY await aggregatorJPY.setLatestAnswer(convertToDecimals(200, 8), 60); // round 2 for sJPY await aggregatorJPY.setLatestAnswer(convertToDecimals(300, 8), 90); // round 3 for sJPY - - await instance.updateRates([sBNB], [toUnit('1000')], '30', { from: oracle }); // round 1 for sBNB - await instance.updateRates([sBNB], [toUnit('2000')], '60', { from: oracle }); // round 2 for sBNB - await instance.updateRates([sBNB], [toUnit('3000')], '90', { from: oracle }); // round 3 for sBNB }); describe('getLastRoundIdBeforeElapsedSecs()', () => { describe('when getLastRoundIdBeforeElapsedSecs() is invoked with the first round and a waiting time of less than 30s', () => { it('then it receives round 1 - no change ', async () => { - // assert both aggregated price and regular prices work as expected assert.equal( (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 40, 10)).toString(), '1' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 40, 10)).toString(), - '1' - ); }); }); @@ -2047,10 +1064,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 40, 20)).toString(), '2' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 40, 20)).toString(), - '2' - ); }); }); @@ -2060,10 +1073,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '2', 65, 25)).toString(), '3' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '2', 65, 25)).toString(), - '3' - ); }); }); @@ -2073,10 +1082,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 40, 40)).toString(), '2' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 40, 40)).toString(), - '2' - ); }); }); describe('when getLastRoundIdBeforeElapsedSecs() is invoked with the first round and a waiting time of 60s exactly', () => { @@ -2085,10 +1090,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 50, 40)).toString(), '3' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 50, 40)).toString(), - '3' - ); }); }); describe('when getLastRoundIdBeforeElapsedSecs() is invoked with the first round and a waiting time beyond 60s', () => { @@ -2097,10 +1098,6 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '1', 55, 6000)).toString(), '3' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 50, 40)).toString(), - '3' - ); }); }); describe('when getLastRoundIdBeforeElapsedSecs() is invoked with the third round and a waiting time beyond 60s', () => { @@ -2109,30 +1106,23 @@ contract('Exchange Rates', async accounts => { (await instance.getLastRoundIdBeforeElapsedSecs(sJPY, '3', 180, 9000)).toString(), '3' ); - assert.equal( - (await instance.getLastRoundIdBeforeElapsedSecs(sBNB, '1', 50, 40)).toString(), - '3' - ); }); }); }); }); + describe('effectiveValueAtRound()', () => { - describe('when both the aggregator and regular prices have been give three rates with current timestamps', () => { + describe('when both aggregated prices have been given three rates with current timestamps', () => { beforeEach(async () => { - let timestamp = await currentTime(); - await aggregatorJPY.setLatestAnswer(convertToDecimals(100, 8), timestamp); // round 1 for sJPY - await instance.updateRates([sBNB], [toUnit('1000')], timestamp, { from: oracle }); // round 1 for sBNB + await setupAggregators([sBNB]); + + await updateRates([sJPY, sBNB], [convertToDecimals(100, 8), toUnit('1000')]); await fastForward(120); - timestamp = await currentTime(); - await aggregatorJPY.setLatestAnswer(convertToDecimals(200, 8), timestamp); // round 2 for sJPY - await instance.updateRates([sBNB], [toUnit('2000')], timestamp, { from: oracle }); // round 2 for sBNB + await updateRates([sJPY, sBNB], [convertToDecimals(200, 8), toUnit('2000')]); await fastForward(120); - timestamp = await currentTime(); - await aggregatorJPY.setLatestAnswer(convertToDecimals(300, 8), timestamp); // round 3 for sJPY - await instance.updateRates([sBNB], [toUnit('4000')], timestamp, { from: oracle }); // round 3 for sBNB + await updateRates([sJPY, sBNB], [convertToDecimals(300, 8), toUnit('4000')]); }); it('accepts various changes to src roundId', async () => { assert.bnEqual( @@ -2990,20 +1980,28 @@ contract('Exchange Rates', async accounts => { }); }; + // utility function to setup price aggregators + async function setupAggregators(keys, decimalsArray = []) { + await setupPriceAggregators(instance, owner, keys, decimalsArray); + } + + // utility function update rates for aggregators that are already set up + async function updateRates(keys, rates, timestamp = undefined) { + await updateAggregatorRates(instance, keys, rates, timestamp); + } + describe('Using ExchangeRates', () => { const exchangeRatesContract = 'ExchangeRates'; before(async () => { - initialTime = await currentTime(); - ({ - ExchangeRates: instance, - SystemSettings: systemSettings, - AddressResolver: resolver, - } = await setupAllContracts({ + ({ ExchangeRates: instance, SystemSettings: systemSettings } = await setupAllContracts({ accounts, contracts: [exchangeRatesContract, 'SystemSettings', 'AddressResolver'], })); + // remove the pre-configured aggregator + await instance.removeAggregator(toBytes32('SNX'), { from: owner }); + aggregatorJPY = await MockAggregator.new({ from: owner }); aggregatorXTZ = await MockAggregator.new({ from: owner }); aggregatorFastGasPrice = await MockAggregator.new({ from: owner }); @@ -3018,22 +2016,10 @@ contract('Exchange Rates', async accounts => { addSnapshotBeforeRestoreAfterEach(); - beforeEach(async () => { - timeSent = await currentTime(); - }); - itIncludesCorrectMutativeFunctions(exchangeRatesContract); itIsConstructedCorrectly(exchangeRatesContract); - itUpdatesRates(); - - itSetsOracle(); - - itDeletesRates(); - - itReturnsRates(); - itCalculatesStaleRates(); itCalculatesInvalidRates(); @@ -3053,16 +2039,14 @@ contract('Exchange Rates', async accounts => { const exchangeRatesContract = 'ExchangeRatesWithDexPricing'; before(async () => { - initialTime = await currentTime(); - ({ - ExchangeRates: instance, - SystemSettings: systemSettings, - AddressResolver: resolver, - } = await setupAllContracts({ + ({ ExchangeRates: instance, SystemSettings: systemSettings } = await setupAllContracts({ accounts, contracts: [exchangeRatesContract, 'SystemSettings', 'AddressResolver'], })); + // remove the pre-configured aggregator + await instance.removeAggregator(toBytes32('SNX'), { from: owner }); + aggregatorJPY = await MockAggregator.new({ from: owner }); aggregatorXTZ = await MockAggregator.new({ from: owner }); aggregatorFastGasPrice = await MockAggregator.new({ from: owner }); @@ -3077,22 +2061,10 @@ contract('Exchange Rates', async accounts => { addSnapshotBeforeRestoreAfterEach(); - beforeEach(async () => { - timeSent = await currentTime(); - }); - itIncludesCorrectMutativeFunctions(exchangeRatesContract); itIsConstructedCorrectly(exchangeRatesContract); - itUpdatesRates(); - - itSetsOracle(); - - itDeletesRates(); - - itReturnsRates(); - itCalculatesStaleRates(); itCalculatesInvalidRates(); diff --git a/test/contracts/Exchanger.spec.js b/test/contracts/Exchanger.spec.js index 2ad3043ec2..5ca67e5e57 100644 --- a/test/contracts/Exchanger.spec.js +++ b/test/contracts/Exchanger.spec.js @@ -18,6 +18,8 @@ const { setStatus, convertToAggregatorPrice, updateRatesWithDefaults, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { @@ -58,8 +60,6 @@ contract('Exchanger (spec tests)', async accounts => { sEURContract, sBTCContract, sETHContract, - oracle, - timestamp, exchanger, exchangeState, exchangeFeeRate, @@ -139,7 +139,7 @@ contract('Exchanger (spec tests)', async accounts => { const amountOfSrcExchanged = toUnit('10'); beforeEach(async () => { - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); await sUSDContract.issue(owner, toUnit('100')); await synthetix.exchange(sUSD, toUnit('10'), sETH, { from: owner }); }); @@ -272,9 +272,7 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { await fastForward(10); // base rate of sETH is 100 from shared setup above - await exchangeRates.updateRates([sETH], [toUnit('300')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('300')]); await synthetix.exchange(sUSD, toUnit('1'), sETH, { from: account1 }); }); it('then the synth is suspended', async () => { @@ -287,9 +285,7 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { await fastForward(10); // base rate of sETH is 100 from shared setup above - await exchangeRates.updateRates([sETH], [toUnit('33')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('33')]); await synthetix.exchange(sUSD, toUnit('1'), sETH, { from: account1 }); }); it('then the synth is suspended', async () => { @@ -307,9 +303,7 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { await fastForward(10); // base rate of sETH is 100 from shared setup above - await exchangeRates.updateRates([sETH], [toUnit('300')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('300')]); await synthetix.exchange(sUSD, toUnit('1'), sETH, { from: account1 }); }); it('then the synth is not suspended', async () => { @@ -322,9 +316,7 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { await fastForward(10); // base rate of sETH is 100 from shared setup above - await exchangeRates.updateRates([sETH], [toUnit('33')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('33')]); await synthetix.exchange(sUSD, toUnit('1'), sETH, { from: account1 }); }); it('then the synth is not suspended', async () => { @@ -667,14 +659,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('given the sEUR rate is 2, and sETH is 100, sBTC is 9000', () => { beforeEach(async () => { // set sUSD:sEUR as 2:1, sUSD:sETH at 100:1, sUSD:sBTC at 9000:1 - await exchangeRates.updateRates( - [sEUR, sETH, sBTC], - ['2', '100', '9000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + await updateRates([sEUR, sETH, sBTC], ['2', '100', '9000'].map(toUnit)); }); describe('and the exchange fee rate is 1% for easier human consumption', () => { beforeEach(async () => { @@ -858,11 +843,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when the price doubles for sUSD:sEUR to 4:1', () => { beforeEach(async () => { await fastForward(5); - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['4'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['4'].map(toUnit)); }); it('then settlement reclaimAmount shows a reclaim of half the entire balance of sEUR', async () => { const expected = calculateExpectedSettlementAmount({ @@ -1084,12 +1065,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when the price halves for sUSD:sEUR to 1:1', () => { beforeEach(async () => { await fastForward(5); - - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['1'].map(toUnit)); }); it('then settlement rebateAmount shows a rebate of half the entire balance of sEUR', async () => { const expected = calculateExpectedSettlementAmount({ @@ -1118,12 +1094,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('and then the price increases for sUSD:sEUR to 2:1', () => { beforeEach(async () => { await fastForward(5); - - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['2'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['2'].map(toUnit)); }); describe('when settlement is invoked', () => { describe('when another minute passes', () => { @@ -1332,12 +1303,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when the price returns to sUSD:sEUR to 2:1', () => { beforeEach(async () => { await fastForward(12); - - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['2'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['2'].map(toUnit)); }); it('then settlement reclaimAmount shows 0 reclaim and 0 refund', async () => { const settlement = await exchanger.settlementOwing(account1, sEUR); @@ -1351,11 +1317,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when another minute elapses and the sETH price changes', () => { beforeEach(async () => { await fastForward(60); - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['3'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['3'].map(toUnit)); }); it('then settlement reclaimAmount still shows 0 reclaim and 0 refund as the timeout period ended', async () => { const settlement = await exchanger.settlementOwing(account1, sEUR); @@ -1411,11 +1373,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('when the price doubles for sUSD:sEUR to 4:1', () => { beforeEach(async () => { await fastForward(5); - timestamp = await currentTime(); - - await exchangeRates.updateRates([sEUR], ['4'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sEUR], ['4'].map(toUnit)); }); it('then settlement shows a rebate rebateAmount', async () => { const { reclaimAmount, rebateAmount } = await exchanger.settlementOwing( @@ -1442,16 +1400,7 @@ contract('Exchanger (spec tests)', async accounts => { }); describe('when the price gains for sBTC more than the loss of the sEUR change', () => { beforeEach(async () => { - await fastForward(5); - timestamp = await currentTime(); - await exchangeRates.updateRates( - [sBTC], - ['20000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + await updateRates([sBTC], ['20000'].map(toUnit)); }); it('then the reclaimAmount is whats left when subtracting the rebate', async () => { const { reclaimAmount, rebateAmount } = await exchanger.settlementOwing( @@ -1506,16 +1455,8 @@ contract('Exchanger (spec tests)', async accounts => { let expectedFromSecond; beforeEach(async () => { await fastForward(5); - timestamp = await currentTime(); - - await exchangeRates.updateRates( - [sBTC], - ['10000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + + await updateRates([sBTC], ['10000'].map(toUnit)); expectedFromFirst = calculateExpectedSettlementAmount({ amount: amountOfSrcExchanged, @@ -1927,11 +1868,7 @@ contract('Exchanger (spec tests)', async accounts => { }); describe('when that synth has a fresh rate', () => { beforeEach(async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sAUD], ['0.75'].map(toUnit), timestamp, { - from: oracle, - }); + await updateRates([sAUD], ['0.75'].map(toUnit)); }); describe(`when the user ${type} into that synth`, () => { beforeEach(async () => { @@ -2754,14 +2691,7 @@ contract('Exchanger (spec tests)', async accounts => { const updateRate = ({ target, rate }) => { beforeEach(async () => { await fastForward(10); - await exchangeRates.updateRates( - [target], - [toUnit(rate.toString())], - await currentTime(), - { - from: oracle, - } - ); + await updateRates([target], [toUnit(rate.toString())]); }); }; @@ -2846,13 +2776,10 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { // sETH over deviation and sEUR slight change await fastForward(10); - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sETH, sEUR], - [toUnit(baseRate * 3).toString(), toUnit('1.9')], - await currentTime(), - { - from: oracle, - } + [toUnit(baseRate * 3).toString(), toUnit('1.9')] ); }); describe('and another user exchanges sETH to sEUR', () => { @@ -2875,13 +2802,10 @@ contract('Exchanger (spec tests)', async accounts => { beforeEach(async () => { // sEUR over deviation and sETH slight change await fastForward(10); - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sETH, sEUR], - [toUnit(baseRate * 1.1).toString(), toUnit('10')], - await currentTime(), - { - from: oracle, - } + [toUnit(baseRate * 1.1).toString(), toUnit('10')] ); }); describe('and another user exchanges sEUR to sETH', () => { @@ -3073,21 +2997,12 @@ contract('Exchanger (spec tests)', async accounts => { describe('when a recent price rate is set way outside of the threshold', () => { beforeEach(async () => { await fastForward(10); - await exchangeRates.updateRates([sETH], [toUnit('1000')], await currentTime(), { - from: oracle, - }); + await updateRates([sETH], [toUnit('1000')]); }); describe('and then put back to normal', () => { beforeEach(async () => { await fastForward(10); - await exchangeRates.updateRates( - [sETH], - [baseRate.toString()], - await currentTime(), - { - from: oracle, - } - ); + await updateRates([sETH], [baseRate.toString()]); }); assertSpike({ from: sUSD, @@ -3500,9 +3415,14 @@ contract('Exchanger (spec tests)', async accounts => { }); }; + async function updateRates(keys, rates) { + await updateAggregatorRates(exchangeRates, keys, rates); + } + describe('With L1 configuration (Synthetix, ExchangerWithFeeRecAlternatives, ExchangeRatesWithDexPricing)', () => { before(async () => { const VirtualSynthMastercopy = artifacts.require('VirtualSynthMastercopy'); + const synths = ['sUSD', 'sETH', 'sEUR', 'sAUD', 'sBTC', 'iBTC', 'sTRX']; ({ Exchanger: exchanger, @@ -3524,7 +3444,7 @@ contract('Exchanger (spec tests)', async accounts => { FlexibleStorage: flexibleStorage, } = await setupAllContracts({ accounts, - synths: ['sUSD', 'sETH', 'sEUR', 'sAUD', 'sBTC', 'iBTC', 'sTRX'], + synths: synths, contracts: [ // L1 specific 'Synthetix', @@ -3548,8 +3468,7 @@ contract('Exchanger (spec tests)', async accounts => { }, })); - // Send a price update to guarantee we're not stale. - oracle = account1; + await setupPriceAggregators(exchangeRates, owner, synths.map(toBytes32)); amountIssued = toUnit('1000'); @@ -3561,15 +3480,10 @@ contract('Exchanger (spec tests)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sETH, sBTC, iBTC], - ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; + const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); + await setupPriceAggregators(exchangeRates, owner, keys); + await updateRates(keys, rates); // set a 0.5% exchange fee rate (1/200) exchangeFeeRate = toUnit('0.005'); @@ -3610,6 +3524,7 @@ contract('Exchanger (spec tests)', async accounts => { describe('With L2 configuration (MintableSynthetix, Exchanger, ExchangeRates)', () => { before(async () => { + const synths = ['sUSD', 'sETH', 'sEUR', 'sAUD', 'sBTC', 'iBTC', 'sTRX']; ({ Exchanger: exchanger, Synthetix: synthetix, @@ -3630,7 +3545,7 @@ contract('Exchanger (spec tests)', async accounts => { FlexibleStorage: flexibleStorage, } = await setupAllContracts({ accounts, - synths: ['sUSD', 'sETH', 'sEUR', 'sAUD', 'sBTC', 'iBTC', 'sTRX'], + synths: synths, contracts: [ // L2 specific 'MintableSynthetix', @@ -3650,8 +3565,7 @@ contract('Exchanger (spec tests)', async accounts => { ], })); - // Send a price update to guarantee we're not stale. - oracle = account1; + await setupPriceAggregators(exchangeRates, owner, synths.map(toBytes32)); amountIssued = toUnit('1000'); @@ -3663,15 +3577,10 @@ contract('Exchanger (spec tests)', async accounts => { addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sETH, sBTC, iBTC], - ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + const keys = [sAUD, sEUR, SNX, sETH, sBTC, iBTC]; + const rates = ['0.5', '2', '1', '100', '5000', '5000'].map(toUnit); + await setupPriceAggregators(exchangeRates, owner, keys); + await updateRates(keys, rates); // set a 0.5% exchange fee rate (1/200) exchangeFeeRate = toUnit('0.005'); diff --git a/test/contracts/FeePool.js b/test/contracts/FeePool.js index 3d88d071e8..b05f2c5675 100644 --- a/test/contracts/FeePool.js +++ b/test/contracts/FeePool.js @@ -7,14 +7,7 @@ const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const FeePool = artifacts.require('FeePool'); const FlexibleStorage = artifacts.require('FlexibleStorage'); -const { - currentTime, - fastForward, - toUnit, - toPreciseUnit, - fromUnit, - multiplyDecimal, -} = require('../utils')(); +const { fastForward, toUnit, toPreciseUnit, fromUnit, multiplyDecimal } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, @@ -24,6 +17,8 @@ const { decodedEventEqual, proxyThruTo, setExchangeFeeRateForSynths, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -34,15 +29,11 @@ const { } = require('../..'); contract('FeePool', async accounts => { - const [deployerAccount, owner, oracle, account1, account2] = accounts; + const [deployerAccount, owner, , account1, account2] = accounts; // Updates rates with defaults so they're not stale. const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sAUD, SNX], ['0.5', '0.1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD, SNX], ['0.5', '0.1'].map(toUnit)); await debtCache.takeDebtSnapshot(); }; @@ -118,6 +109,8 @@ contract('FeePool', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sAUD]); + FEE_ADDRESS = await feePool.FEE_ADDRESS(); }); @@ -829,15 +822,10 @@ contract('FeePool', async accounts => { .concat(synths) .filter(key => key !== 'sUSD' && ![].concat(type).includes(key)); - const timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, ratesToUpdate.map(toBytes32), - ratesToUpdate.map(() => toUnit('1')), - timestamp, - { - from: oracle, - } + ratesToUpdate.map(() => toUnit('1')) ); await debtCache.takeDebtSnapshot(); }); @@ -1104,10 +1092,7 @@ contract('FeePool', async accounts => { // Increase the price so we start well and truly within our 20% ratio. const newRate = (await exchangeRates.rateForCurrency(SNX)).add(web3.utils.toBN('1')); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); assert.equal(await feePool.isFeesClaimable(owner), true); @@ -1121,10 +1106,7 @@ contract('FeePool', async accounts => { const newRate = (await exchangeRates.rateForCurrency(SNX)).add( step.mul(web3.utils.toBN('1')) ); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); const issuanceRatio = fromUnit(await feePool.issuanceRatio()); @@ -1146,10 +1128,7 @@ contract('FeePool', async accounts => { // Bump the rate down. const newRate = (await exchangeRates.rateForCurrency(SNX)).sub(step); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); } }); @@ -1181,10 +1160,7 @@ contract('FeePool', async accounts => { const currentRate = await exchangeRates.rateForCurrency(SNX); const newRate = currentRate.sub(multiplyDecimal(currentRate, toUnit('0.15'))); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); // fees available is unaffected but not claimable @@ -1224,10 +1200,7 @@ contract('FeePool', async accounts => { const currentRate = await exchangeRates.rateForCurrency(SNX); const newRate = currentRate.sub(multiplyDecimal(currentRate, toUnit('0.15'))); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); await debtCache.takeDebtSnapshot(); // fees available is unaffected but not claimable @@ -1328,15 +1301,10 @@ contract('FeePool', async accounts => { .concat(synths) .filter(key => key !== 'sUSD' && ![].concat(type).includes(key)); - const timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, ratesToUpdate.map(toBytes32), - ratesToUpdate.map(() => toUnit('1')), - timestamp, - { - from: oracle, - } + ratesToUpdate.map(() => toUnit('1')) ); await debtCache.takeDebtSnapshot(); }); diff --git a/test/contracts/FeePoolState.js b/test/contracts/FeePoolState.js index 19542e204a..e88cc83c16 100644 --- a/test/contracts/FeePoolState.js +++ b/test/contracts/FeePoolState.js @@ -360,14 +360,11 @@ contract('FeePoolState', async accounts => { // TODO checks SynthetixState debt entry is same as stored FeePoolState Entry // it.only('should allow an issuer to issue max synths and track debt issuance in feePool', async function() { // // Send a price update to guarantee we're not depending on values from outside this test. - // const oracle = await exchangeRates.oracle(); - // const timestamp = await currentTime(); - // await exchangeRates.updateRates( + // await updateAggregatorRates( + // exchangeRates, // [sAUD, sEUR, SNX], - // ['0.5', '1.25', '0.1'].map(toUnit), - // timestamp, - // { from: oracle } + // ['0.5', '1.25', '0.1'].map(toUnit) // ); // // Give some SNX to account1 @@ -397,14 +394,11 @@ contract('FeePoolState', async accounts => { // it('should allow an issuer to issue synths many times and track debt issuance in feePool', async function() { // // Send a price update to guarantee we're not depending on values from outside this test. - // const oracle = await exchangeRates.oracle(); - // const timestamp = await currentTime(); - // await exchangeRates.updateRates( + // await updateAggregatorRates( + // exchangeRates, // [sAUD, sEUR, SNX], // ['0.5', '1.25', '0.1'].map(toUnit), - // timestamp, - // { from: oracle } // ); // // Give some SNX to account1 diff --git a/test/contracts/Issuer.js b/test/contracts/Issuer.js index 5b1e034282..fdf8c432d2 100644 --- a/test/contracts/Issuer.js +++ b/test/contracts/Issuer.js @@ -25,6 +25,8 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { @@ -41,7 +43,7 @@ contract('Issuer (via Synthetix)', async accounts => { ); const synthKeys = [sUSD, sAUD, sEUR, sETH, SNX]; - const [, owner, oracle, account1, account2, account3, account6] = accounts; + const [, owner, , account1, account2, account3, account6] = accounts; let synthetix, systemStatus, @@ -56,7 +58,6 @@ contract('Issuer (via Synthetix)', async accounts => { sAUDContract, escrow, rewardEscrowV2, - timestamp, debtCache, issuer, synths, @@ -111,18 +112,17 @@ contract('Issuer (via Synthetix)', async accounts => { 'SynthRedeemer', ], })); + + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sETH, ETH]); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, SNX, sETH], - ['0.5', '1.25', '0.1', '200'].map(toUnit), - timestamp, - { from: oracle } + ['0.5', '1.25', '0.1', '200'].map(toUnit) ); // set a 0.3% default exchange fee rate @@ -314,11 +314,10 @@ contract('Issuer (via Synthetix)', async accounts => { beforeEach(async () => { await fastForward(10); // Send a price update to give the synth rates - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, sEUR, sETH, ETH, SNX], - ['0.5', '1.25', '100', '100', '2'].map(toUnit), - await currentTime(), - { from: oracle } + ['0.5', '1.25', '100', '100', '2'].map(toUnit) ); await debtCache.takeDebtSnapshot(); }); @@ -396,8 +395,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('debtBalance()', () => { it('should not change debt balance % if exchange rates change', async () => { let newAUDRate = toUnit('0.5'); - let timestamp = await currentTime(); - await exchangeRates.updateRates([sAUD], [newAUDRate], timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sAUD], [newAUDRate]); await debtCache.takeDebtSnapshot(); await synthetix.transfer(account1, toUnit('20000'), { @@ -427,9 +425,8 @@ contract('Issuer (via Synthetix)', async accounts => { PRECISE_UNIT ); - timestamp = await currentTime(); newAUDRate = toUnit('1.85'); - await exchangeRates.updateRates([sAUD], [newAUDRate], timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sAUD], [newAUDRate]); await debtCache.takeDebtSnapshot(); totalIssuedSynthsUSD = await synthetix.totalIssuedSynths(sUSD); @@ -669,6 +666,7 @@ contract('Issuer (via Synthetix)', async accounts => { })); await issuer.addSynth(synth.address, { from: owner }); + await setupPriceAggregators(exchangeRates, owner, [currencyKey]); }); it('should be able to query multiple synth addresses', async () => { @@ -719,9 +717,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); describe('when the synth has a rate', () => { beforeEach(async () => { - await exchangeRates.updateRates([currencyKey], [toUnit('2')], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [currencyKey], [toUnit('2')]); }); describe('when another user exchanges into the synth', () => { @@ -739,17 +735,6 @@ contract('Issuer (via Synthetix)', async accounts => { const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); assert.equal(numEntries, '0'); }); - describe('when the rate is also removed', () => { - beforeEach(async () => { - await exchangeRates.deleteRate(currencyKey, { from: oracle }); - }); - it('then settling works as expected', async () => { - await synthetix.settle(currencyKey); - - const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); - assert.equal(numEntries, '0'); - }); - }); }); describe('when the same user exchanges out of the synth', () => { beforeEach(async () => { @@ -773,22 +758,6 @@ contract('Issuer (via Synthetix)', async accounts => { const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); assert.equal(numEntries, '0'); }); - describe('when the rate is also removed', () => { - beforeEach(async () => { - await exchangeRates.deleteRate(currencyKey, { from: oracle }); - }); - it('then settling works as expected', async () => { - await synthetix.settle(currencyKey); - - const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); - assert.equal(numEntries, '0'); - }); - it('then settling from the original currency works too', async () => { - await synthetix.settle(currencyKey); - const { numEntries } = await exchanger.settlementOwing(owner, currencyKey); - assert.equal(numEntries, '0'); - }); - }); }); }); }); @@ -1040,15 +1009,10 @@ contract('Issuer (via Synthetix)', async accounts => { .concat(synths) .filter(key => key !== 'sUSD' && ![].concat(type).includes(key)); - const timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, ratesToUpdate.map(toBytes32), - ratesToUpdate.map(() => toUnit('1')), - timestamp, - { - from: oracle, - } + ratesToUpdate.map(() => toUnit('1')) ); await debtCache.takeDebtSnapshot(); }); @@ -1272,15 +1236,10 @@ contract('Issuer (via Synthetix)', async accounts => { .concat(synths) .filter(key => key !== 'sUSD' && ![].concat(type).includes(key)); - const timestamp = await currentTime(); - - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, ratesToUpdate.map(toBytes32), - ratesToUpdate.map(rate => toUnit(rate === 'SNX' ? '0.1' : '1')), - timestamp, - { - from: oracle, - } + ratesToUpdate.map(rate => toUnit(rate === 'SNX' ? '0.1' : '1')) ); await debtCache.takeDebtSnapshot(); }); @@ -1606,9 +1565,7 @@ contract('Issuer (via Synthetix)', async accounts => { from: owner, }); // Set SNX price to 1 - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['1'].map(toUnit)); await debtCache.takeDebtSnapshot(); // Issue await synthetix.issueMaxSynths({ from: account1 }); @@ -1621,9 +1578,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the SNX price drops 50%', () => { let maxIssuableSynths; beforeEach(async () => { - await exchangeRates.updateRates([SNX], ['.5'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['.5'].map(toUnit)); await debtCache.takeDebtSnapshot(); maxIssuableSynths = await synthetix.maxIssuableSynths(account1); assert.equal(await feePool.isFeesClaimable(account1), false); @@ -1645,9 +1600,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the SNX price drops 10%', () => { let maxIssuableSynths; beforeEach(async () => { - await exchangeRates.updateRates([SNX], ['.9'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['.9'].map(toUnit)); await debtCache.takeDebtSnapshot(); maxIssuableSynths = await synthetix.maxIssuableSynths(account1); }); @@ -1668,9 +1621,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the SNX price drops 90%', () => { let maxIssuableSynths; beforeEach(async () => { - await exchangeRates.updateRates([SNX], ['.1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['.1'].map(toUnit)); await debtCache.takeDebtSnapshot(); maxIssuableSynths = await synthetix.maxIssuableSynths(account1); }); @@ -1691,9 +1642,7 @@ contract('Issuer (via Synthetix)', async accounts => { describe('when the SNX price increases 100%', () => { let maxIssuableSynths; beforeEach(async () => { - await exchangeRates.updateRates([SNX], ['2'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['2'].map(toUnit)); await debtCache.takeDebtSnapshot(); maxIssuableSynths = await synthetix.maxIssuableSynths(account1); }); @@ -1780,9 +1729,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); describe('and the sEUR price decreases by 20% to 1', () => { beforeEach(async () => { - await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sEUR], ['1'].map(toUnit)); await debtCache.takeDebtSnapshot(); }); describe('and 60s elapses', () => { @@ -2105,8 +2052,7 @@ contract('Issuer (via Synthetix)', async accounts => { it("should prevent more issuance if the user's collaterisation changes to be insufficient", async () => { // Set sEUR for purposes of this test - const timestamp1 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('0.75')], timestamp1, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sEUR], [toUnit('0.75')]); await debtCache.takeDebtSnapshot(); const issuedSynthetixs = web3.utils.toBN('200000'); @@ -2125,8 +2071,7 @@ contract('Issuer (via Synthetix)', async accounts => { await synthetix.exchange(sUSD, issuedSynths, sEUR, { from: account1 }); // Increase the value of sEUR relative to synthetix - const timestamp2 = await currentTime(); - await exchangeRates.updateRates([sEUR], [toUnit('1.10')], timestamp2, { from: oracle }); + await updateAggregatorRates(exchangeRates, [sEUR], [toUnit('1.1')]); await debtCache.takeDebtSnapshot(); await assert.revert( @@ -2427,7 +2372,7 @@ contract('Issuer (via Synthetix)', async accounts => { await synthetix.transfer(authoriser, toUnit('20000'), { from: owner, }); - await exchangeRates.updateRates([SNX], ['1'].map(toUnit), timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [SNX], [toUnit('1')]); await debtCache.takeDebtSnapshot(); }); describe('when not approved it should revert on', async () => { @@ -2514,9 +2459,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); it('and calling burnSynthsToTargetOnBehalf() succeeds', async () => { // need the user to be undercollaterized for this to succeed - await exchangeRates.updateRates([SNX], ['0.001'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [toUnit('0.001')]); await debtCache.takeDebtSnapshot(); await synthetix.burnSynthsToTargetOnBehalf(authoriser, { from: delegate }); }); @@ -2566,7 +2509,7 @@ contract('Issuer (via Synthetix)', async accounts => { }); it('should approveBurnOnBehalf and burnSynthsToTarget', async () => { await synthetix.issueMaxSynths({ from: authoriser }); - await exchangeRates.updateRates([SNX], ['0.01'].map(toUnit), timestamp, { from: oracle }); + await updateAggregatorRates(exchangeRates, [SNX], [toUnit('0.01')]); await debtCache.takeDebtSnapshot(); await delegateApprovals.approveBurnOnBehalf(delegate, { from: authoriser }); diff --git a/test/contracts/Liquidations.js b/test/contracts/Liquidations.js index 2f4bef7079..1255160b68 100644 --- a/test/contracts/Liquidations.js +++ b/test/contracts/Liquidations.js @@ -12,6 +12,7 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + updateAggregatorRates, } = require('./helpers'); const { @@ -24,7 +25,7 @@ const FlexibleStorage = artifacts.require('FlexibleStorage'); contract('Liquidations', accounts => { const [sUSD, SNX] = ['sUSD', 'SNX'].map(toBytes32); - const [deployerAccount, owner, oracle, account1, alice, bob, carol, david] = accounts; + const [deployerAccount, owner, , account1, alice, bob, carol, david] = accounts; const week = 3600 * 24 * 7; const sUSD100 = toUnit('100'); @@ -38,8 +39,7 @@ contract('Liquidations', accounts => { systemStatus, feePoolState, debtCache, - issuer, - timestamp; + issuer; // run this once before all tests to prepare our environment, snapshots on beforeEach will take // care of resetting to this state @@ -86,16 +86,11 @@ contract('Liquidations', accounts => { }; const updateRatesWithDefaults = async () => { - timestamp = await currentTime(); - // SNX is 6 dolla await updateSNXPrice('6'); }; const updateSNXPrice = async rate => { - timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [rate].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [rate].map(toUnit)); await debtCache.takeDebtSnapshot(); }; diff --git a/test/contracts/MultiCollateralSynth.js b/test/contracts/MultiCollateralSynth.js index 60e92c1c7c..165a13186a 100644 --- a/test/contracts/MultiCollateralSynth.js +++ b/test/contracts/MultiCollateralSynth.js @@ -6,8 +6,13 @@ const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); let MultiCollateralSynth; -const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions } = require('./helpers'); -const { toUnit, currentTime, fastForward } = require('../utils')(); +const { + onlyGivenAddressCanInvoke, + ensureOnlyExpectedMutativeFunctions, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); +const { toUnit, fastForward } = require('../utils')(); const { toBytes32, constants: { ZERO_ADDRESS }, @@ -16,9 +21,10 @@ const { const { setupAllContracts } = require('./setup'); contract('MultiCollateralSynth', accounts => { - const [deployerAccount, owner, oracle, , account1] = accounts; + const [deployerAccount, owner, , , account1] = accounts; const sETH = toBytes32('sETH'); + const sBTC = toBytes32('sBTC'); let issuer, resolver, @@ -43,20 +49,6 @@ contract('MultiCollateralSynth', accounts => { }); }; - const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); - }; - before(async () => { MultiCollateralSynth = artifacts.require('MultiCollateralSynth'); }); @@ -91,6 +83,9 @@ contract('MultiCollateralSynth', accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sETH, sBTC]); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); + await managerState.setAssociatedContract(manager.address, { from: owner }); await manager.rebuildCache(); @@ -99,8 +94,6 @@ contract('MultiCollateralSynth', accounts => { await manager.addCollaterals([ceth.address], { from: owner }); - await updateRatesWithDefaults(); - await issuesUSDToAccount(toUnit(1000), owner); await debtCache.takeDebtSnapshot(); }); @@ -199,11 +192,9 @@ contract('MultiCollateralSynth', accounts => { describe('when multiCollateral is set to the owner', () => { beforeEach(async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([toBytes32('sXYZ')], [toUnit(5)], timestamp, { - from: oracle, - }); + const sXYZ = toBytes32('sXYZ'); + await setupPriceAggregators(exchangeRates, owner, [sXYZ]); + await updateAggregatorRates(exchangeRates, [sXYZ], [toUnit(5)]); }); describe('when multiCollateral tries to issue', () => { it('then it can issue new synths', async () => { diff --git a/test/contracts/NativeEtherWrapper.js b/test/contracts/NativeEtherWrapper.js index 920891fcb2..c305cb4327 100644 --- a/test/contracts/NativeEtherWrapper.js +++ b/test/contracts/NativeEtherWrapper.js @@ -4,13 +4,15 @@ const { contract, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, toUnit } = require('../utils')(); +const { toUnit } = require('../utils')(); const { GAS_PRICE } = require('../../hardhat.config'); const { ensureOnlyExpectedMutativeFunctions, getDecodedLogs, decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -22,7 +24,7 @@ contract('NativeEtherWrapper', async accounts => { const synths = ['sUSD', 'sETH', 'ETH', 'SNX']; const [sETH, ETH] = ['sETH', 'ETH'].map(toBytes32); - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let systemSettings, exchangeRates, @@ -30,8 +32,7 @@ contract('NativeEtherWrapper', async accounts => { sETHSynth, etherWrapper, nativeEtherWrapper, - weth, - timestamp; + weth; before(async () => { ({ @@ -63,12 +64,10 @@ contract('NativeEtherWrapper', async accounts => { ], })); - timestamp = await currentTime(); + await setupPriceAggregators(exchangeRates, owner, [sETH, ETH]); // Depot requires ETH rates - await exchangeRates.updateRates([sETH, ETH], ['1500', '1500'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, ETH], ['1500', '1500'].map(toUnit)); }); addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/PurgeableSynth.js b/test/contracts/PurgeableSynth.js index 812c1fcf0c..a66cb71a00 100644 --- a/test/contracts/PurgeableSynth.js +++ b/test/contracts/PurgeableSynth.js @@ -8,7 +8,7 @@ const TokenState = artifacts.require('TokenState'); const Proxy = artifacts.require('Proxy'); const PurgeableSynth = artifacts.require('PurgeableSynth'); -const { currentTime, fastForward, toUnit } = require('../utils')(); +const { fastForward, toUnit } = require('../utils')(); const { toBytes32, constants: { ZERO_ADDRESS }, @@ -20,6 +20,8 @@ const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -27,7 +29,7 @@ const { setupAllContracts } = require('./setup'); contract('PurgeableSynth', accounts => { const [sUSD, SNX, sAUD, iETH] = ['sUSD', 'SNX', 'sAUD', 'iETH'].map(toBytes32); const synthKeys = [sUSD, sAUD, iETH]; - const [deployerAccount, owner, oracle, , account1, account2] = accounts; + const [deployerAccount, owner, , , account1, account2] = accounts; let exchangeRates, exchanger, @@ -36,7 +38,6 @@ contract('PurgeableSynth', accounts => { sAUDContract, iETHContract, systemStatus, - timestamp, addressResolver, debtCache, issuer; @@ -71,7 +72,7 @@ contract('PurgeableSynth', accounts => { ], })); - timestamp = await currentTime(); + await setupPriceAggregators(exchangeRates, owner, [sAUD, iETH]); }); beforeEach(async () => { @@ -155,13 +156,10 @@ contract('PurgeableSynth', accounts => { describe("when there's a price for the purgeable synth", () => { beforeEach(async () => { - await exchangeRates.updateRates( + await updateAggregatorRates( + exchangeRates, [sAUD, SNX, iETH], - ['0.5', '1', '170'].map(toUnit), - timestamp, - { - from: oracle, - } + ['0.5', '1', '170'].map(toUnit) ); await debtCache.takeDebtSnapshot(); }); @@ -207,9 +205,7 @@ contract('PurgeableSynth', accounts => { }); describe('when rates are received', () => { beforeEach(async () => { - await exchangeRates.updateRates([iETH], ['170'].map(toUnit), await currentTime(), { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [iETH], ['170'].map(toUnit)); await debtCache.takeDebtSnapshot(); }); it('then purge() still works as expected', async () => { @@ -322,9 +318,7 @@ contract('PurgeableSynth', accounts => { describe('Replacing an existing Synth with a Purgeable one to purge and remove it', () => { describe('when sAUD has a price', () => { beforeEach(async () => { - await exchangeRates.updateRates([sAUD], ['0.776845993'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sAUD], ['0.776845993'].map(toUnit)); await debtCache.takeDebtSnapshot(); }); describe('when a user holds some sAUD', () => { diff --git a/test/contracts/RewardsIntegrationTests.js b/test/contracts/RewardsIntegrationTests.js index 64415c28dd..f50af7f70c 100644 --- a/test/contracts/RewardsIntegrationTests.js +++ b/test/contracts/RewardsIntegrationTests.js @@ -6,9 +6,14 @@ const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { toBytes32 } = require('../..'); -const { currentTime, fastForward, toUnit, toPreciseUnit, multiplyDecimal } = require('../utils')(); +const { fastForward, toUnit, toPreciseUnit, multiplyDecimal } = require('../utils')(); -const { setExchangeFeeRateForSynths } = require('./helpers'); +const { + setExchangeFeeRateForSynths, + setupPriceAggregators, + updateRatesWithDefaults, + updateAggregatorRates, +} = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -60,22 +65,6 @@ contract('Rewards Integration Tests', accounts => { const synthKeys = [sUSD, sAUD, sEUR, sBTC, iBTC, sETH, ETH]; - // Updates rates with defaults so they're not stale. - const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates( - [sAUD, sEUR, SNX, sBTC, iBTC, sETH, ETH], - ['0.5', '1.25', '0.1', '5000', '4000', '172', '172'].map(toUnit), - timestamp, - { - from: oracle, - } - ); - - await debtCache.takeDebtSnapshot(); - }; - const fastForwardAndCloseFeePeriod = async () => { const feePeriodDuration = await feePool.feePeriodDuration(); // Note: add on a small addition of 10 seconds - this seems to have @@ -87,12 +76,12 @@ contract('Rewards Integration Tests', accounts => { // Fast forward another day after feePeriod closed before minting await fastForward(DAY + 10); - await updateRatesWithDefaults(); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); }; const fastForwardAndUpdateRates = async seconds => { await fastForward(seconds); - await updateRatesWithDefaults(); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); }; const exchangeFeeRate = toUnit('0.003'); // 30 bips @@ -130,7 +119,7 @@ contract('Rewards Integration Tests', accounts => { // const YEAR = 31556926; // ACCOUNTS - const [deployerAccount, owner, oracle, feeAuthority, account1, account2, account3] = accounts; + const [deployerAccount, owner, , feeAuthority, account1, account2, account3] = accounts; // VARIABLES let feePool, @@ -181,6 +170,8 @@ contract('Rewards Integration Tests', accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sBTC, iBTC, sETH, ETH]); + MINTER_SNX_REWARD = await supplySchedule.minterReward(); await setExchangeFeeRateForSynths({ @@ -627,10 +618,7 @@ contract('Rewards Integration Tests', accounts => { ); // Increase sBTC price by 100% - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], ['10000'].map(toUnit)); await debtCache.takeDebtSnapshot(); // Account 3 (enters the system and) mints 10K sUSD (minus half of an exchange fee - to balance the fact @@ -928,10 +916,7 @@ contract('Rewards Integration Tests', accounts => { const currentRate = await exchangeRates.rateForCurrency(SNX); const newRate = currentRate.sub(multiplyDecimal(currentRate, toUnit('0.009'))); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); // we will be able to claim fees assert.equal(await feePool.isFeesClaimable(account1), true); @@ -953,11 +938,7 @@ contract('Rewards Integration Tests', accounts => { it('should block user from claiming fees and rewards when users claim rewards >10% threshold collateralisation ratio', async () => { // But if the price of SNX decreases a lot... const newRate = (await exchangeRates.rateForCurrency(SNX)).sub(toUnit('0.09')); - const timestamp = await currentTime(); - await exchangeRates.updateRates([SNX], [newRate], timestamp, { - from: oracle, - }); - + await updateAggregatorRates(exchangeRates, [SNX], [newRate]); // we will fall into the >100% bracket assert.equal(await feePool.isFeesClaimable(account1), false); diff --git a/test/contracts/ShortingRewards.js b/test/contracts/ShortingRewards.js index 4c6de47f63..4915282112 100644 --- a/test/contracts/ShortingRewards.js +++ b/test/contracts/ShortingRewards.js @@ -5,7 +5,12 @@ const { toBytes32, constants: { ZERO_ADDRESS }, } = require('../..'); -const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions } = require('./helpers'); +const { + onlyGivenAddressCanInvoke, + ensureOnlyExpectedMutativeFunctions, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { setupAllContracts, setupContract } = require('./setup'); const { currentTime, toUnit, fastForward } = require('../utils')(); @@ -17,7 +22,7 @@ contract('ShortingRewards', accounts => { const [ deployerAccount, owner, - oracle, + , authority, rewardEscrowAddress, account1, @@ -27,7 +32,9 @@ contract('ShortingRewards', accounts => { const sUSD = toBytes32('sUSD'); const sETH = toBytes32('sETH'); + const iETH = toBytes32('iETH'); const sBTC = toBytes32('sBTC'); + const iBTC = toBytes32('iBTC'); // Synthetix is the rewardsToken let rewardsToken, @@ -57,33 +64,11 @@ contract('ShortingRewards', accounts => { return event.args.id; }; - const updateRatesWithDefaults = async () => { - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH], ['100'].map(toUnit), timestamp, { - from: oracle, - }); - - const sBTC = toBytes32('sBTC'); - - await exchangeRates.updateRates([sBTC], ['10000'].map(toUnit), timestamp, { - from: oracle, - }); - }; - const setRewardsTokenExchangeRate = async ({ rateStaleDays } = { rateStaleDays: 7 }) => { const rewardsTokenIdentifier = await rewardsToken.symbol(); await systemSettings.setRateStalePeriod(DAY * rateStaleDays, { from: owner }); - const updatedTime = await currentTime(); - await exchangeRates.updateRates( - [toBytes32(rewardsTokenIdentifier)], - [toUnit('2')], - updatedTime, - { - from: oracle, - } - ); + await updateAggregatorRates(exchangeRates, [toBytes32(rewardsTokenIdentifier)], [toUnit('2')]); assert.equal(await exchangeRates.rateIsStale(toBytes32(rewardsTokenIdentifier)), false); }; @@ -150,6 +135,8 @@ contract('ShortingRewards', accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sBTC, iBTC, sETH, iETH]); + managerState = await CollateralManagerState.new(owner, ZERO_ADDRESS, { from: deployerAccount }); const maxDebt = toUnit(10000000); @@ -232,7 +219,7 @@ contract('ShortingRewards', accounts => { }); beforeEach(async () => { - await updateRatesWithDefaults(); + await updateAggregatorRates(exchangeRates, [sETH, sBTC], [100, 10000].map(toUnit)); await issuesUSDToAccount(toUnit(100000), owner); await issuesBTCtoAccount(toUnit(10), owner); @@ -443,10 +430,7 @@ contract('ShortingRewards', accounts => { await fastForward(DAY); // Make the short so underwater it must get closed. - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['20000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], ['20000'].map(toUnit)); // close the loan via liquidation await issuesBTCtoAccount(toUnit(1), account2); @@ -465,10 +449,7 @@ contract('ShortingRewards', accounts => { await fastForward(DAY); // Make the short so underwater it must get closed. - const timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC], ['20000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sBTC], ['20000'].map(toUnit)); // close the loan via liquidation await issuesBTCtoAccount(toUnit(1), account2); diff --git a/test/contracts/StakingRewards.js b/test/contracts/StakingRewards.js index 59a94833ef..ae19180d18 100644 --- a/test/contracts/StakingRewards.js +++ b/test/contracts/StakingRewards.js @@ -2,7 +2,12 @@ const { contract } = require('hardhat'); const { toBN } = require('web3-utils'); const { toBytes32 } = require('../..'); -const { onlyGivenAddressCanInvoke, ensureOnlyExpectedMutativeFunctions } = require('./helpers'); +const { + onlyGivenAddressCanInvoke, + ensureOnlyExpectedMutativeFunctions, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { mockToken, setupAllContracts, setupContract } = require('./setup'); const { currentTime, toUnit, fastForward } = require('../utils')(); @@ -11,7 +16,7 @@ contract('StakingRewards', accounts => { const [ , owner, - oracle, + , authority, rewardEscrowAddress, stakingAccount1, @@ -33,18 +38,12 @@ contract('StakingRewards', accounts => { const setRewardsTokenExchangeRate = async ({ rateStaleDays } = { rateStaleDays: 7 }) => { const rewardsTokenIdentifier = await rewardsToken.symbol(); + const tokenKey = toBytes32(rewardsTokenIdentifier); await systemSettings.setRateStalePeriod(DAY * rateStaleDays, { from: owner }); - const updatedTime = await currentTime(); - await exchangeRates.updateRates( - [toBytes32(rewardsTokenIdentifier)], - [toUnit('2')], - updatedTime, - { - from: oracle, - } - ); - assert.equal(await exchangeRates.rateIsStale(toBytes32(rewardsTokenIdentifier)), false); + await setupPriceAggregators(exchangeRates, owner, [tokenKey]); + await updateAggregatorRates(exchangeRates, [tokenKey], [toUnit('2')]); + assert.equal(await exchangeRates.rateIsStale(tokenKey), false); }; addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/Synth.js b/test/contracts/Synth.js index a0251a261e..73a95273fd 100644 --- a/test/contracts/Synth.js +++ b/test/contracts/Synth.js @@ -9,12 +9,14 @@ const Synth = artifacts.require('Synth'); const { setupAllContracts } = require('./setup'); -const { currentTime, toUnit, bytesToString } = require('../utils')(); +const { toUnit, bytesToString } = require('../utils')(); const { issueSynthsToUser, ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke, setStatus, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { @@ -25,7 +27,7 @@ const { contract('Synth', async accounts => { const [sUSD, SNX, sEUR] = ['sUSD', 'SNX', 'sEUR'].map(toBytes32); - const [deployerAccount, owner, oracle, , account1, account2] = accounts; + const [deployerAccount, owner, , , account1, account2] = accounts; let feePool, FEE_ADDRESS, @@ -71,18 +73,16 @@ contract('Synth', async accounts => { ], })); + await setupPriceAggregators(exchangeRates, owner, [sEUR]); + FEE_ADDRESS = await feePool.FEE_ADDRESS(); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - const timestamp = await currentTime(); - // Send a price update to guarantee we're not stale. - await exchangeRates.updateRates([SNX], ['0.1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [SNX], ['0.1'].map(toUnit)); await debtCache.takeDebtSnapshot(); // set default issuanceRatio to 0.2 @@ -733,12 +733,8 @@ contract('Synth', async accounts => { contracts: [{ contract: 'Synth', properties: { currencyKey: sEUR } }], })); - const timestamp = await currentTime(); - // Send a price update to guarantee we're not stale. - await exchangeRates.updateRates([sEUR], ['1'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sEUR], ['1'].map(toUnit)); await debtCache.takeDebtSnapshot(); }); diff --git a/test/contracts/SynthUtil.js b/test/contracts/SynthUtil.js index d2d7cc1ad7..9abd6f8360 100644 --- a/test/contracts/SynthUtil.js +++ b/test/contracts/SynthUtil.js @@ -3,16 +3,20 @@ const { contract } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); const { toBytes32 } = require('../..'); -const { toUnit, currentTime } = require('../utils')(); -const { setExchangeFeeRateForSynths } = require('./helpers'); +const { toUnit } = require('../utils')(); +const { + setExchangeFeeRateForSynths, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { setupAllContracts } = require('./setup'); contract('SynthUtil', accounts => { - const [, ownerAccount, oracle, account2] = accounts; - let synthUtil, sUSDContract, synthetix, exchangeRates, timestamp, systemSettings, debtCache; + const [, ownerAccount, , account2] = accounts; + let synthUtil, sUSDContract, synthetix, exchangeRates, systemSettings, debtCache; - const [sUSD, sBTC, iBTC] = ['sUSD', 'sBTC', 'iBTC'].map(toBytes32); + const [sUSD, sBTC, iBTC, SNX] = ['sUSD', 'sBTC', 'iBTC', 'SNX'].map(toBytes32); const synthKeys = [sUSD, sBTC, iBTC]; const synthPrices = [toUnit('1'), toUnit('5000'), toUnit('5000')]; @@ -42,15 +46,18 @@ contract('SynthUtil', accounts => { 'RewardEscrowV2', // required for issuer._collateral to read collateral ], })); + + await setupPriceAggregators(exchangeRates, ownerAccount, [sBTC, iBTC]); }); addSnapshotBeforeRestoreAfterEach(); beforeEach(async () => { - timestamp = await currentTime(); - await exchangeRates.updateRates([sBTC, iBTC], ['5000', '5000'].map(toUnit), timestamp, { - from: oracle, - }); + await updateAggregatorRates( + exchangeRates, + [sBTC, iBTC, SNX], + ['5000', '5000', '0.2'].map(toUnit) + ); await debtCache.takeDebtSnapshot(); // set a 0% default exchange fee rate for test purpose diff --git a/test/contracts/Synthetix.js b/test/contracts/Synthetix.js index a7a5159c6d..a7de48399f 100644 --- a/test/contracts/Synthetix.js +++ b/test/contracts/Synthetix.js @@ -15,6 +15,7 @@ const { fastForwardTo, toUnit, fromUnit } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, updateRatesWithDefaults, + setupPriceAggregators, setStatus, } = require('./helpers'); @@ -34,7 +35,6 @@ contract('Synthetix', async accounts => { supplySchedule, rewardEscrow, rewardEscrowV2, - oracle, addressResolver, systemStatus, sUSDContract, @@ -72,8 +72,7 @@ contract('Synthetix', async accounts => { ], })); - // Send a price update to guarantee we're not stale. - oracle = account1; + await setupPriceAggregators(exchangeRates, owner, [sAUD, sEUR, sETH]); }); addSnapshotBeforeRestoreAfterEach(); @@ -187,7 +186,7 @@ contract('Synthetix', async accounts => { // ensure mint() can succeed by default const week234 = INFLATION_START_DATE + WEEK * 234; await fastForwardTo(new Date(week234 * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); }); ['System', 'Issuance'].forEach(section => { describe(`when ${section} is suspended`, () => { @@ -212,7 +211,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to end of inflation supply decay at week 234 const week234 = INFLATION_START_DATE + WEEK * 234; await fastForwardTo(new Date(week234 * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -248,7 +247,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 3 in of the inflationary supply const weekThree = INFLATION_START_DATE + WEEK * 2 + DAY; await fastForwardTo(new Date(weekThree * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -285,7 +284,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 2 in Year 3 schedule starting at UNIX 1583971200+ const weekThirtyNine = INFLATION_START_DATE + WEEK * 39 + DAY; await fastForwardTo(new Date(weekThirtyNine * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingTotalSupply = await synthetix.totalSupply(); const currentRewardEscrowBalance = await synthetix.balanceOf(rewardEscrow.address); @@ -312,7 +311,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to week 236 const september142023 = INFLATION_START_DATE + 236 * WEEK + DAY; await fastForwardTo(new Date(september142023 * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingTotalSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -334,7 +333,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to week 236 const week573 = INFLATION_START_DATE + 572 * WEEK + DAY; await fastForwardTo(new Date(week573 * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingTotalSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -356,7 +355,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 3 in Year 2 schedule starting at UNIX 1553040000+ const weekThree = INFLATION_START_DATE + 2 * WEEK + 1 * DAY; await fastForwardTo(new Date(weekThree * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); let existingTotalSupply = await synthetix.totalSupply(); let mintableSupply = await supplySchedule.mintableSupply(); @@ -370,7 +369,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 4 const weekFour = weekThree + 1 * WEEK + 1 * DAY; await fastForwardTo(new Date(weekFour * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); existingTotalSupply = await synthetix.totalSupply(); mintableSupply = await supplySchedule.mintableSupply(); @@ -386,7 +385,7 @@ contract('Synthetix', async accounts => { // fast forward EVM to Week 3 of inflation const weekThree = INFLATION_START_DATE + 2 * WEEK + DAY; await fastForwardTo(new Date(weekThree * 1000)); - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); const existingTotalSupply = await synthetix.totalSupply(); const mintableSupply = await supplySchedule.mintableSupply(); @@ -444,7 +443,7 @@ contract('Synthetix', async accounts => { contractExample = await MockThirdPartyExchangeContract.new(addressResolver.address); // ensure rates are set - await updateRatesWithDefaults({ exchangeRates, oracle, debtCache }); + await updateRatesWithDefaults({ exchangeRates, owner, debtCache }); // issue sUSD from the owner await synthetix.issueSynths(amountOfsUSD, { from: owner }); diff --git a/test/contracts/TradingRewards.spec.js b/test/contracts/TradingRewards.spec.js index cbb4b3aa68..97fc403a4c 100644 --- a/test/contracts/TradingRewards.spec.js +++ b/test/contracts/TradingRewards.spec.js @@ -2,8 +2,14 @@ const { contract, web3 } = require('hardhat'); const { toBN } = web3.utils; const { assert, addSnapshotBeforeRestoreAfter } = require('./common'); const { setupAllContracts } = require('./setup'); -const { currentTime, toUnit, multiplyDecimal } = require('../utils')(); -const { setExchangeFeeRateForSynths, getDecodedLogs, decodedEventEqual } = require('./helpers'); +const { toUnit, multiplyDecimal } = require('../utils')(); +const { + setExchangeFeeRateForSynths, + getDecodedLogs, + decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, +} = require('./helpers'); const { toBytes32 } = require('../..'); /* @@ -15,9 +21,9 @@ const { toBytes32 } = require('../..'); contract('TradingRewards', accounts => { const [, owner, account1] = accounts; - const synths = ['sUSD', 'sETH', 'sBTC']; + const synths = ['sUSD', 'sETH', 'sBTC', 'SNX']; const synthKeys = synths.map(toBytes32); - const [sUSD, sETH, sBTC] = synthKeys; + const [sUSD, sETH, sBTC, SNX] = synthKeys; let synthetix, exchanger, exchangeRates, rewards, resolver, systemSettings; let sUSDContract, sETHContract, sBTCContract; @@ -31,6 +37,7 @@ contract('TradingRewards', accounts => { const rates = { [sETH]: toUnit('100'), [sBTC]: toUnit('12000'), + [SNX]: toUnit('0.2'), }; let feesPaidUSD; @@ -91,6 +98,8 @@ contract('TradingRewards', accounts => { 'CollateralManager', ], })); + + await setupPriceAggregators(exchangeRates, owner, [sETH, sBTC]); }); before('BRRRRRR', async () => { @@ -100,12 +109,7 @@ contract('TradingRewards', accounts => { }); before('set exchange rates', async () => { - const oracle = account1; - const timestamp = await currentTime(); - - await exchangeRates.updateRates([sETH, sBTC], Object.values(rates), timestamp, { - from: oracle, - }); + await updateAggregatorRates(exchangeRates, [sETH, sBTC, SNX], Object.values(rates)); await setExchangeFeeRateForSynths({ owner, diff --git a/test/contracts/Wrapper.js b/test/contracts/Wrapper.js index 10ca44eb9d..4247835683 100644 --- a/test/contracts/Wrapper.js +++ b/test/contracts/Wrapper.js @@ -4,12 +4,14 @@ const { contract, artifacts, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, toUnit } = require('../utils')(); +const { toUnit } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, getDecodedLogs, decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -23,7 +25,7 @@ contract('Wrapper', async accounts => { const ONE = toBN('1'); - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let systemSettings, feePool, @@ -35,8 +37,7 @@ contract('Wrapper', async accounts => { sETHSynth, wrapperFactory, etherWrapper, - weth, - timestamp; + weth; const calculateETHToUSD = async feesInETH => { // Ask the Depot how many sUSD I will get for this ETH @@ -112,12 +113,8 @@ contract('Wrapper', async accounts => { from: owner, }); - timestamp = await currentTime(); - - // Depot requires ETH rates - await exchangeRates.updateRates([sETH, ETH], ['1500', '1500'].map(toUnit), timestamp, { - from: oracle, - }); + await setupPriceAggregators(exchangeRates, owner, [sETH, ETH]); + await updateAggregatorRates(exchangeRates, [sETH, ETH], ['1500', '1500'].map(toUnit)); }); addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/WrapperFactory.js b/test/contracts/WrapperFactory.js index 5fd83537bc..8c0552ebee 100644 --- a/test/contracts/WrapperFactory.js +++ b/test/contracts/WrapperFactory.js @@ -4,13 +4,15 @@ const { contract, artifacts, web3 } = require('hardhat'); const { assert, addSnapshotBeforeRestoreAfterEach } = require('./common'); -const { currentTime, toUnit } = require('../utils')(); +const { toUnit } = require('../utils')(); const { ensureOnlyExpectedMutativeFunctions, onlyGivenAddressCanInvoke, getDecodedLogs, decodedEventEqual, + setupPriceAggregators, + updateAggregatorRates, } = require('./helpers'); const { setupAllContracts } = require('./setup'); @@ -22,7 +24,7 @@ contract('WrapperFactory', async accounts => { const synths = ['sUSD', 'sETH', 'ETH', 'SNX']; const [sETH, ETH] = ['sETH', 'ETH'].map(toBytes32); - const [, owner, oracle, , account1] = accounts; + const [, owner, , , account1] = accounts; let addressResolver, flexibleStorage, @@ -32,8 +34,7 @@ contract('WrapperFactory', async accounts => { FEE_ADDRESS, sUSDSynth, wrapperFactory, - weth, - timestamp; + weth; before(async () => { ({ @@ -66,12 +67,10 @@ contract('WrapperFactory', async accounts => { })); FEE_ADDRESS = await feePool.FEE_ADDRESS(); - timestamp = await currentTime(); // Depot requires ETH rates - await exchangeRates.updateRates([sETH, ETH], ['1500', '1500'].map(toUnit), timestamp, { - from: oracle, - }); + await setupPriceAggregators(exchangeRates, owner, [sETH, ETH]); + await updateAggregatorRates(exchangeRates, [sETH, ETH], ['1500', '1500'].map(toUnit)); }); addSnapshotBeforeRestoreAfterEach(); diff --git a/test/contracts/helpers.js b/test/contracts/helpers.js index b5e9c7d0da..37a83e09de 100644 --- a/test/contracts/helpers.js +++ b/test/contracts/helpers.js @@ -11,6 +11,48 @@ const { constants: { ZERO_ADDRESS, ZERO_BYTES32 }, } = require('../..'); +const MockAggregator = artifacts.require('MockAggregatorV2V3'); + +/// utility function to setup price aggregators +/// @param exchangeRates instance of ExchangeRates contract +/// @param owner owner account of exchangeRates contract for adding an aggregator +/// @param keys array of bytes32 currency keys +/// @param decimalsArray optional array of ints for each key, defaults to 18 decimals +async function setupPriceAggregators(exchangeRates, owner, keys, decimalsArray = []) { + let aggregator; + for (let i = 0; i < keys.length; i++) { + aggregator = await MockAggregator.new({ from: owner }); + await aggregator.setDecimals(decimalsArray.length > 0 ? decimalsArray[i] : 18); + await exchangeRates.addAggregator(keys[i], aggregator.address, { from: owner }); + } +} + +/// same as setupPriceAggregators, but checks if an aggregator for that currency is already setup up +async function setupMissingPriceAggregators(exchangeRates, owner, keys) { + const missingKeys = []; + for (let i = 0; i < keys.length; i++) { + if ((await exchangeRates.aggregators(keys[i])) === ZERO_ADDRESS) { + missingKeys.push(keys[i]); + } + } + await setupPriceAggregators(exchangeRates, owner, missingKeys); +} +// utility function update rates for aggregators that are already set up +/// @param exchangeRates instance of ExchangeRates contract +/// @param owner owner account of exchangeRates contract for adding an aggregator +/// @param keys array of bytes32 currency keys +/// @param rates array of BN rates +/// @param timestamp optional timestamp for the update, currentTime() is used by default +async function updateAggregatorRates(exchangeRates, keys, rates, timestamp = undefined) { + timestamp = timestamp || (await currentTime()); + for (let i = 0; i < keys.length; i++) { + const aggregatorAddress = await exchangeRates.aggregators(keys[i]); + const aggregator = await MockAggregator.at(aggregatorAddress); + // set the rate + await aggregator.setLatestAnswer(rates[i], timestamp); + } +} + module.exports = { /** * the truffle transaction does not return all events logged, only those from the invoked @@ -87,28 +129,17 @@ module.exports = { return web3.utils.hexToAscii(web3.utils.utf8ToHex(input)); }, - async updateRatesWithDefaults({ exchangeRates, oracle, debtCache }) { - const timestamp = await currentTime(); - - const [SNX, sAUD, sEUR, sBTC, iBTC, sETH, ETH] = [ - 'SNX', - 'sAUD', - 'sEUR', - 'sBTC', - 'iBTC', - 'sETH', - 'ETH', - ].map(toBytes32); - - await exchangeRates.updateRates( - [SNX, sAUD, sEUR, sBTC, iBTC, sETH, ETH], - ['0.1', '0.5', '1.25', '5000', '4000', '172', '172'].map(toUnit), - timestamp, - { - from: oracle, - } - ); + setupPriceAggregators, + + updateAggregatorRates, + + async updateRatesWithDefaults({ exchangeRates, owner, debtCache }) { + const keys = ['SNX', 'sAUD', 'sEUR', 'sBTC', 'iBTC', 'sETH', 'ETH'].map(toBytes32); + const rates = ['0.1', '0.5', '1.25', '5000', '4000', '172', '172'].map(toUnit); + // set up any missing aggregators + await setupMissingPriceAggregators(exchangeRates, owner, keys); + await updateAggregatorRates(exchangeRates, keys, rates); await debtCache.takeDebtSnapshot(); }, diff --git a/test/contracts/setup.js b/test/contracts/setup.js index 925d74f4ca..c81fa976dc 100644 --- a/test/contracts/setup.js +++ b/test/contracts/setup.js @@ -4,6 +4,8 @@ const { artifacts, web3, log } = require('hardhat'); const { toWei } = web3.utils; const { toUnit } = require('../utils')(); +const { setupPriceAggregators, updateAggregatorRates } = require('./helpers'); + const { toBytes32, getUsers, @@ -108,7 +110,7 @@ const setupContract = async ({ skipPostDeploy = false, properties = {}, }) => { - const [deployerAccount, owner, oracle, fundsWallet] = accounts; + const [deployerAccount, owner, , fundsWallet] = accounts; const artifact = artifacts.require(contract); @@ -146,20 +148,8 @@ const setupContract = async ({ AddressResolver: [owner], SystemStatus: [owner], FlexibleStorage: [tryGetAddressOf('AddressResolver')], - ExchangeRates: [ - owner, - oracle, - tryGetAddressOf('AddressResolver'), - [toBytes32('SNX')], - [toWei('0.2', 'ether')], - ], - ExchangeRatesWithDexPricing: [ - owner, - oracle, - tryGetAddressOf('AddressResolver'), - [toBytes32('SNX')], - [toWei('0.2', 'ether')], - ], + ExchangeRates: [owner, tryGetAddressOf('AddressResolver')], + ExchangeRatesWithDexPricing: [owner, tryGetAddressOf('AddressResolver')], SynthetixState: [owner, ZERO_ADDRESS], SupplySchedule: [owner, 0, 0], Proxy: [owner], @@ -1132,6 +1122,13 @@ const setupAllContracts = async ({ .map(mock => mock.setAddressResolver(returnObj['AddressResolver'].address)) ); + if (returnObj['ExchangeRates']) { + // setup SNX price feed + const SNX = toBytes32('SNX'); + await setupPriceAggregators(returnObj['ExchangeRates'], owner, [SNX]); + await updateAggregatorRates(returnObj['ExchangeRates'], [SNX], [toUnit('0.2')]); + } + return returnObj; }; diff --git a/test/integration/behaviors/redeem.behavior.js b/test/integration/behaviors/redeem.behavior.js index 7b1d5f793b..4e855ce62c 100644 --- a/test/integration/behaviors/redeem.behavior.js +++ b/test/integration/behaviors/redeem.behavior.js @@ -6,7 +6,7 @@ const { } = require('../../../index'); const { ensureBalance } = require('../utils/balances'); const { skipWaitingPeriod } = require('../utils/skip'); -const { updateExchangeRatesIfNeeded } = require('../utils/rates'); +const { increaseStalePeriodAndCheckRatesAndCache } = require('../utils/rates'); function itCanRedeem({ ctx }) { describe('redemption of deprecated synths', () => { @@ -57,7 +57,7 @@ function itCanRedeem({ ctx }) { }); before('update rates and take snapshot if needed', async () => { - await updateExchangeRatesIfNeeded({ ctx }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx }); }); before('record total system debt', async () => { diff --git a/test/integration/utils/bootstrap.js b/test/integration/utils/bootstrap.js index 0060f92781..c905e731e5 100644 --- a/test/integration/utils/bootstrap.js +++ b/test/integration/utils/bootstrap.js @@ -2,7 +2,7 @@ const hre = require('hardhat'); const ethers = require('ethers'); const { loadUsers } = require('./users'); const { connectContracts } = require('./contracts'); -const { updateExchangeRatesIfNeeded } = require('./rates'); +const { increaseStalePeriodAndCheckRatesAndCache } = require('./rates'); const { ensureBalance } = require('./balances'); const { setupOptimismWatchers, approveBridge } = require('./optimism'); const { startOpsHeartbeat } = require('./optimism-temp'); @@ -26,7 +26,7 @@ function bootstrapL1({ ctx }) { connectContracts({ ctx }); - await updateExchangeRatesIfNeeded({ ctx }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx }); }); } @@ -57,7 +57,7 @@ function bootstrapL2({ ctx }) { connectContracts({ ctx }); - await updateExchangeRatesIfNeeded({ ctx }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx }); await ensureBalance({ ctx, @@ -95,8 +95,8 @@ function bootstrapDual({ ctx }) { connectContracts({ ctx: ctx.l1 }); connectContracts({ ctx: ctx.l2 }); - await updateExchangeRatesIfNeeded({ ctx: ctx.l1 }); - await updateExchangeRatesIfNeeded({ ctx: ctx.l2 }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx: ctx.l1 }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx: ctx.l2 }); await approveBridge({ ctx: ctx.l1, amount: ethers.utils.parseEther('100000000') }); diff --git a/test/integration/utils/rates.js b/test/integration/utils/rates.js index a620cebcc0..bdc7556eed 100644 --- a/test/integration/utils/rates.js +++ b/test/integration/utils/rates.js @@ -1,15 +1,18 @@ const ethers = require('ethers'); const { setSystemSetting } = require('./settings'); const { toBytes32 } = require('../../..'); +const { createMockAggregatorFactory } = require('../../utils')(); -async function updateExchangeRatesIfNeeded({ ctx }) { +async function increaseStalePeriodAndCheckRatesAndCache({ ctx }) { await setSystemSetting({ ctx, settingName: 'rateStalePeriod', newValue: '1000000000' }); if (await _areRatesInvalid({ ctx })) { - await _setNewRates({ ctx }); + // try to add the missing rates + await _setMissingRates({ ctx }); + // check again if (await _areRatesInvalid({ ctx })) { await _printRatesInfo({ ctx }); - throw new Error('Rates are still invalid after updating them.'); + throw new Error('Rates are still invalid after updating.'); } } @@ -71,35 +74,40 @@ async function _getAvailableCurrencyKeys({ ctx }) { .concat(['SNX', 'ETH'].map(toBytes32)); } -async function _getCurrentRates({ ctx, currencyKeys }) { - const { ExchangeRates } = ctx.contracts; - - const rates = []; - for (const currencyKey of currencyKeys) { - const rate = await ExchangeRates.rateForCurrency(currencyKey); - - // Simualte any exchange rates that are 0. - const newRate = rate.toString() === '0' ? ethers.utils.parseEther('1') : rate; - - rates.push(newRate); +async function _setMissingRates({ ctx }) { + let currencyKeys; + if (ctx.fork) { + // this adds a rate for only e.g. sREDEEMER in fork mode (which is not an existing synth + // but is added to test the redeeming functionality) + // All other synths should have feeds in fork mode + currencyKeys = (ctx.addedSynths || []).map(o => toBytes32(o.name)); + } else { + // set missing rates for all synths, since not in fork mode we don't have existing feeds + currencyKeys = await _getAvailableCurrencyKeys({ ctx }); } - return rates; -} + const owner = ctx.users.owner; + const ExchangeRates = ctx.contracts.ExchangeRates.connect(owner); -async function _setNewRates({ ctx }) { - let { ExchangeRates } = ctx.contracts; - const oracle = await ExchangeRates.oracle(); - const signer = ctx.fork ? ctx.provider.getSigner(oracle) : ctx.users.owner; - ExchangeRates = ExchangeRates.connect(signer); + // factory for price aggregators contracts + const MockAggregatorFactory = await createMockAggregatorFactory(owner); - const currencyKeys = await _getAvailableCurrencyKeys({ ctx }); + // got over all rates and add aggregators const { timestamp } = await ctx.provider.getBlock(); - - const rates = await _getCurrentRates({ ctx, currencyKeys }); - - const tx = await ExchangeRates.updateRates(currencyKeys, rates, timestamp); - await tx.wait(); + for (const currencyKey of currencyKeys) { + const rate = await ExchangeRates.rateForCurrency(currencyKey); + if (rate.toString() === '0') { + // deploy an aggregator + let aggregator = await MockAggregatorFactory.deploy(); + aggregator = aggregator.connect(owner); + // set decimals + await (await aggregator.setDecimals(18)).wait(); + // push the new price + await (await aggregator.setLatestAnswer(ethers.utils.parseEther('1'), timestamp)).wait(); + // set the aggregator in ExchangeRates + await (await ExchangeRates.addAggregator(currencyKey, aggregator.address)).wait(); + } + } } async function _updateCache({ ctx }) { @@ -122,7 +130,7 @@ async function getRate({ ctx, symbol }) { } module.exports = { - updateExchangeRatesIfNeeded, + increaseStalePeriodAndCheckRatesAndCache, getRate, updateCache, }; diff --git a/test/integration/utils/skip.js b/test/integration/utils/skip.js index b32ce985ce..f668acff22 100644 --- a/test/integration/utils/skip.js +++ b/test/integration/utils/skip.js @@ -1,7 +1,7 @@ const { fastForward } = require('../../test-utils/rpc'); const { wait } = require('../../test-utils/wait'); const { getSystemSetting } = require('./settings'); -const { updateExchangeRatesIfNeeded } = require('./rates'); +const { increaseStalePeriodAndCheckRatesAndCache } = require('./rates'); async function skipWaitingPeriod({ ctx }) { await _dualFastForward({ @@ -35,7 +35,7 @@ async function _dualFastForward({ ctx, seconds }) { await fastForward({ seconds: parseInt(seconds), provider: l1Ctx.provider }); await wait({ seconds: 6 }); - await updateExchangeRatesIfNeeded({ ctx }); + await increaseStalePeriodAndCheckRatesAndCache({ ctx }); } module.exports = { diff --git a/test/publish/index.js b/test/publish/index.js index b98bc6805e..46fa1250fd 100644 --- a/test/publish/index.js +++ b/test/publish/index.js @@ -6,14 +6,12 @@ const pLimit = require('p-limit'); const ethers = require('ethers'); const isCI = require('is-ci'); -const { loadCompiledFiles } = require('../../publish/src/solidity'); const { loadLocalWallets } = require('../test-utils/wallets'); const { fastForward } = require('../test-utils/rpc'); const deployStakingRewardsCmd = require('../../publish/src/commands/deploy-staking-rewards'); const deployShortingRewardsCmd = require('../../publish/src/commands/deploy-shorting-rewards'); const deployCmd = require('../../publish/src/commands/deploy'); -const { buildPath } = deployCmd.DEFAULTS; const testUtils = require('../utils'); const commands = { @@ -97,6 +95,7 @@ describe('publish scripts', () => { let sETH; let provider; let overrides; + let MockAggregatorFactory; const resetConfigAndSynthFiles = () => { // restore the synths and config files for this env (cause removal updated it) @@ -135,7 +134,7 @@ describe('publish scripts', () => { url: 'http://localhost:8545', }); - const { isCompileRequired } = testUtils(); + const { isCompileRequired, createMockAggregatorFactory } = testUtils(); // load accounts used by local EVM const wallets = loadLocalWallets({ provider }); @@ -154,6 +153,8 @@ describe('publish scripts', () => { console.log('Skipping build as everything up to date'); } + MockAggregatorFactory = await createMockAggregatorFactory(accounts.deployer); + [sUSD, sBTC, sETH] = ['sUSD', 'sBTC', 'sETH'].map(toBytes32); gasLimit = 8000000; @@ -195,15 +196,6 @@ describe('publish scripts', () => { ); const createMockAggregator = async () => { - // get last build - const { compiled } = loadCompiledFiles({ buildPath }); - const { - abi, - evm: { - bytecode: { object: bytecode }, - }, - } = compiled['MockAggregatorV2V3']; - const MockAggregatorFactory = new ethers.ContractFactory(abi, bytecode, accounts.deployer); const MockAggregator = await MockAggregatorFactory.deploy({ gasLimit, gasPrice }); const tx = await MockAggregator.setDecimals('8', { diff --git a/test/utils/index.js b/test/utils/index.js index 54f251eaa1..e49020edbe 100644 --- a/test/utils/index.js +++ b/test/utils/index.js @@ -528,6 +528,18 @@ module.exports = ({ web3 } = {}) => { return latestSolTimestamp > earliestCompiledTimestamp; }; + // create a factory to deploy mock price aggregators + const createMockAggregatorFactory = async account => { + const { compiled } = loadCompiledFiles({ buildPath }); + const { + abi, + evm: { + bytecode: { object: bytecode }, + }, + } = compiled['MockAggregatorV2V3']; + return new ethers.ContractFactory(abi, bytecode, account); + }; + const setupProvider = ({ providerUrl, privateKey, publicKey }) => { const provider = new ethers.providers.JsonRpcProvider(providerUrl); @@ -606,6 +618,7 @@ module.exports = ({ web3 } = {}) => { loadLocalUsers, isCompileRequired, + createMockAggregatorFactory, setupProvider, getContract,