Skip to content

Commit 7d34911

Browse files
committed
Revert SIP-193 Reduce size for SystemSettings (#1627)
1 parent cb04549 commit 7d34911

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2274
-766
lines changed

contracts/ExchangeRates.sol

Lines changed: 159 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
2222
using SafeDecimalMath for uint;
2323

2424
bytes32 public constant CONTRACT_NAME = "ExchangeRates";
25-
//slither-disable-next-line naming-convention
26-
bytes32 internal constant sUSD = "sUSD";
25+
26+
// Exchange rates and update times stored by currency code, e.g. 'SNX', or 'sUSD'
27+
mapping(bytes32 => mapping(uint => RateAndUpdatedTime)) private _rates;
28+
29+
// The address of the oracle which pushes rate updates to this contract
30+
address public oracle;
2731

2832
// Decentralized oracle networks that feed into pricing aggregators
2933
mapping(bytes32 => AggregatorV2V3Interface) public aggregators;
@@ -33,12 +37,58 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
3337
// List of aggregator keys for convenient iteration
3438
bytes32[] public aggregatorKeys;
3539

40+
// Do not allow the oracle to submit times any further forward into the future than this constant.
41+
uint private constant ORACLE_FUTURE_LIMIT = 10 minutes;
42+
43+
mapping(bytes32 => uint) public currentRoundForRate;
44+
45+
//
3646
// ========== CONSTRUCTOR ==========
3747

38-
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
48+
constructor(
49+
address _owner,
50+
address _oracle,
51+
address _resolver,
52+
bytes32[] memory _currencyKeys,
53+
uint[] memory _newRates
54+
) public Owned(_owner) MixinSystemSettings(_resolver) {
55+
require(_currencyKeys.length == _newRates.length, "Currency key length and rate length must match.");
56+
57+
oracle = _oracle;
58+
59+
// The sUSD rate is always 1 and is never stale.
60+
_setRate("sUSD", SafeDecimalMath.unit(), now);
61+
62+
internalUpdateRates(_currencyKeys, _newRates, now);
63+
}
64+
65+
/* ========== SETTERS ========== */
66+
67+
function setOracle(address _oracle) external onlyOwner {
68+
oracle = _oracle;
69+
emit OracleUpdated(oracle);
70+
}
3971

4072
/* ========== MUTATIVE FUNCTIONS ========== */
4173

74+
function updateRates(
75+
bytes32[] calldata currencyKeys,
76+
uint[] calldata newRates,
77+
uint timeSent
78+
) external onlyOracle returns (bool) {
79+
return internalUpdateRates(currencyKeys, newRates, timeSent);
80+
}
81+
82+
function deleteRate(bytes32 currencyKey) external onlyOracle {
83+
require(_getRate(currencyKey) > 0, "Rate is zero");
84+
85+
delete _rates[currencyKey][currentRoundForRate[currencyKey]];
86+
87+
currentRoundForRate[currencyKey]--;
88+
89+
emit RateDeleted(currencyKey);
90+
}
91+
4292
function addAggregator(bytes32 currencyKey, address aggregatorAddress) external onlyOwner {
4393
AggregatorV2V3Interface aggregator = AggregatorV2V3Interface(aggregatorAddress);
4494
// This check tries to make sure that a valid aggregator is being added.
@@ -238,7 +288,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
238288
function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid) {
239289
RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey);
240290

241-
if (currencyKey == sUSD) {
291+
if (currencyKey == "sUSD") {
242292
return (rateAndTime.rate, false);
243293
}
244294
return (
@@ -264,7 +314,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
264314
// do one lookup of the rate & time to minimize gas
265315
RateAndUpdatedTime memory rateEntry = _getRateAndUpdatedTime(currencyKeys[i]);
266316
rates[i] = rateEntry.rate;
267-
if (!anyRateInvalid && currencyKeys[i] != sUSD) {
317+
if (!anyRateInvalid && currencyKeys[i] != "sUSD") {
268318
anyRateInvalid = flagList[i] || _rateIsStaleWithTime(_rateStalePeriod, rateEntry.time);
269319
}
270320
}
@@ -322,6 +372,52 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
322372
}
323373
}
324374

375+
function _setRate(
376+
bytes32 currencyKey,
377+
uint256 rate,
378+
uint256 time
379+
) internal {
380+
// Note: this will effectively start the rounds at 1, which matches Chainlink's Agggregators
381+
currentRoundForRate[currencyKey]++;
382+
383+
_rates[currencyKey][currentRoundForRate[currencyKey]] = RateAndUpdatedTime({
384+
rate: uint216(rate),
385+
time: uint40(time)
386+
});
387+
}
388+
389+
function internalUpdateRates(
390+
bytes32[] memory currencyKeys,
391+
uint[] memory newRates,
392+
uint timeSent
393+
) internal returns (bool) {
394+
require(currencyKeys.length == newRates.length, "Currency key array length must match rates array length.");
395+
require(timeSent < (now + ORACLE_FUTURE_LIMIT), "Time is too far into the future");
396+
397+
// Loop through each key and perform update.
398+
for (uint i = 0; i < currencyKeys.length; i++) {
399+
bytes32 currencyKey = currencyKeys[i];
400+
401+
// Should not set any rate to zero ever, as no asset will ever be
402+
// truely worthless and still valid. In this scenario, we should
403+
// delete the rate and remove it from the system.
404+
require(newRates[i] != 0, "Zero is not a valid rate, please call deleteRate instead.");
405+
require(currencyKey != "sUSD", "Rate of sUSD cannot be updated, it's always UNIT.");
406+
407+
// We should only update the rate if it's at least the same age as the last rate we've got.
408+
if (timeSent < _getUpdatedTime(currencyKey)) {
409+
continue;
410+
}
411+
412+
// Ok, go ahead with the update.
413+
_setRate(currencyKey, newRates[i], timeSent);
414+
}
415+
416+
emit RatesUpdated(currencyKeys, newRates);
417+
418+
return true;
419+
}
420+
325421
function removeFromArray(bytes32 entry, bytes32[] storage array) internal returns (bool) {
326422
for (uint i = 0; i < array.length; i++) {
327423
if (array[i] == entry) {
@@ -351,64 +447,60 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
351447
}
352448

353449
function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) {
354-
// sUSD rate is 1.0
355-
if (currencyKey == sUSD) {
356-
return RateAndUpdatedTime({rate: uint216(SafeDecimalMath.unit()), time: 0});
450+
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
451+
452+
if (aggregator != AggregatorV2V3Interface(0)) {
453+
// this view from the aggregator is the most gas efficient but it can throw when there's no data,
454+
// so let's call it low-level to suppress any reverts
455+
bytes memory payload = abi.encodeWithSignature("latestRoundData()");
456+
// solhint-disable avoid-low-level-calls
457+
(bool success, bytes memory returnData) = address(aggregator).staticcall(payload);
458+
459+
if (success) {
460+
(, int256 answer, , uint256 updatedAt, ) =
461+
abi.decode(returnData, (uint80, int256, uint256, uint256, uint80));
462+
return
463+
RateAndUpdatedTime({
464+
rate: uint216(_formatAggregatorAnswer(currencyKey, answer)),
465+
time: uint40(updatedAt)
466+
});
467+
}
357468
} else {
358-
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
359-
if (aggregator != AggregatorV2V3Interface(0)) {
360-
// this view from the aggregator is the most gas efficient but it can throw when there's no data,
361-
// so let's call it low-level to suppress any reverts
362-
bytes memory payload = abi.encodeWithSignature("latestRoundData()");
363-
// solhint-disable avoid-low-level-calls
364-
// slither-disable-next-line low-level-calls
365-
(bool success, bytes memory returnData) = address(aggregator).staticcall(payload);
366-
367-
if (success) {
368-
(, int256 answer, , uint256 updatedAt, ) =
369-
abi.decode(returnData, (uint80, int256, uint256, uint256, uint80));
370-
return
371-
RateAndUpdatedTime({
372-
rate: uint216(_formatAggregatorAnswer(currencyKey, answer)),
373-
time: uint40(updatedAt)
374-
});
375-
} // else return defaults, to avoid reverting in views
376-
} // else return defaults, to avoid reverting in views
469+
uint roundId = currentRoundForRate[currencyKey];
470+
RateAndUpdatedTime memory entry = _rates[currencyKey][roundId];
471+
472+
return RateAndUpdatedTime({rate: uint216(entry.rate), time: entry.time});
377473
}
378474
}
379475

380476
function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) {
381-
if (currencyKey == sUSD) {
382-
return 0; // no roundIds for sUSD
383-
}
384477
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
478+
385479
if (aggregator != AggregatorV2V3Interface(0)) {
386480
return aggregator.latestRound();
387-
} // else return defaults, to avoid reverting in views
481+
} else {
482+
return currentRoundForRate[currencyKey];
483+
}
388484
}
389485

390486
function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) {
391-
// short circuit sUSD
392-
if (currencyKey == sUSD) {
393-
// sUSD has no rounds, and 0 time is preferrable for "volatility" heuristics
394-
// which are used in atomic swaps and fee reclamation
395-
return (SafeDecimalMath.unit(), 0);
487+
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
488+
489+
if (aggregator != AggregatorV2V3Interface(0)) {
490+
// this view from the aggregator is the most gas efficient but it can throw when there's no data,
491+
// so let's call it low-level to suppress any reverts
492+
bytes memory payload = abi.encodeWithSignature("getRoundData(uint80)", roundId);
493+
// solhint-disable avoid-low-level-calls
494+
(bool success, bytes memory returnData) = address(aggregator).staticcall(payload);
495+
496+
if (success) {
497+
(, int256 answer, , uint256 updatedAt, ) =
498+
abi.decode(returnData, (uint80, int256, uint256, uint256, uint80));
499+
return (_formatAggregatorAnswer(currencyKey, answer), updatedAt);
500+
}
396501
} else {
397-
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
398-
399-
if (aggregator != AggregatorV2V3Interface(0)) {
400-
// this view from the aggregator is the most gas efficient but it can throw when there's no data,
401-
// so let's call it low-level to suppress any reverts
402-
bytes memory payload = abi.encodeWithSignature("getRoundData(uint80)", roundId);
403-
// solhint-disable avoid-low-level-calls
404-
(bool success, bytes memory returnData) = address(aggregator).staticcall(payload);
405-
406-
if (success) {
407-
(, int256 answer, , uint256 updatedAt, ) =
408-
abi.decode(returnData, (uint80, int256, uint256, uint256, uint80));
409-
return (_formatAggregatorAnswer(currencyKey, answer), updatedAt);
410-
} // else return defaults, to avoid reverting in views
411-
} // else return defaults, to avoid reverting in views
502+
RateAndUpdatedTime memory update = _rates[currencyKey][roundId];
503+
return (update.rate, update.time);
412504
}
413505
}
414506

@@ -450,7 +542,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
450542

451543
function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) {
452544
// sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime)
453-
if (currencyKey == sUSD) return false;
545+
if (currencyKey == "sUSD") return false;
454546

455547
return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey));
456548
}
@@ -461,7 +553,7 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
461553

