-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathRWADynamicOracle.sol
407 lines (373 loc) · 13.3 KB
/
RWADynamicOracle.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/**SPDX-License-Identifier: BUSL-1.1
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
import "contracts/rwaOracles/IRWAOracle.sol";
import "contracts/external/openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "contracts/external/openzeppelin/contracts/security/Pausable.sol";
contract RWADynamicOracle is IRWAOracle, AccessControlEnumerable, Pausable {
uint256 public constant DAY = 1 days;
Range[] public ranges;
bytes32 public constant SETTER_ROLE = keccak256("SETTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
constructor(
address admin,
address setter,
address pauser,
uint256 firstRangeStart,
uint256 firstRangeEnd,
uint256 dailyIR,
uint256 startPrice
) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(PAUSER_ROLE, pauser);
_grantRole(SETTER_ROLE, setter);
if (firstRangeStart >= firstRangeEnd) revert InvalidRange();
uint256 trueStart = (startPrice * ONE) / dailyIR;
ranges.push(Range(firstRangeStart, firstRangeEnd, dailyIR, trueStart));
}
/*//////////////////////////////////////////////////////////////
Public Functions
//////////////////////////////////////////////////////////////*/
/**
* @notice Function which returns the daily price of USDY given the range previously set
*
* @return price The current price of USDY
* @return timestamp The current timestamp of the call
*/
function getPriceData()
external
view
returns (uint256 price, uint256 timestamp)
{
price = getPrice();
timestamp = block.timestamp;
}
/**
* @notice Function which returns the daily price of USDY given the range previously set
*
* @return price The current price of USDY
*
* @dev The Ranges are not intended to be set more than 2 ranges in advance
* from the current range
*/
function getPrice() public view whenNotPaused returns (uint256 price) {
uint256 length = ranges.length;
for (uint256 i = 0; i < length; ++i) {
Range storage range = ranges[(length - 1) - i];
if (range.start <= block.timestamp) {
if (range.end <= block.timestamp) {
return derivePrice(range, range.end - 1);
} else {
return derivePrice(range, block.timestamp);
}
}
}
}
/**
* @notice External helper function used to simulate the derivation of the prices returned
* from the oracle, given a range and a timestamp
*
* @dev If you are simulating the first range, you MUST set `startTime` and `rangeStartPrice`
* @dev If you are simulating a range > 1st then `startTime` and `rangeStartPrice` values
* remain unused.
*
* @param blockTimeStamp The unixTimestamp of the point in time you wish to simulate
* @param dailyIR The daily Interest Rate for the range to simulate
* @param endTime The end time for the range to simulate
* @param startTime The start time for the range to simulate
* @param rangeStartPrice The start price for the range to simulate
*
*/
function simulateRange(
uint256 blockTimeStamp,
uint256 dailyIR,
uint256 endTime,
uint256 startTime,
uint256 rangeStartPrice
) external view returns (uint256 price) {
uint256 length = ranges.length;
Range[] memory rangeList = new Range[](length + 1);
for (uint256 i = 0; i < length; ++i) {
rangeList[i] = ranges[i];
}
if (startTime == ranges[0].start) {
uint256 trueStart = (rangeStartPrice * ONE) / dailyIR;
rangeList[length] = Range(startTime, endTime, dailyIR, trueStart);
} else {
Range memory lastRange = ranges[ranges.length - 1];
uint256 prevClosePrice = derivePrice(lastRange, lastRange.end - 1);
rangeList[length] = Range(
lastRange.end,
endTime,
dailyIR,
prevClosePrice
);
}
for (uint256 i = 0; i < length + 1; ++i) {
Range memory range = rangeList[(length) - i];
if (range.start <= blockTimeStamp) {
if (range.end <= blockTimeStamp) {
return derivePrice(range, range.end - 1);
} else {
return derivePrice(range, blockTimeStamp);
}
}
}
}
/*//////////////////////////////////////////////////////////////
Admin Functions
//////////////////////////////////////////////////////////////*/
/**
* @notice Function that allows for an admin to set a given price range for USDY
*
* @param endTimestamp The timestamp for the range to end
* @param dailyInterestRate The daily interest rate during said range
*/
function setRange(
uint256 endTimestamp,
uint256 dailyInterestRate
) external onlyRole(SETTER_ROLE) {
Range memory lastRange = ranges[ranges.length - 1];
// Check that the endTimestamp is greater than the last range's end time
if (lastRange.end >= endTimestamp) revert InvalidRange();
uint256 prevClosePrice = derivePrice(lastRange, lastRange.end - 1);
ranges.push(
Range(lastRange.end, endTimestamp, dailyInterestRate, prevClosePrice)
);
emit RangeSet(
ranges.length - 1,
lastRange.end,
endTimestamp,
dailyInterestRate,
prevClosePrice
);
}
/**
* @notice Function that allows for an admin to override a previously set range
*
* @param indexToModify The index of the range that we want to change
* @param newStart The new start time for the updated range
* @param newEnd The new end time for the updated range
* @param newDailyIR The new daily interest rate for the range to update
* @param newPrevRangeClosePrice The previous ranges close price
*
* @dev This function enforces that the range being overriden does not
* overlap with any other set ranges
* @dev If closed ranges are updated, the result is a stale value for `prevRangeClosePrice`
*/
function overrideRange(
uint256 indexToModify,
uint256 newStart,
uint256 newEnd,
uint256 newDailyIR,
uint256 newPrevRangeClosePrice
) external onlyRole(DEFAULT_ADMIN_ROLE) {
// Check that the ranges start and end time are less than each other
if (newStart >= newEnd) revert InvalidRange();
uint256 rangeLength = ranges.length;
// Case 1: The range being modified is the first range
if (indexToModify == 0) {
// If the length of ranges is greater than 1,
// Ensure that the newEnd time is not greater than the start time of the next range
if (rangeLength > 1 && newEnd > ranges[indexToModify + 1].start)
revert InvalidRange();
}
// Case 2: The range being modified is the last range
else if (indexToModify == rangeLength - 1) {
// Ensure that the newStart time is not less than the end time of the previous range
if (newStart < ranges[indexToModify - 1].end) revert InvalidRange();
}
// Case 3: The range being modified is between first and last range
else {
// Ensure that the newStart time is less than the end time of the previous range
if (newStart < ranges[indexToModify - 1].end) revert InvalidRange();
// Ensure that the newEnd time is not greater than the start time of the next range
if (newEnd > ranges[indexToModify + 1].start) revert InvalidRange();
}
// Update range
if (indexToModify == 0) {
uint256 trueStart = (newPrevRangeClosePrice * ONE) / newDailyIR;
ranges[indexToModify] = Range(newStart, newEnd, newDailyIR, trueStart);
} else {
ranges[indexToModify] = Range(
newStart,
newEnd,
newDailyIR,
newPrevRangeClosePrice
);
}
emit RangeOverriden(
indexToModify,
newStart,
newEnd,
newDailyIR,
newPrevRangeClosePrice
);
}
/**
* @notice Function to pause the oracle
*/
function pauseOracle() external onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* @notice Function to unpause the oracle
*/
function unpauseOracle() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
/*//////////////////////////////////////////////////////////////
Internal Functions
//////////////////////////////////////////////////////////////*/
/**
* @notice Internal helper function used to derive the price of USDY
*
* @param currentRange The current range to derive the price of USDY from
* @param currentTime The current unixTimestamp of the blockchain
*/
function derivePrice(
Range memory currentRange,
uint256 currentTime
) internal pure returns (uint256 price) {
uint256 elapsedDays = (currentTime - currentRange.start) / DAY;
return
roundUpTo8(
_rmul(
_rpow(currentRange.dailyInterestRate, elapsedDays + 1, ONE),
currentRange.prevRangeClosePrice
)
);
}
/**
* @notice internal function that will round derived price to the 8th decimal
* and will round 5 up
*
* @param value The value to round
*/
function roundUpTo8(uint256 value) internal pure returns (uint256) {
uint256 remainder = value % 1e10;
if (remainder >= 0.5e10) {
value += 1e10;
}
value -= remainder;
return value;
}
/*//////////////////////////////////////////////////////////////
Structs, Events and Errors
//////////////////////////////////////////////////////////////*/
struct Range {
uint256 start;
uint256 end;
uint256 dailyInterestRate;
uint256 prevRangeClosePrice;
}
/**
* @notice Event emitted when a range has been set
*
* @param start The start time for the range
* @param end The end time for the range
* @param dailyInterestRate The daily interest rate for the range
*/
event RangeSet(
uint256 indexed index,
uint256 start,
uint256 end,
uint256 dailyInterestRate,
uint256 prevRangeClosePrice
);
/**
* @notice Event emitted when a previously set range is overriden
*
* @param index The index of the range being modified
* @param newStart The new start time for the modified range
* @param newEnd The new end time for the modified range
* @param newDailyInterestRate The new dailyInterestRate for the modified range
* @param newPrevRangeClosePrice The new prevRangeClosePrice for the modified range
*/
event RangeOverriden(
uint256 indexed index,
uint256 newStart,
uint256 newEnd,
uint256 newDailyInterestRate,
uint256 newPrevRangeClosePrice
);
error InvalidPrice();
error InvalidRange();
error PriceNotSet();
/*//////////////////////////////////////////////////////////////
Interest calculation helper functions
//////////////////////////////////////////////////////////////*/
// Copied from https://github.com/makerdao/dss/blob/master/src/jug.sol
uint256 private constant ONE = 10 ** 27;
function _rpow(
uint256 x,
uint256 n,
uint256 base
) internal pure returns (uint256 z) {
assembly {
switch x
case 0 {
switch n
case 0 {
z := base
}
default {
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
z := base
}
default {
z := x
}
let half := div(base, 2) // for rounding.
for {
n := div(n, 2)
} n {
n := div(n, 2)
} {
let xx := mul(x, x)
if iszero(eq(div(xx, x), x)) {
revert(0, 0)
}
let xxRound := add(xx, half)
if lt(xxRound, xx) {
revert(0, 0)
}
x := div(xxRound, base)
if mod(n, 2) {
let zx := mul(z, x)
if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) {
revert(0, 0)
}
let zxRound := add(zx, half)
if lt(zxRound, zx) {
revert(0, 0)
}
z := div(zxRound, base)
}
}
}
}
}
function _rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = _mul(x, y) / ONE;
}
function _mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(y == 0 || (z = x * y) / y == x);
}
}