-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathThrottle.sol
91 lines (78 loc) · 3.27 KB
/
Throttle.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
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "./Fixed.sol";
uint48 constant ONE_HOUR = 3600; // {seconds/hour}
/**
* @title ThrottleLib
* A library that implements a usage throttle that can be used to ensure net issuance
* or net redemption for an RToken never exceeds some bounds per unit time (hour).
*
* It is expected for the RToken to use this library with two instances, one for issuance
* and one for redemption. Issuance causes the available redemption amount to increase, and
* visa versa.
*/
library ThrottleLib {
using FixLib for uint192;
struct Params {
uint256 amtRate; // {qRTok/hour} a quantity of RToken hourly; cannot be 0
uint192 pctRate; // {1/hour} a fraction of RToken hourly; can be 0
}
struct Throttle {
// === Gov params ===
Params params;
// === Cache ===
uint48 lastTimestamp; // {seconds}
uint256 lastAvailable; // {qRTok}
}
/// Reverts if usage amount exceeds available amount
/// @param supply {qRTok} Total RToken supply beforehand
/// @param amount {qRTok} Amount of RToken to use. Should be negative for the issuance
/// throttle during redemption and for the redemption throttle during issuance.
function useAvailable(
Throttle storage throttle,
uint256 supply,
int256 amount
) internal {
// untestable: amtRate will always be > 0 due to previous validations
if (throttle.params.amtRate == 0 && throttle.params.pctRate == 0) return;
// Calculate hourly limit
uint256 limit = hourlyLimit(throttle, supply); // {qRTok}
// Calculate available amount before supply change
uint256 available = currentlyAvailable(throttle, limit);
// Update throttle.timestamp if available amount changed or at limit
if (available != throttle.lastAvailable || available == limit) {
throttle.lastTimestamp = uint48(block.timestamp);
}
// Update throttle.lastAvailable
if (amount > 0) {
require(uint256(amount) <= available, "supply change throttled");
available -= uint256(amount);
// untestable: the final else statement, amount will never be 0
} else if (amount < 0) {
available += uint256(-amount);
}
throttle.lastAvailable = available;
}
/// @param limit {qRTok/hour} The hourly limit
/// @return available {qRTok} Amount currently available for consumption
function currentlyAvailable(Throttle storage throttle, uint256 limit)
internal
view
returns (uint256 available)
{
uint48 delta = uint48(block.timestamp) - throttle.lastTimestamp; // {seconds}
available = throttle.lastAvailable + (limit * delta) / ONE_HOUR;
if (available > limit) available = limit;
}
/// @return limit {qRTok} The hourly limit
function hourlyLimit(Throttle storage throttle, uint256 supply)
internal
view
returns (uint256 limit)
{
Params storage params = throttle.params;
// Calculate hourly limit as: max(params.amtRate, supply.mul(params.pctRate))
limit = (supply * params.pctRate) / FIX_ONE_256; // {qRTok}
if (params.amtRate > limit) limit = params.amtRate;
}
}