462554
function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) {
463555
// sUSD is a special case and is never invalid
464-
if (currencyKey == sUSD) return false;
556+
if (currencyKey == "sUSD") return false;
465557
address aggregator = address(aggregators[currencyKey]);
466558
// when no aggregator or when the flags haven't been setup
467559
if (aggregator == address(0) || flags == FlagsInterface(0)) {
@@ -471,12 +563,25 @@ contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
471563
}
472564

473565
function _notImplemented() internal pure {
474-
// slither-disable-next-line dead-code
475566
revert("Cannot be run on this layer");
476567
}
477568

569+
/* ========== MODIFIERS ========== */
570+
571+
modifier onlyOracle {
572+
_onlyOracle();
573+
_;
574+
}
575+
576+
function _onlyOracle() internal view {
577+
require(msg.sender == oracle, "Only the oracle can perform this action");
578+
}
579+
478580
/* ========== EVENTS ========== */
479581

582+
event OracleUpdated(address newOracle);
583+
event RatesUpdated(bytes32[] currencyKeys, uint[] newRates);
584+
event RateDeleted(bytes32 currencyKey);
480585
event AggregatorAdded(bytes32 currencyKey, address aggregator);
481586
event AggregatorRemoved(bytes32 currencyKey, address aggregator);
482587
}

