@@ -22,11 +22,6 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
22
22
using SafeMath for uint ;
23
23
using SafeDecimalMath for uint ;
24
24
25
- struct RateAndUpdatedTime {
26
- uint216 rate;
27
- uint40 time;
28
- }
29
-
30
25
// Exchange rates and update times stored by currency code, e.g. 'SNX', or 'sUSD'
31
26
mapping (bytes32 => mapping (uint => RateAndUpdatedTime)) private _rates;
32
27
@@ -44,14 +39,8 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
44
39
45
40
int private constant AGGREGATOR_RATE_MULTIPLIER = 1e10 ;
46
41
47
- // For inverted prices, keep a mapping of their entry, limits and frozen status
48
- struct InversePricing {
49
- uint entryPoint;
50
- uint upperLimit;
51
- uint lowerLimit;
52
- bool frozen;
53
- }
54
42
mapping (bytes32 => InversePricing) public inversePricing;
43
+
55
44
bytes32 [] public invertedKeys;
56
45
57
46
mapping (bytes32 => uint ) public currentRoundForRate;
@@ -110,43 +99,47 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
110
99
uint entryPoint ,
111
100
uint upperLimit ,
112
101
uint lowerLimit ,
113
- bool freeze ,
114
- bool freezeAtUpperLimit
102
+ bool freezeAtUpperLimit ,
103
+ bool freezeAtLowerLimit
115
104
) external onlyOwner {
116
105
// 0 < lowerLimit < entryPoint => 0 < entryPoint
117
106
require (lowerLimit > 0 , "lowerLimit must be above 0 " );
118
107
require (upperLimit > entryPoint, "upperLimit must be above the entryPoint " );
119
108
require (upperLimit < entryPoint.mul (2 ), "upperLimit must be less than double entryPoint " );
120
109
require (lowerLimit < entryPoint, "lowerLimit must be below the entryPoint " );
121
110
122
- if (inversePricing[currencyKey].entryPoint <= 0 ) {
111
+ require (! (freezeAtUpperLimit && freezeAtLowerLimit), "Cannot freeze at both limits " );
112
+
113
+ InversePricing storage inverse = inversePricing[currencyKey];
114
+ if (inverse.entryPoint == 0 ) {
123
115
// then we are adding a new inverse pricing, so add this
124
116
invertedKeys.push (currencyKey);
125
117
}
126
- inversePricing[currencyKey].entryPoint = entryPoint;
127
- inversePricing[currencyKey].upperLimit = upperLimit;
128
- inversePricing[currencyKey].lowerLimit = lowerLimit;
129
- inversePricing[currencyKey].frozen = freeze;
118
+ inverse.entryPoint = entryPoint;
119
+ inverse.upperLimit = upperLimit;
120
+ inverse.lowerLimit = lowerLimit;
121
+
122
+ if (freezeAtUpperLimit || freezeAtLowerLimit) {
123
+ // When indicating to freeze, we need to know the rate to freeze it at - either upper or lower
124
+ // this is useful in situations where ExchangeRates is updated and there are existing inverted
125
+ // rates already frozen in the current contract that need persisting across the upgrade
126
+
127
+ inverse.frozenAtUpperLimit = freezeAtUpperLimit;
128
+ inverse.frozenAtLowerLimit = freezeAtLowerLimit;
129
+ emit InversePriceFrozen (currencyKey, freezeAtUpperLimit ? upperLimit : lowerLimit, msg .sender );
130
+ } else {
131
+ // unfreeze if need be
132
+ inverse.frozenAtUpperLimit = false ;
133
+ inverse.frozenAtLowerLimit = false ;
134
+ }
130
135
131
136
emit InversePriceConfigured (currencyKey, entryPoint, upperLimit, lowerLimit);
132
-
133
- // When indicating to freeze, we need to know the rate to freeze it at - either upper or lower
134
- // this is useful in situations where ExchangeRates is updated and there are existing inverted
135
- // rates already frozen in the current contract that need persisting across the upgrade
136
- if (freeze) {
137
- emit InversePriceFrozen (currencyKey);
138
-
139
- _setRate (currencyKey, freezeAtUpperLimit ? upperLimit : lowerLimit, now );
140
- }
141
137
}
142
138
143
139
function removeInversePricing (bytes32 currencyKey ) external onlyOwner {
144
140
require (inversePricing[currencyKey].entryPoint > 0 , "No inverted price exists " );
145
141
146
- inversePricing[currencyKey].entryPoint = 0 ;
147
- inversePricing[currencyKey].upperLimit = 0 ;
148
- inversePricing[currencyKey].lowerLimit = 0 ;
149
- inversePricing[currencyKey].frozen = false ;
142
+ delete inversePricing[currencyKey];
150
143
151
144
// now remove inverted key from array
152
145
bool wasRemoved = removeFromArray (currencyKey, invertedKeys);
@@ -180,8 +173,47 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
180
173
}
181
174
}
182
175
176
+ // SIP-75 Public keeper function to freeze a synth that is out of bounds
177
+ function freezeRate (bytes32 currencyKey ) external {
178
+ InversePricing storage inverse = inversePricing[currencyKey];
179
+ require (inverse.entryPoint > 0 , "Cannot freeze non-inverse rate " );
180
+ require (! inverse.frozenAtUpperLimit && ! inverse.frozenAtLowerLimit, "The rate is already frozen " );
181
+
182
+ uint rate = _getRate (currencyKey);
183
+
184
+ if (rate > 0 && (rate >= inverse.upperLimit || rate <= inverse.lowerLimit)) {
185
+ inverse.frozenAtUpperLimit = (rate == inverse.upperLimit);
186
+ inverse.frozenAtLowerLimit = (rate == inverse.lowerLimit);
187
+ emit InversePriceFrozen (currencyKey, rate, msg .sender );
188
+ } else {
189
+ revert ("Rate within bounds " );
190
+ }
191
+ }
192
+
183
193
/* ========== VIEWS ========== */
184
194
195
+ // SIP-75 View to determine if freezeRate can be called safely
196
+ function canFreezeRate (bytes32 currencyKey ) external view returns (bool ) {
197
+ InversePricing memory inverse = inversePricing[currencyKey];
198
+ if (inverse.entryPoint == 0 || inverse.frozenAtUpperLimit || inverse.frozenAtLowerLimit) {
199
+ return false ;
200
+ } else {
201
+ uint rate = _getRate (currencyKey);
202
+ return (rate > 0 && (rate >= inverse.upperLimit || rate <= inverse.lowerLimit));
203
+ }
204
+ }
205
+
206
+ function currenciesUsingAggregator (address aggregator ) external view returns (bytes32 [] memory currencies ) {
207
+ uint count = 0 ;
208
+ currencies = new bytes32 [](aggregatorKeys.length );
209
+ for (uint i = 0 ; i < aggregatorKeys.length ; i++ ) {
210
+ bytes32 currencyKey = aggregatorKeys[i];
211
+ if (address (aggregators[currencyKey]) == aggregator) {
212
+ currencies[count++ ] = currencyKey;
213
+ }
214
+ }
215
+ }
216
+
185
217
function rateStalePeriod () external view returns (uint ) {
186
218
return getRateStalePeriod ();
187
219
}
@@ -337,7 +369,7 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
337
369
}
338
370
339
371
function rateIsFrozen (bytes32 currencyKey ) external view returns (bool ) {
340
- return inversePricing[ currencyKey].frozen ;
372
+ return _rateIsFrozen ( currencyKey) ;
341
373
}
342
374
343
375
function rateIsInvalid (bytes32 currencyKey ) external view returns (bool ) {
@@ -421,8 +453,6 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
421
453
continue ;
422
454
}
423
455
424
- newRates[i] = rateOrInverted (currencyKey, newRates[i]);
425
-
426
456
// Ok, go ahead with the update.
427
457
_setRate (currencyKey, newRates[i], timeSent);
428
458
}
@@ -432,44 +462,6 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
432
462
return true ;
433
463
}
434
464
435
- function rateOrInverted (bytes32 currencyKey , uint rate ) internal returns (uint ) {
436
- // if an inverse mapping exists, adjust the price accordingly
437
- InversePricing storage inverse = inversePricing[currencyKey];
438
- if (inverse.entryPoint <= 0 ) {
439
- return rate;
440
- }
441
-
442
- // set the rate to the current rate initially (if it's frozen, this is what will be returned)
443
- uint newInverseRate = _getRate (currencyKey);
444
-
445
- // get the new inverted rate if not frozen
446
- if (! inverse.frozen) {
447
- uint doubleEntryPoint = inverse.entryPoint.mul (2 );
448
- if (doubleEntryPoint <= rate) {
449
- // avoid negative numbers for unsigned ints, so set this to 0
450
- // which by the requirement that lowerLimit be > 0 will
451
- // cause this to freeze the price to the lowerLimit
452
- newInverseRate = 0 ;
453
- } else {
454
- newInverseRate = doubleEntryPoint.sub (rate);
455
- }
456
-
457
- // now if new rate hits our limits, set it to the limit and freeze
458
- if (newInverseRate >= inverse.upperLimit) {
459
- newInverseRate = inverse.upperLimit;
460
- } else if (newInverseRate <= inverse.lowerLimit) {
461
- newInverseRate = inverse.lowerLimit;
462
- }
463
-
464
- if (newInverseRate == inverse.upperLimit || newInverseRate == inverse.lowerLimit) {
465
- inverse.frozen = true ;
466
- emit InversePriceFrozen (currencyKey);
467
- }
468
- }
469
-
470
- return newInverseRate;
471
- }
472
-
473
465
function removeFromArray (bytes32 entry , bytes32 [] storage array ) internal returns (bool ) {
474
466
for (uint i = 0 ; i < array.length ; i++ ) {
475
467
if (array[i] == entry) {
@@ -489,17 +481,59 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
489
481
return false ;
490
482
}
491
483
484
+ function _rateOrInverted (bytes32 currencyKey , uint rate ) internal view returns (uint newRate ) {
485
+ // if an inverse mapping exists, adjust the price accordingly
486
+ InversePricing memory inverse = inversePricing[currencyKey];
487
+ if (inverse.entryPoint == 0 || rate == 0 ) {
488
+ // when no inverse is set or when given a 0 rate, return the rate, regardless of the inverse status
489
+ // (the latter is so when a new inverse is set but the underlying has no rate, it will return 0 as
490
+ // the rate, not the lowerLimit)
491
+ return rate;
492
+ }
493
+
494
+ newRate = rate;
495
+
496
+ // These cases ensures that if a price has been frozen, it stays frozen even if it returns to the bounds
497
+ if (inverse.frozenAtUpperLimit) {
498
+ newRate = inverse.upperLimit;
499
+ } else if (inverse.frozenAtLowerLimit) {
500
+ newRate = inverse.lowerLimit;
501
+ } else {
502
+ // this ensures any rate outside the limit will never be returned
503
+ uint doubleEntryPoint = inverse.entryPoint.mul (2 );
504
+ if (doubleEntryPoint <= rate) {
505
+ // avoid negative numbers for unsigned ints, so set this to 0
506
+ // which by the requirement that lowerLimit be > 0 will
507
+ // cause this to freeze the price to the lowerLimit
508
+ newRate = 0 ;
509
+ } else {
510
+ newRate = doubleEntryPoint.sub (rate);
511
+ }
512
+
513
+ // now ensure the rate is between the bounds
514
+ if (newRate >= inverse.upperLimit) {
515
+ newRate = inverse.upperLimit;
516
+ } else if (newRate <= inverse.lowerLimit) {
517
+ newRate = inverse.lowerLimit;
518
+ }
519
+ }
520
+ }
521
+
492
522
function _getRateAndUpdatedTime (bytes32 currencyKey ) internal view returns (RateAndUpdatedTime memory ) {
493
523
AggregatorInterface aggregator = aggregators[currencyKey];
494
524
495
525
if (aggregator != AggregatorInterface (0 )) {
496
526
return
497
527
RateAndUpdatedTime ({
498
- rate: uint216 (aggregator.latestAnswer () * AGGREGATOR_RATE_MULTIPLIER),
528
+ rate: uint216 (
529
+ _rateOrInverted (currencyKey, uint (aggregator.latestAnswer () * AGGREGATOR_RATE_MULTIPLIER))
530
+ ),
499
531
time: uint40 (aggregator.latestTimestamp ())
500
532
});
501
533
} else {
502
- return _rates[currencyKey][currentRoundForRate[currencyKey]];
534
+ RateAndUpdatedTime memory entry = _rates[currencyKey][currentRoundForRate[currencyKey]];
535
+
536
+ return RateAndUpdatedTime ({rate: uint216 (_rateOrInverted (currencyKey, entry.rate)), time: entry.time});
503
537
}
504
538
}
505
539
@@ -517,10 +551,13 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
517
551
AggregatorInterface aggregator = aggregators[currencyKey];
518
552
519
553
if (aggregator != AggregatorInterface (0 )) {
520
- return (uint (aggregator.getAnswer (roundId) * AGGREGATOR_RATE_MULTIPLIER), aggregator.getTimestamp (roundId));
554
+ return (
555
+ _rateOrInverted (currencyKey, uint (aggregator.getAnswer (roundId) * AGGREGATOR_RATE_MULTIPLIER)),
556
+ aggregator.getTimestamp (roundId)
557
+ );
521
558
} else {
522
- RateAndUpdatedTime storage update = _rates[currencyKey][roundId];
523
- return (update.rate, update.time);
559
+ RateAndUpdatedTime memory update = _rates[currencyKey][roundId];
560
+ return (_rateOrInverted (currencyKey, update.rate) , update.time);
524
561
}
525
562
}
526
563
@@ -568,6 +605,11 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
568
605
return _time.add (_rateStalePeriod) < now ;
569
606
}
570
607
608
+ function _rateIsFrozen (bytes32 currencyKey ) internal view returns (bool ) {
609
+ InversePricing memory inverse = inversePricing[currencyKey];
610
+ return inverse.frozenAtUpperLimit || inverse.frozenAtLowerLimit;
611
+ }
612
+
571
613
function _rateIsFlagged (bytes32 currencyKey , FlagsInterface flags ) internal view returns (bool ) {
572
614
// sUSD is a special case and is never invalid
573
615
if (currencyKey == "sUSD " ) return false ;
@@ -592,7 +634,7 @@ contract ExchangeRates is Owned, SelfDestructible, MixinResolver, MixinSystemSet
592
634
event RatesUpdated (bytes32 [] currencyKeys , uint [] newRates );
593
635
event RateDeleted (bytes32 currencyKey );
594
636
event InversePriceConfigured (bytes32 currencyKey , uint entryPoint , uint upperLimit , uint lowerLimit );
595
- event InversePriceFrozen (bytes32 currencyKey );
637
+ event InversePriceFrozen (bytes32 currencyKey , uint rate , address initiator );
596
638
event AggregatorAdded (bytes32 currencyKey , address aggregator );
597
639
event AggregatorRemoved (bytes32 currencyKey , address aggregator );
598
640
}
0 commit comments