@juicedollar/jusd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +356 -0
- package/contracts/Equity.sol +457 -0
- package/contracts/JuiceDollar.sol +363 -0
- package/contracts/Leadrate.sol +79 -0
- package/contracts/MintingHubV2/MintingHub.sol +445 -0
- package/contracts/MintingHubV2/Position.sol +810 -0
- package/contracts/MintingHubV2/PositionFactory.sol +69 -0
- package/contracts/MintingHubV2/PositionRoller.sol +159 -0
- package/contracts/MintingHubV2/interface/IMintingHub.sol +26 -0
- package/contracts/MintingHubV2/interface/IPosition.sol +90 -0
- package/contracts/MintingHubV2/interface/IPositionFactory.sol +20 -0
- package/contracts/Savings.sol +141 -0
- package/contracts/SavingsVaultJUSD.sol +140 -0
- package/contracts/StablecoinBridge.sol +109 -0
- package/contracts/StartUSD.sol +16 -0
- package/contracts/gateway/CoinLendingGateway.sol +223 -0
- package/contracts/gateway/FrontendGateway.sol +224 -0
- package/contracts/gateway/MintingHubGateway.sol +87 -0
- package/contracts/gateway/SavingsGateway.sol +51 -0
- package/contracts/gateway/interface/ICoinLendingGateway.sol +73 -0
- package/contracts/gateway/interface/IFrontendGateway.sol +49 -0
- package/contracts/gateway/interface/IMintingHubGateway.sol +12 -0
- package/contracts/impl/ERC3009.sol +171 -0
- package/contracts/interface/IJuiceDollar.sol +54 -0
- package/contracts/interface/ILeadrate.sol +7 -0
- package/contracts/interface/IReserve.sol +9 -0
- package/contracts/interface/ISavingsJUSD.sol +49 -0
- package/contracts/test/FreakToken.sol +25 -0
- package/contracts/test/Math.sol +339 -0
- package/contracts/test/MockEquity.sol +15 -0
- package/contracts/test/PositionExpirationTest.sol +75 -0
- package/contracts/test/PositionRollingTest.sol +65 -0
- package/contracts/test/TestFlashLoan.sol +84 -0
- package/contracts/test/TestFlashLoanGateway.sol +49 -0
- package/contracts/test/TestMathUtil.sol +40 -0
- package/contracts/test/TestToken.sol +45 -0
- package/contracts/test/TestWcBTC.sol +35 -0
- package/contracts/utils/MathUtil.sol +61 -0
- package/dist/index.d.mts +8761 -0
- package/dist/index.d.ts +8761 -0
- package/dist/index.js +11119 -0
- package/dist/index.mjs +11073 -0
- package/exports/abis/MintingHubV2/PositionFactoryV2.ts +90 -0
- package/exports/abis/MintingHubV2/PositionRoller.ts +183 -0
- package/exports/abis/MintingHubV2/PositionV2.ts +999 -0
- package/exports/abis/core/CoinLendingGateway.ts +427 -0
- package/exports/abis/core/Equity.ts +1286 -0
- package/exports/abis/core/FrontendGateway.ts +906 -0
- package/exports/abis/core/JuiceDollar.ts +1366 -0
- package/exports/abis/core/MintingHubGateway.ts +865 -0
- package/exports/abis/core/SavingsGateway.ts +559 -0
- package/exports/abis/core/SavingsVaultJUSD.ts +920 -0
- package/exports/abis/utils/ERC20.ts +310 -0
- package/exports/abis/utils/ERC20PermitLight.ts +520 -0
- package/exports/abis/utils/Leadrate.ts +175 -0
- package/exports/abis/utils/MintingHubV2.ts +682 -0
- package/exports/abis/utils/Ownable.ts +76 -0
- package/exports/abis/utils/Savings.ts +453 -0
- package/exports/abis/utils/StablecoinBridge.ts +209 -0
- package/exports/abis/utils/StartUSD.ts +315 -0
- package/exports/abis/utils/UniswapV3Pool.ts +638 -0
- package/exports/address.config.ts +48 -0
- package/exports/index.ts +28 -0
- package/package.json +87 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {Equity} from "./Equity.sol";
|
|
5
|
+
import {IJuiceDollar} from "./interface/IJuiceDollar.sol";
|
|
6
|
+
import {IReserve} from "./interface/IReserve.sol";
|
|
7
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
8
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
9
|
+
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
|
10
|
+
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
|
11
|
+
import {ERC3009} from "./impl/ERC3009.sol";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @title JuiceDollar
|
|
15
|
+
* @notice The JuiceDollar (JUSD) is an ERC-20 token that is designed to track the value of the Dollar.
|
|
16
|
+
* It is not upgradable, but open to arbitrary minting plugins. These are automatically accepted if none of the
|
|
17
|
+
* qualified pool shareholders casts a veto, leading to a flexible but conservative governance.
|
|
18
|
+
*/
|
|
19
|
+
contract JuiceDollar is ERC20Permit, ERC3009, IJuiceDollar, ERC165 {
|
|
20
|
+
/**
|
|
21
|
+
* @notice Minimal fee and application period when suggesting a new minter.
|
|
22
|
+
*/
|
|
23
|
+
uint256 public constant MIN_FEE = 1000 * (10 ** 18);
|
|
24
|
+
uint256 public immutable MIN_APPLICATION_PERIOD; // For example: 10 days
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @notice The contract that holds the reserve.
|
|
28
|
+
*/
|
|
29
|
+
IReserve public immutable override reserve;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @notice How much of the reserve belongs to the minters. Everything else belongs to the pool shareholders.
|
|
33
|
+
* Stored with 6 additional digits of accuracy so no rounding is necessary when dealing with parts per
|
|
34
|
+
* million (ppm) in reserve calculations.
|
|
35
|
+
*/
|
|
36
|
+
uint256 private minterReserveE6;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @notice Map of minters to approval time stamps. If the time stamp is in the past, the minter contract is allowed
|
|
40
|
+
* to mint JuiceDollars.
|
|
41
|
+
*/
|
|
42
|
+
mapping(address minter => uint256 validityStart) public minters;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @notice List of positions that are allowed to mint and the minter that registered them.
|
|
46
|
+
*/
|
|
47
|
+
mapping(address position => address registeringMinter) public positions;
|
|
48
|
+
|
|
49
|
+
event MinterApplied(address indexed minter, uint256 applicationPeriod, uint256 applicationFee, string message);
|
|
50
|
+
event MinterDenied(address indexed minter, string message);
|
|
51
|
+
event Loss(address indexed reportingMinter, uint256 amount);
|
|
52
|
+
event Profit(address indexed reportingMinter, uint256 amount);
|
|
53
|
+
event ProfitDistributed(address indexed recipient, uint256 amount);
|
|
54
|
+
|
|
55
|
+
error PeriodTooShort();
|
|
56
|
+
error FeeTooLow();
|
|
57
|
+
error AlreadyRegistered();
|
|
58
|
+
error NotMinter();
|
|
59
|
+
error TooLate();
|
|
60
|
+
|
|
61
|
+
modifier minterOnly() {
|
|
62
|
+
if (!isMinter(msg.sender) && !isMinter(positions[msg.sender])) revert NotMinter();
|
|
63
|
+
_;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @notice Initiates the JuiceDollar with the provided minimum application period for new plugins
|
|
68
|
+
* in seconds, for example 10 days, i.e. 3600*24*10 = 864000
|
|
69
|
+
*/
|
|
70
|
+
constructor(uint256 _minApplicationPeriod) ERC20Permit("Juice Dollar") ERC20("Juice Dollar", "JUSD") {
|
|
71
|
+
MIN_APPLICATION_PERIOD = _minApplicationPeriod;
|
|
72
|
+
reserve = new Equity(this);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function initialize(address _minter, string calldata _message) external {
|
|
76
|
+
require(totalSupply() == 0 && reserve.totalSupply() == 0);
|
|
77
|
+
minters[_minter] = block.timestamp;
|
|
78
|
+
emit MinterApplied(_minter, 0, 0, _message);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @notice Publicly accessible method to suggest a new way of minting JuiceDollar.
|
|
83
|
+
* @dev The caller has to pay an application fee that is irrevocably lost even if the new minter is vetoed.
|
|
84
|
+
* The caller must assume that someone will veto the new minter unless there is broad consensus that the new minter
|
|
85
|
+
* adds value to the JuiceDollar system. Complex proposals should have application periods and applications fees
|
|
86
|
+
* above the minimum. It is assumed that over time, informal ways to coordinate on new minters will emerge. The message
|
|
87
|
+
* parameter might be useful for initiating further communication. Maybe it contains a link to a website describing
|
|
88
|
+
* the proposed minter.
|
|
89
|
+
*
|
|
90
|
+
* @param _minter An address that is given the permission to mint JuiceDollars
|
|
91
|
+
* @param _applicationPeriod The time others have to veto the suggestion, at least MIN_APPLICATION_PERIOD
|
|
92
|
+
* @param _applicationFee The fee paid by the caller, at least MIN_FEE
|
|
93
|
+
* @param _message An optional human readable message to everyone watching this contract
|
|
94
|
+
*/
|
|
95
|
+
function suggestMinter(
|
|
96
|
+
address _minter,
|
|
97
|
+
uint256 _applicationPeriod,
|
|
98
|
+
uint256 _applicationFee,
|
|
99
|
+
string calldata _message
|
|
100
|
+
) external override {
|
|
101
|
+
if (_applicationPeriod < MIN_APPLICATION_PERIOD) revert PeriodTooShort();
|
|
102
|
+
if (_applicationFee < MIN_FEE) revert FeeTooLow();
|
|
103
|
+
if (minters[_minter] != 0) revert AlreadyRegistered();
|
|
104
|
+
_collectProfits(address(this), msg.sender, _applicationFee);
|
|
105
|
+
minters[_minter] = block.timestamp + _applicationPeriod;
|
|
106
|
+
emit MinterApplied(_minter, _applicationPeriod, _applicationFee, _message);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @notice Make the system more user friendly by skipping the allowance in many cases.
|
|
111
|
+
* @dev We trust minters and the positions they have created to mint and burn as they please, so
|
|
112
|
+
* giving them arbitrary allowances does not pose an additional risk.
|
|
113
|
+
*/
|
|
114
|
+
function allowance(address owner, address spender) public view override(IERC20, ERC20) returns (uint256) {
|
|
115
|
+
uint256 explicit = super.allowance(owner, spender);
|
|
116
|
+
if (explicit > 0) {
|
|
117
|
+
return explicit; // don't waste gas checking minter
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (spender == address(reserve)) {
|
|
121
|
+
return type(uint256).max;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
(isMinter(spender) || isMinter(getPositionParent(spender))) &&
|
|
126
|
+
(isMinter(owner) || positions[owner] != address(0) || owner == address(reserve))
|
|
127
|
+
) {
|
|
128
|
+
return type(uint256).max;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @notice The reserve provided by the owners of collateralized positions.
|
|
136
|
+
* @dev The minter reserve can be used to cover losses after the equity holders have been wiped out.
|
|
137
|
+
*/
|
|
138
|
+
function minterReserve() public view returns (uint256) {
|
|
139
|
+
return minterReserveE6 / 1_000_000;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @notice Allows minters to register collateralized debt positions, thereby giving them the ability to mint JuiceDollars.
|
|
144
|
+
* @dev It is assumed that the responsible minter that registers the position ensures that the position can be trusted.
|
|
145
|
+
*/
|
|
146
|
+
function registerPosition(address _position) external override {
|
|
147
|
+
if (!isMinter(msg.sender)) revert NotMinter();
|
|
148
|
+
positions[_position] = msg.sender;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @notice The amount of equity of the JuiceDollar system in JUSD, owned by the holders of Juice Protocol (JUICE).
|
|
153
|
+
* @dev Note that the equity contract technically holds both the minter reserve as well as the equity, so the minter
|
|
154
|
+
* reserve must be subtracted. All fees and other kinds of income are added to the Equity contract and essentially
|
|
155
|
+
* constitute profits attributable to the pool shareholders.
|
|
156
|
+
*/
|
|
157
|
+
function equity() public view returns (uint256) {
|
|
158
|
+
uint256 balance = balanceOf(address(reserve));
|
|
159
|
+
uint256 minReserve = minterReserve();
|
|
160
|
+
if (balance <= minReserve) {
|
|
161
|
+
return 0;
|
|
162
|
+
} else {
|
|
163
|
+
return balance - minReserve;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @notice Qualified pool shareholders can deny minters during the application period.
|
|
169
|
+
* @dev Calling this function is relatively cheap thanks to the deletion of a storage slot.
|
|
170
|
+
*/
|
|
171
|
+
function denyMinter(address _minter, address[] calldata _helpers, string calldata _message) external override {
|
|
172
|
+
if (block.timestamp > minters[_minter]) revert TooLate();
|
|
173
|
+
reserve.checkQualified(msg.sender, _helpers);
|
|
174
|
+
delete minters[_minter];
|
|
175
|
+
emit MinterDenied(_minter, _message);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @notice Mints the provided amount of JUSD to the target address, automatically forwarding
|
|
180
|
+
* the minting fee and the reserve to the right place.
|
|
181
|
+
*/
|
|
182
|
+
function mintWithReserve(address _target, uint256 _amount, uint32 _reservePPM) external override minterOnly {
|
|
183
|
+
uint256 usableMint = (_amount * (1_000_000 - _reservePPM)) / 1_000_000; // rounding down is fine
|
|
184
|
+
_mint(_target, usableMint);
|
|
185
|
+
_mint(address(reserve), _amount - usableMint); // rest goes to equity as reserves or as fees
|
|
186
|
+
minterReserveE6 += _amount * _reservePPM;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function mint(address _target, uint256 _amount) external override minterOnly {
|
|
190
|
+
_mint(_target, _amount);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Anyone is allowed to burn their JUSD.
|
|
195
|
+
*/
|
|
196
|
+
function burn(uint256 _amount) external {
|
|
197
|
+
_burn(msg.sender, _amount);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @notice Burn someone else's JUSD.
|
|
202
|
+
*/
|
|
203
|
+
function burnFrom(address _owner, uint256 _amount) external override minterOnly {
|
|
204
|
+
_spendAllowance(_owner, msg.sender, _amount);
|
|
205
|
+
_burn(_owner, _amount);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @notice Burn the amount without reclaiming the reserve, but freeing it up and thereby essentially donating it to the
|
|
210
|
+
* pool shareholders. This can make sense in combination with 'coverLoss', i.e. when it is the pool shareholders
|
|
211
|
+
* that bear the risk and depending on the outcome they make a profit or a loss.
|
|
212
|
+
*
|
|
213
|
+
* Design rule: Minters calling this method are only allowed to do so for token amounts they previously minted with
|
|
214
|
+
* the same _reservePPM amount.
|
|
215
|
+
*
|
|
216
|
+
* For example, if someone minted 50 JUSD earlier with a 20% reserve requirement (200000 ppm), they got 40 JUSD
|
|
217
|
+
* and paid 10 JUSD into the reserve. Now they want to repay the debt by burning 50 JUSD. When doing so using this
|
|
218
|
+
* method, 50 JUSD get burned and on top of that, 10 JUSD previously assigned to the minter's reserve are
|
|
219
|
+
* reassigned to the pool shareholders.
|
|
220
|
+
*/
|
|
221
|
+
function burnWithoutReserve(uint256 amount, uint32 reservePPM) public override minterOnly {
|
|
222
|
+
_burn(msg.sender, amount);
|
|
223
|
+
|
|
224
|
+
uint256 equityBefore = equity();
|
|
225
|
+
uint256 reserveReduction = amount * reservePPM;
|
|
226
|
+
minterReserveE6 = minterReserveE6 > reserveReduction ? minterReserveE6 - reserveReduction : 0;
|
|
227
|
+
uint256 equityAfter = equity();
|
|
228
|
+
|
|
229
|
+
if (equityAfter > equityBefore) {
|
|
230
|
+
emit Profit(msg.sender, equityAfter - equityBefore);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @notice Burns the target amount taking the tokens to be burned from the payer and the payer's reserve.
|
|
236
|
+
* Only use this method for tokens also minted by the caller with the same reservePPM.
|
|
237
|
+
*
|
|
238
|
+
* Example: the calling contract has previously minted 100 JUSD with a reserve ratio of 20% (i.e. 200000 ppm).
|
|
239
|
+
* To burn half of that again, the minter calls burnFromWithReserve with a target amount of 50 JUSD. Assuming that reserves
|
|
240
|
+
* are only 90% covered, this call will deduct 41 JUSD from the payer's balance and 9 from the reserve, while
|
|
241
|
+
* reducing the minter reserve by 10.
|
|
242
|
+
*/
|
|
243
|
+
function burnFromWithReserve(
|
|
244
|
+
address payer,
|
|
245
|
+
uint256 targetTotalBurnAmount,
|
|
246
|
+
uint32 reservePPM
|
|
247
|
+
) public override minterOnly returns (uint256) {
|
|
248
|
+
uint256 assigned = calculateAssignedReserve(targetTotalBurnAmount, reservePPM);
|
|
249
|
+
_spendAllowance(payer, msg.sender, targetTotalBurnAmount - assigned); // spend amount excluding the reserve
|
|
250
|
+
_burn(address(reserve), assigned); // burn reserve amount from the reserve
|
|
251
|
+
_burn(payer, targetTotalBurnAmount - assigned); // burn remaining amount from the payer
|
|
252
|
+
minterReserveE6 -= targetTotalBurnAmount * reservePPM; // reduce reserve requirements by original ratio
|
|
253
|
+
return assigned;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @notice Calculates the assigned reserve for a given amount and reserve requirement, adjusted for reserve losses.
|
|
258
|
+
* @return `amountExcludingReserve` plus its share of the reserve.
|
|
259
|
+
*/
|
|
260
|
+
function calculateFreedAmount(uint256 amountExcludingReserve, uint32 _reservePPM) public view returns (uint256) {
|
|
261
|
+
uint256 effectiveReservePPM = _effectiveReservePPM(_reservePPM);
|
|
262
|
+
return (1_000_000 * amountExcludingReserve) / (1_000_000 - effectiveReservePPM);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @notice Calculates the reserve attributable to someone who minted the given amount with the given reserve requirement.
|
|
267
|
+
* Under normal circumstances, this is just the reserve requirement multiplied by the amount. However, after a
|
|
268
|
+
* severe loss of capital that burned into the minter's reserve, this can also be less than that.
|
|
269
|
+
*/
|
|
270
|
+
function calculateAssignedReserve(uint256 mintedAmount, uint32 _reservePPM) public view returns (uint256) {
|
|
271
|
+
uint256 effectiveReservePPM = _effectiveReservePPM(_reservePPM);
|
|
272
|
+
return (effectiveReservePPM * mintedAmount) / 1_000_000;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @notice Calculates the reserve ratio adjusted for any reserve shortfall
|
|
277
|
+
* @dev When there's a reserve shortfall (currentReserve < minterReserve), the effective reserve ratio is proportionally reduced.
|
|
278
|
+
* This ensures fair distribution of remaining reserves during repayment.
|
|
279
|
+
* @param reservePPM The nominal reserve ratio in parts per million
|
|
280
|
+
* @return The effective reserve ratio in parts per million, adjusted for any shortfall
|
|
281
|
+
*/
|
|
282
|
+
function _effectiveReservePPM(uint32 reservePPM) internal view returns (uint256) {
|
|
283
|
+
uint256 minterReserve_ = minterReserve();
|
|
284
|
+
uint256 currentReserve = balanceOf(address(reserve));
|
|
285
|
+
return currentReserve < minterReserve_ ? (reservePPM * currentReserve) / minterReserve_ : reservePPM;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @notice Notify the JuiceDollar that a minter lost economic access to some coins. This does not mean that the coins are
|
|
290
|
+
* literally lost. It just means that some JUSD will likely never be repaid and that in order to bring the system
|
|
291
|
+
* back into balance, the lost amount of JUSD must be removed from the reserve instead.
|
|
292
|
+
*
|
|
293
|
+
* For example, if a minter printed 1 million JUSD for a mortgage and the mortgage turned out to be unsound with
|
|
294
|
+
* the house only yielding 800,000 in the subsequent auction, there is a loss of 200,000 that needs to be covered
|
|
295
|
+
* by the reserve.
|
|
296
|
+
*/
|
|
297
|
+
function coverLoss(address source, uint256 _amount) external override minterOnly {
|
|
298
|
+
_withdrawFromReserve(source, _amount);
|
|
299
|
+
emit Loss(source, _amount);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* @notice Distribute profits (e.g., savings interest) from the reserve to recipients.
|
|
304
|
+
*
|
|
305
|
+
* @param recipient The address receiving the payout.
|
|
306
|
+
* @param amount The amount of JUSD to distribute.
|
|
307
|
+
*/
|
|
308
|
+
function distributeProfits(address recipient, uint256 amount) external override minterOnly {
|
|
309
|
+
_withdrawFromReserve(recipient, amount);
|
|
310
|
+
emit ProfitDistributed(recipient, amount);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function collectProfits(address source, uint256 _amount) external override minterOnly {
|
|
314
|
+
_collectProfits(msg.sender, source, _amount);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function _collectProfits(address minter, address source, uint256 _amount) internal {
|
|
318
|
+
_spendAllowance(source, minter, _amount);
|
|
319
|
+
_transfer(source, address(reserve), _amount);
|
|
320
|
+
emit Profit(minter, _amount);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @notice Transfers the specified amount from the reserve if possible; mints the remainder if necessary.
|
|
325
|
+
* @param recipient The address receiving the funds.
|
|
326
|
+
* @param amount The total amount to be paid.
|
|
327
|
+
*/
|
|
328
|
+
function _withdrawFromReserve(address recipient, uint256 amount) internal {
|
|
329
|
+
uint256 reserveLeft = balanceOf(address(reserve));
|
|
330
|
+
if (reserveLeft >= amount) {
|
|
331
|
+
_transfer(address(reserve), recipient, amount);
|
|
332
|
+
} else {
|
|
333
|
+
_transfer(address(reserve), recipient, reserveLeft);
|
|
334
|
+
_mint(recipient, amount - reserveLeft);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @notice Returns true if the address is an approved minter.
|
|
340
|
+
*/
|
|
341
|
+
function isMinter(address _minter) public view override returns (bool) {
|
|
342
|
+
return minters[_minter] != 0 && block.timestamp >= minters[_minter];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @notice Returns the address of the minter that created this position or null if the provided address is unknown.
|
|
347
|
+
*/
|
|
348
|
+
function getPositionParent(address _position) public view override returns (address) {
|
|
349
|
+
return positions[_position];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @dev See {IERC165-supportsInterface}.
|
|
354
|
+
*/
|
|
355
|
+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
|
356
|
+
return
|
|
357
|
+
interfaceId == type(IERC20).interfaceId ||
|
|
358
|
+
interfaceId == type(ERC20Permit).interfaceId ||
|
|
359
|
+
interfaceId == type(ERC3009).interfaceId ||
|
|
360
|
+
interfaceId == type(IJuiceDollar).interfaceId ||
|
|
361
|
+
super.supportsInterface(interfaceId);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IReserve} from "./interface/IReserve.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title Leadrate (attempt at translating the concise German term 'Leitzins')
|
|
8
|
+
*
|
|
9
|
+
* A module that can provide other modules with the leading interest rate for the system.
|
|
10
|
+
*
|
|
11
|
+
**/
|
|
12
|
+
contract Leadrate {
|
|
13
|
+
IReserve public immutable equity;
|
|
14
|
+
|
|
15
|
+
// The following five variables are less than 256 bits, so they should be stored
|
|
16
|
+
// in the same slot, making them cheaper to access together, right?
|
|
17
|
+
|
|
18
|
+
uint24 public currentRatePPM; // 24 bits allows rates of up to ~1670% per year
|
|
19
|
+
uint24 public nextRatePPM;
|
|
20
|
+
uint40 public nextChange;
|
|
21
|
+
|
|
22
|
+
uint40 private anchorTime; // 40 bits for time in seconds spans up to 1000 human generations
|
|
23
|
+
uint64 private ticksAnchor; // in bips * seconds
|
|
24
|
+
|
|
25
|
+
event RateProposed(address who, uint24 nextRate, uint40 nextChange);
|
|
26
|
+
event RateChanged(uint24 newRate);
|
|
27
|
+
|
|
28
|
+
error NoPendingChange();
|
|
29
|
+
error ChangeNotReady();
|
|
30
|
+
|
|
31
|
+
constructor(IReserve equity_, uint24 initialRatePPM) {
|
|
32
|
+
equity = equity_;
|
|
33
|
+
nextRatePPM = initialRatePPM;
|
|
34
|
+
currentRatePPM = initialRatePPM;
|
|
35
|
+
nextChange = uint40(block.timestamp);
|
|
36
|
+
anchorTime = nextChange;
|
|
37
|
+
ticksAnchor = 0;
|
|
38
|
+
emit RateChanged(initialRatePPM); // emit for initialization indexing, if desired
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Proposes a new interest rate that will automatically be applied after seven days.
|
|
43
|
+
* To cancel a proposal, just overwrite it with a new one proposing the current rate.
|
|
44
|
+
*/
|
|
45
|
+
function proposeChange(uint24 newRatePPM_, address[] calldata helpers) external {
|
|
46
|
+
equity.checkQualified(msg.sender, helpers);
|
|
47
|
+
nextRatePPM = newRatePPM_;
|
|
48
|
+
nextChange = uint40(block.timestamp + 7 days);
|
|
49
|
+
emit RateProposed(msg.sender, nextRatePPM, nextChange);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Setting a previously proposed interest rate change into force.
|
|
54
|
+
*/
|
|
55
|
+
function applyChange() external {
|
|
56
|
+
if (currentRatePPM == nextRatePPM) revert NoPendingChange();
|
|
57
|
+
uint40 timeNow = uint40(block.timestamp);
|
|
58
|
+
if (timeNow < nextChange) revert ChangeNotReady();
|
|
59
|
+
ticksAnchor += (timeNow - anchorTime) * currentRatePPM;
|
|
60
|
+
anchorTime = timeNow;
|
|
61
|
+
currentRatePPM = nextRatePPM;
|
|
62
|
+
emit RateChanged(currentRatePPM);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Total accumulated 'interest ticks' since this contract was deployed.
|
|
67
|
+
* One 'tick' is a ppm-second, so one month of 12% annual interest is
|
|
68
|
+
* 120000*30*24*3600 = 311040000000 ticks.
|
|
69
|
+
* Two months of 6% annual interest would result in the same number of
|
|
70
|
+
* ticks. For simplicity, this is linear, so there is no "interest on interest".
|
|
71
|
+
*/
|
|
72
|
+
function currentTicks() public view returns (uint64) {
|
|
73
|
+
return ticks(block.timestamp);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function ticks(uint256 timestamp) public view returns (uint64) {
|
|
77
|
+
return ticksAnchor + (uint64(timestamp) - anchorTime) * currentRatePPM;
|
|
78
|
+
}
|
|
79
|
+
}
|