contracts/ExchangeRatesWithDexPricing.sol

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ contract ExchangeRatesWithDexPricing is ExchangeRates {
1010

1111
bytes32 internal constant SETTING_DEX_PRICE_AGGREGATOR = "dexPriceAggregator";
1212

13-
constructor(address _owner, address _resolver) public ExchangeRates(_owner, _resolver) {}
13+
constructor(
14+
address _owner,
15+
address _oracle,
16+
address _resolver,
17+
bytes32[] memory _currencyKeys,
18+
uint[] memory _newRates
19+
) public ExchangeRates(_owner, _oracle, _resolver, _currencyKeys, _newRates) {}
1420

1521
/* ========== SETTERS ========== */
1622

contracts/interfaces/IExchangeRates.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ interface IExchangeRates {
1515

1616
function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool);
1717

18+
function currentRoundForRate(bytes32 currencyKey) external view returns (uint);
19+
1820
function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory);
1921

2022
function effectiveValue(
@@ -69,6 +71,8 @@ interface IExchangeRates {
6971

7072
function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256);
7173

74+
function oracle() external view returns (address);
75+
7276
function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time);
7377

7478
function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time);

hardhat.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,5 @@ module.exports = {
8383
},
8484
mocha: {
8585
timeout: 120e3, // 120s
86-
retries: 3,
8786
},
8887
};

publish/releases.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,6 @@
432432
"sources": ["CollateralEth"],
433433
"released": "ovm"
434434
},
435-
{
436-
"sip": 196,
437-
"layer": "both",
438-
"sources": ["ExchangeRates"]
439-
},
440435
{
441436
"sip": 200,
442437
"layer": "both",

publish/src/commands/deploy/deploy-core.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = async ({
1414
currentSynthetixSupply,
1515
currentWeekOfInflation,
1616
deployer,
17+
oracleAddress,
1718
useOvm,
1819
}) => {
1920
console.log(gray(`\n------ DEPLOY LIBRARIES ------\n`));
@@ -58,7 +59,7 @@ module.exports = async ({
5859
await deployer.deployContract({
5960
name: 'ExchangeRates',
6061
source: useOvm ? 'ExchangeRates' : 'ExchangeRatesWithDexPricing',
61-
args: [account, addressOf(readProxyForResolver)],
62+
args: [account, oracleAddress, addressOf(readProxyForResolver), [], []],
6263
});
6364

6465
await deployer.deployContract({

publish/src/commands/deploy/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const deploy = async ({
6262
ignoreSafetyChecks,
6363
manageNonces,
6464
network = DEFAULTS.network,
65+
oracleExrates,
6566
privateKey,
6667
providerUrl,
6768
skipFeedChecks = false,
@@ -211,6 +212,7 @@ const deploy = async ({
211212
currentSynthetixSupply,
212213
currentLastMintEvent,
213214
currentWeekOfInflation,
215+
oracleAddress,
214216
systemSuspended,
215217
} = await systemAndParameterCheck({
216218
account,
@@ -227,6 +229,7 @@ const deploy = async ({
227229
maxPriorityFeePerGas,
228230
getDeployParameter,
229231
network,
232+
oracleExrates,
230233
providerUrl,
231234
skipFeedChecks,
232235
standaloneFeeds,
@@ -273,6 +276,7 @@ const deploy = async ({
273276
currentSynthetixSupply,
274277
currentWeekOfInflation,
275278
deployer,
279+
oracleAddress,
276280
useOvm,
277281
});
278282

0 commit comments

Comments
 (0)