@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,810 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IMintingHubGateway} from "../gateway/interface/IMintingHubGateway.sol";
|
|
5
|
+
import {IJuiceDollar} from "../interface/IJuiceDollar.sol";
|
|
6
|
+
import {IReserve} from "../interface/IReserve.sol";
|
|
7
|
+
import {MathUtil} from "../utils/MathUtil.sol";
|
|
8
|
+
import {IMintingHub} from "./interface/IMintingHub.sol";
|
|
9
|
+
import {IPosition} from "./interface/IPosition.sol";
|
|
10
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
11
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
12
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @title Position
|
|
16
|
+
* @notice A collateralized minting position.
|
|
17
|
+
*/
|
|
18
|
+
contract Position is Ownable, IPosition, MathUtil {
|
|
19
|
+
/**
|
|
20
|
+
* @notice Note that this contract is intended to be cloned. All clones will share the same values for
|
|
21
|
+
* the constant and immutable fields, but have their own values for the other fields.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @notice The JUSD price per unit of the collateral below which challenges succeed, (36 - collateral.decimals) decimals
|
|
26
|
+
*/
|
|
27
|
+
uint256 public price;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @notice How much has been minted in total. This variable is only used in the parent position.
|
|
31
|
+
*/
|
|
32
|
+
uint256 private totalMinted;
|
|
33
|
+
|
|
34
|
+
uint256 public immutable limit;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @notice Amount of the collateral that is currently under a challenge.
|
|
38
|
+
* Used to figure out whether there are pending challenges.
|
|
39
|
+
*/
|
|
40
|
+
uint256 public challengedAmount;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @notice The price at which the challenge was initiated.
|
|
44
|
+
*/
|
|
45
|
+
uint256 private challengedPrice;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @notice Challenge period in seconds.
|
|
49
|
+
*/
|
|
50
|
+
uint40 public immutable challengePeriod;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @notice Timestamp when minting can start and the position is no longer denied.
|
|
54
|
+
*/
|
|
55
|
+
uint40 public immutable start;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @notice End of the latest cooldown. If this is in the future, minting is suspended.
|
|
59
|
+
*/
|
|
60
|
+
uint40 public cooldown;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @notice Timestamp of the expiration of the position. After expiration, challenges cannot be averted
|
|
64
|
+
* any more. This is also the basis for fee calculations.
|
|
65
|
+
*/
|
|
66
|
+
uint40 public expiration;
|
|
67
|
+
|
|
68
|
+
bool private closed;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @notice The original position to help identify clones.
|
|
72
|
+
*/
|
|
73
|
+
address public immutable original;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @notice Pointer to the minting hub.
|
|
77
|
+
*/
|
|
78
|
+
address public immutable hub;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @notice The JuiceDollar contract.
|
|
82
|
+
*/
|
|
83
|
+
IJuiceDollar public immutable jusd;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @notice The collateral token.
|
|
87
|
+
*/
|
|
88
|
+
IERC20 public immutable override collateral;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @notice Minimum acceptable collateral amount to prevent dust.
|
|
92
|
+
*/
|
|
93
|
+
uint256 public immutable override minimumCollateral;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @notice The interest in parts per million per year that is deducted when minting JUSD.
|
|
97
|
+
*/
|
|
98
|
+
uint24 public immutable riskPremiumPPM;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @notice The locked-in rate (including riskPremiumPPM) for this position.
|
|
102
|
+
*/
|
|
103
|
+
uint24 public fixedAnnualRatePPM;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @notice The reserve contribution in parts per million of the minted amount.
|
|
107
|
+
*/
|
|
108
|
+
uint24 public immutable reserveContribution;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @notice The total principal borrowed.
|
|
112
|
+
*/
|
|
113
|
+
uint256 public principal;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @notice The total outstanding interest.
|
|
117
|
+
*/
|
|
118
|
+
uint256 public interest;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @notice The timestamp of the last interest accrual.
|
|
122
|
+
*/
|
|
123
|
+
uint40 public lastAccrual;
|
|
124
|
+
|
|
125
|
+
event MintingUpdate(uint256 collateral, uint256 price, uint256 principal);
|
|
126
|
+
event PositionDenied(address indexed sender, string message); // emitted if closed by governance
|
|
127
|
+
|
|
128
|
+
error InsufficientCollateral(uint256 needed, uint256 available);
|
|
129
|
+
error TooLate();
|
|
130
|
+
error RepaidTooMuch(uint256 excess);
|
|
131
|
+
error LimitExceeded(uint256 tried, uint256 available);
|
|
132
|
+
error ChallengeTooSmall();
|
|
133
|
+
error Expired(uint40 time, uint40 expiration);
|
|
134
|
+
error Alive();
|
|
135
|
+
error Closed();
|
|
136
|
+
error Hot();
|
|
137
|
+
error Challenged();
|
|
138
|
+
error NotHub();
|
|
139
|
+
error NotOriginal();
|
|
140
|
+
error InvalidExpiration();
|
|
141
|
+
error AlreadyInitialized();
|
|
142
|
+
error PriceTooHigh(uint256 newPrice, uint256 maxPrice);
|
|
143
|
+
|
|
144
|
+
modifier alive() {
|
|
145
|
+
if (block.timestamp >= expiration) revert Expired(uint40(block.timestamp), expiration);
|
|
146
|
+
_;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// requires that the position has always been backed by a minimal amount of collateral
|
|
150
|
+
modifier backed() {
|
|
151
|
+
if (isClosed()) revert Closed();
|
|
152
|
+
_;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
modifier expired() {
|
|
156
|
+
if (block.timestamp < expiration) revert Alive();
|
|
157
|
+
_;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
modifier noCooldown() {
|
|
161
|
+
if (block.timestamp <= cooldown) revert Hot();
|
|
162
|
+
_;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
modifier noChallenge() {
|
|
166
|
+
if (challengedAmount > 0) revert Challenged();
|
|
167
|
+
_;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
modifier onlyHub() {
|
|
171
|
+
if (msg.sender != address(hub)) revert NotHub();
|
|
172
|
+
_;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
modifier ownerOrRoller() {
|
|
176
|
+
if (msg.sender != address(IMintingHub(hub).ROLLER())) _checkOwner();
|
|
177
|
+
_;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @dev See MintingHub.openPosition
|
|
182
|
+
*
|
|
183
|
+
* @param _riskPremiumPPM ppm of minted amount that is added to the applicable minting fee as a risk premium
|
|
184
|
+
*/
|
|
185
|
+
constructor(
|
|
186
|
+
address _owner,
|
|
187
|
+
address _hub,
|
|
188
|
+
address _jusd,
|
|
189
|
+
address _collateral,
|
|
190
|
+
uint256 _minCollateral,
|
|
191
|
+
uint256 _initialLimit,
|
|
192
|
+
uint40 _initPeriod,
|
|
193
|
+
uint40 _duration,
|
|
194
|
+
uint40 _challengePeriod,
|
|
195
|
+
uint24 _riskPremiumPPM,
|
|
196
|
+
uint256 _liqPrice,
|
|
197
|
+
uint24 _reservePPM
|
|
198
|
+
) Ownable(_owner) {
|
|
199
|
+
original = address(this);
|
|
200
|
+
hub = _hub;
|
|
201
|
+
jusd = IJuiceDollar(_jusd);
|
|
202
|
+
collateral = IERC20(_collateral);
|
|
203
|
+
riskPremiumPPM = _riskPremiumPPM;
|
|
204
|
+
reserveContribution = _reservePPM;
|
|
205
|
+
minimumCollateral = _minCollateral;
|
|
206
|
+
challengePeriod = _challengePeriod;
|
|
207
|
+
start = uint40(block.timestamp) + _initPeriod; // at least six hours time to deny the position
|
|
208
|
+
cooldown = start;
|
|
209
|
+
expiration = start + _duration;
|
|
210
|
+
limit = _initialLimit;
|
|
211
|
+
_setPrice(_liqPrice, _initialLimit);
|
|
212
|
+
_fixRateToLeadrate(_riskPremiumPPM);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Initialization method for clones.
|
|
217
|
+
* Can only be called once. Should be called immediately after creating the clone.
|
|
218
|
+
*/
|
|
219
|
+
function initialize(address parent, uint40 _expiration) external onlyHub {
|
|
220
|
+
if (expiration != 0) revert AlreadyInitialized();
|
|
221
|
+
if (_expiration < block.timestamp || _expiration > Position(original).expiration()) revert InvalidExpiration(); // expiration must not be later than original
|
|
222
|
+
expiration = _expiration;
|
|
223
|
+
price = Position(parent).price();
|
|
224
|
+
_fixRateToLeadrate(Position(parent).riskPremiumPPM());
|
|
225
|
+
_transferOwnership(hub);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Cloning a position is only allowed if the position is not challenged, not expired and not in cooldown.
|
|
230
|
+
*/
|
|
231
|
+
function assertCloneable() external noChallenge noCooldown alive backed {}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Notify the original that some amount has been minted.
|
|
235
|
+
*/
|
|
236
|
+
function notifyMint(uint256 mint_) external {
|
|
237
|
+
if (jusd.getPositionParent(msg.sender) != hub) revert NotHub();
|
|
238
|
+
totalMinted += mint_;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function notifyRepaid(uint256 repaid_) external {
|
|
242
|
+
if (jusd.getPositionParent(msg.sender) != hub) revert NotHub();
|
|
243
|
+
totalMinted -= repaid_;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Should only be called on the original position.
|
|
248
|
+
* Better use 'availableForMinting'.
|
|
249
|
+
*/
|
|
250
|
+
function availableForClones() external view returns (uint256) {
|
|
251
|
+
// reserve capacity for the original to the extent the owner provided collateral
|
|
252
|
+
uint256 potential = (_collateralBalance() * price) / ONE_DEC18;
|
|
253
|
+
uint256 unusedPotential = principal > potential ? 0 : potential - principal;
|
|
254
|
+
if (totalMinted + unusedPotential >= limit) {
|
|
255
|
+
return 0;
|
|
256
|
+
} else {
|
|
257
|
+
return limit - totalMinted - unusedPotential;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The amount available for minting in this position family.
|
|
263
|
+
*
|
|
264
|
+
* Does not check if positions are challenged, closed, or under cooldown.
|
|
265
|
+
*/
|
|
266
|
+
function availableForMinting() public view returns (uint256) {
|
|
267
|
+
if (address(this) == original) {
|
|
268
|
+
return limit - totalMinted;
|
|
269
|
+
} else {
|
|
270
|
+
return Position(original).availableForClones();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @notice Qualified pool share holders can call this method to immediately expire a freshly proposed position.
|
|
276
|
+
*/
|
|
277
|
+
function deny(address[] calldata helpers, string calldata message) external {
|
|
278
|
+
if (block.timestamp >= start) revert TooLate();
|
|
279
|
+
IReserve(jusd.reserve()).checkQualified(msg.sender, helpers);
|
|
280
|
+
_close();
|
|
281
|
+
emit PositionDenied(msg.sender, message);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Closes the position by putting it into eternal cooldown.
|
|
286
|
+
* This allows the users to still withdraw the collateral that is left, but never to mint again.
|
|
287
|
+
*/
|
|
288
|
+
function _close() internal {
|
|
289
|
+
closed = true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function isClosed() public view returns (bool) {
|
|
293
|
+
return closed;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @notice This is how much the minter can actually use when minting JUSD, with the rest being assigned
|
|
298
|
+
* to the minter reserve.
|
|
299
|
+
*/
|
|
300
|
+
function getUsableMint(uint256 mintAmount) public view returns (uint256) {
|
|
301
|
+
return (mintAmount * (1_000_000 - reserveContribution)) / 1_000_000;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Returns the corresponding mint amount (disregarding the limit).
|
|
306
|
+
*/
|
|
307
|
+
function getMintAmount(uint256 usableMint) external view returns (uint256) {
|
|
308
|
+
return _ceilDivPPM(usableMint, reserveContribution);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @notice "All in one" function to adjust the principal, the collateral amount,
|
|
313
|
+
* and the price in one transaction.
|
|
314
|
+
*/
|
|
315
|
+
function adjust(uint256 newPrincipal, uint256 newCollateral, uint256 newPrice) external onlyOwner {
|
|
316
|
+
uint256 colbal = _collateralBalance();
|
|
317
|
+
if (newCollateral > colbal) {
|
|
318
|
+
collateral.transferFrom(msg.sender, address(this), newCollateral - colbal);
|
|
319
|
+
}
|
|
320
|
+
// Must be called after collateral deposit, but before withdrawal
|
|
321
|
+
if (newPrincipal < principal) {
|
|
322
|
+
uint256 debt = principal + _accrueInterest();
|
|
323
|
+
_payDownDebt(debt - newPrincipal);
|
|
324
|
+
}
|
|
325
|
+
if (newCollateral < colbal) {
|
|
326
|
+
_withdrawCollateral(msg.sender, colbal - newCollateral);
|
|
327
|
+
}
|
|
328
|
+
// Must be called after collateral withdrawal
|
|
329
|
+
if (newPrincipal > principal) {
|
|
330
|
+
_mint(msg.sender, newPrincipal - principal, newCollateral);
|
|
331
|
+
}
|
|
332
|
+
if (newPrice != price) {
|
|
333
|
+
_adjustPrice(newPrice);
|
|
334
|
+
}
|
|
335
|
+
emit MintingUpdate(newCollateral, newPrice, newPrincipal);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @notice Allows the position owner to adjust the liquidation price as long as there is no pending challenge.
|
|
340
|
+
* Lowering the liquidation price can be done with immediate effect, given that there is enough collateral.
|
|
341
|
+
* Increasing the liquidation price triggers a cooldown period of 3 days, during which minting is suspended.
|
|
342
|
+
*/
|
|
343
|
+
function adjustPrice(uint256 newPrice) public onlyOwner {
|
|
344
|
+
_adjustPrice(newPrice);
|
|
345
|
+
emit MintingUpdate(_collateralBalance(), price, principal);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function _adjustPrice(uint256 newPrice) internal noChallenge alive backed noCooldown {
|
|
349
|
+
if (newPrice > price) {
|
|
350
|
+
_restrictMinting(3 days);
|
|
351
|
+
} else {
|
|
352
|
+
_checkCollateral(_collateralBalance(), newPrice);
|
|
353
|
+
}
|
|
354
|
+
_setPrice(newPrice, principal + availableForMinting());
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function _setPrice(uint256 newPrice, uint256 bounds) internal {
|
|
358
|
+
uint256 colBalance = _collateralBalance();
|
|
359
|
+
if (block.timestamp >= start && newPrice > 2 * price) {
|
|
360
|
+
revert PriceTooHigh(newPrice, 2 * price);
|
|
361
|
+
}
|
|
362
|
+
if (newPrice * colBalance > bounds * ONE_DEC18) {
|
|
363
|
+
revert PriceTooHigh(newPrice, (bounds * ONE_DEC18) / colBalance);
|
|
364
|
+
}
|
|
365
|
+
price = newPrice;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function _collateralBalance() internal view returns (uint256) {
|
|
369
|
+
return IERC20(collateral).balanceOf(address(this));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* @notice Mint JUSD as long as there is no open challenge, the position is not subject to a cooldown,
|
|
374
|
+
* and there is sufficient collateral.
|
|
375
|
+
*/
|
|
376
|
+
function mint(address target, uint256 amount) public ownerOrRoller {
|
|
377
|
+
uint256 collateralBalance = _collateralBalance();
|
|
378
|
+
_mint(target, amount, collateralBalance);
|
|
379
|
+
emit MintingUpdate(collateralBalance, price, principal);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @notice Returns the virtual price of the collateral in JUSD.
|
|
384
|
+
*/
|
|
385
|
+
function virtualPrice() public view returns (uint256) {
|
|
386
|
+
return _virtualPrice(_collateralBalance(), price);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* @notice Computes the virtual price of the collateral in JUSD, which is the minimum collateral
|
|
391
|
+
* price required to cover the entire debt with interest overcollateralization, lower bounded by the floor price.
|
|
392
|
+
* Returns the challenged price if a challenge is active.
|
|
393
|
+
* @param colBalance The collateral balance of the position.
|
|
394
|
+
* @param floorPrice The minimum price of the collateral in JUSD.
|
|
395
|
+
*/
|
|
396
|
+
function _virtualPrice(uint256 colBalance, uint256 floorPrice) internal view returns (uint256) {
|
|
397
|
+
if (challengedAmount > 0) return challengedPrice;
|
|
398
|
+
if (colBalance == 0) return floorPrice;
|
|
399
|
+
|
|
400
|
+
uint256 virtPrice = (_getCollateralRequirement() * ONE_DEC18) / colBalance;
|
|
401
|
+
return virtPrice < floorPrice ? floorPrice: virtPrice;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* @notice Fixes the annual rate to the current leadrate plus the risk premium.
|
|
406
|
+
* This re-prices the entire position based on the current leadrate.
|
|
407
|
+
*/
|
|
408
|
+
function _fixRateToLeadrate(uint24 _riskPremiumPPM) internal {
|
|
409
|
+
fixedAnnualRatePPM = IMintingHub(hub).RATE().currentRatePPM() + _riskPremiumPPM;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @notice Accrues interest on the principal amount since the last accrual time.
|
|
414
|
+
* @return newInterest The total outstanding interest to be paid.
|
|
415
|
+
*/
|
|
416
|
+
function _accrueInterest() internal returns (uint256 newInterest) {
|
|
417
|
+
newInterest = _calculateInterest();
|
|
418
|
+
|
|
419
|
+
if (newInterest > interest) {
|
|
420
|
+
interest = newInterest;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
lastAccrual = uint40(block.timestamp);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* @notice Computes the total outstanding interest, including newly accrued interest.
|
|
428
|
+
* @dev This function calculates interest accumulated since the last accrual based on
|
|
429
|
+
* the usable principal (principal minus reserve), the annual interest rate, and the elapsed time.
|
|
430
|
+
* Interest is only charged on the amount the user actually received (usable mint).
|
|
431
|
+
* The newly accrued interest is added to the current outstanding interest.
|
|
432
|
+
* @return newInterest The total outstanding interest, including newly accrued interest.
|
|
433
|
+
*/
|
|
434
|
+
function _calculateInterest() internal view returns (uint256 newInterest) {
|
|
435
|
+
uint256 timestamp = block.timestamp;
|
|
436
|
+
newInterest = interest;
|
|
437
|
+
|
|
438
|
+
if (timestamp > lastAccrual && principal > 0) {
|
|
439
|
+
uint256 delta = timestamp - lastAccrual;
|
|
440
|
+
// Interest is calculated only on the usable principal (what user actually received).
|
|
441
|
+
// Single-division optimization for gas efficiency and precision. Equivalent formula:
|
|
442
|
+
// uint256 usablePrincipal = (principal * (1_000_000 - reserveContribution)) / 1_000_000;
|
|
443
|
+
// newInterest += (usablePrincipal * fixedAnnualRatePPM * delta) / (365 days * 1_000_000);
|
|
444
|
+
newInterest += (principal * (1_000_000 - reserveContribution) * fixedAnnualRatePPM * delta)
|
|
445
|
+
/ (365 days * 1_000_000 * 1_000_000);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return newInterest;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* @notice Calculates the current debt (principal + accrued interest)
|
|
453
|
+
* @return Total debt without interest overcollateralization
|
|
454
|
+
*/
|
|
455
|
+
function _getDebt() internal view returns (uint256) {
|
|
456
|
+
return principal + _calculateInterest();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* @notice Calculates total debt accounting for interest overcollateralization
|
|
461
|
+
* @return Total debt including overcollateralized interest
|
|
462
|
+
*/
|
|
463
|
+
function _getCollateralRequirement() internal view returns (uint256) {
|
|
464
|
+
return principal + _ceilDivPPM(_calculateInterest(), reserveContribution);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* @notice Public function to calculate current debt
|
|
469
|
+
* @return The total current debt (principal + current accrued interest)
|
|
470
|
+
*/
|
|
471
|
+
function getDebt() public view returns (uint256) {
|
|
472
|
+
return _getDebt();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* @notice Public function to calculate current debt with overcollateralized interest
|
|
477
|
+
* @return The total debt including overcollateralized interest
|
|
478
|
+
*/
|
|
479
|
+
function getCollateralRequirement() public view returns (uint256) {
|
|
480
|
+
return _getCollateralRequirement();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* @notice Public function to get the current outstanding interest
|
|
485
|
+
*/
|
|
486
|
+
function getInterest() public view returns (uint256) {
|
|
487
|
+
return _calculateInterest();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function _mint(address target, uint256 amount, uint256 collateral_) internal noChallenge noCooldown alive backed {
|
|
491
|
+
if (amount > availableForMinting()) revert LimitExceeded(amount, availableForMinting());
|
|
492
|
+
|
|
493
|
+
_accrueInterest(); // accrue interest
|
|
494
|
+
_fixRateToLeadrate(riskPremiumPPM); // sync interest rate with leadrate
|
|
495
|
+
|
|
496
|
+
Position(original).notifyMint(amount);
|
|
497
|
+
jusd.mintWithReserve(target, amount, reserveContribution);
|
|
498
|
+
|
|
499
|
+
principal += amount;
|
|
500
|
+
_checkCollateral(collateral_, price);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function _restrictMinting(uint40 period) internal {
|
|
504
|
+
uint40 horizon = uint40(block.timestamp) + period;
|
|
505
|
+
if (horizon > cooldown) {
|
|
506
|
+
cooldown = horizon;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* @notice Repays a specified amount of debt from `msg.sender`, prioritizing accrued interest first and then principal.
|
|
512
|
+
* @dev This method integrates the logic of paying accrued interest before principal, as introduced in the continuous
|
|
513
|
+
* interest accrual model. Any interest repaid is collected as profit, and principal repayment uses `burnFromWithReserve`.
|
|
514
|
+
*
|
|
515
|
+
* Unlike previous implementations, this function delegates the actual repayment steps to `_payDownDebt`, ensuring
|
|
516
|
+
* a clean separation of logic. As a result:
|
|
517
|
+
* - Any surplus `amount` beyond what is needed to pay all outstanding interest and principal is never withdrawn
|
|
518
|
+
* from `msg.sender`’s account (no leftover handling required).
|
|
519
|
+
* - The function can be called while there are challenges, though in that scenario, collateral withdrawals remain
|
|
520
|
+
* blocked until all challenges are resolved.
|
|
521
|
+
*
|
|
522
|
+
* To fully close the position (bring `debt` to 0), the amount required generally follows the formula:
|
|
523
|
+
* `debt = principal + interest`. Under normal conditions, this simplifies to:
|
|
524
|
+
* `amount = (principal * (1000000 - reservePPM)) / 1000000 + interest`.
|
|
525
|
+
*
|
|
526
|
+
* For example, if `principal` is 40, `interest` is 10, and `reservePPM` is 200000, repaying 42 JUSD
|
|
527
|
+
* is required to fully close the position.
|
|
528
|
+
*
|
|
529
|
+
* @param amount The maximum amount of JUSD that `msg.sender` is willing to repay.
|
|
530
|
+
* @return used The actual amount of JUSD used for interest and principal repayment.
|
|
531
|
+
*
|
|
532
|
+
* Emits a {MintingUpdate} event.
|
|
533
|
+
*/
|
|
534
|
+
function repay(uint256 amount) public returns (uint256) {
|
|
535
|
+
uint256 used = _payDownDebt(amount);
|
|
536
|
+
emit MintingUpdate(_collateralBalance(), price, principal);
|
|
537
|
+
return used;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function repayFull() external returns (uint256) {
|
|
541
|
+
return repay(principal + _accrueInterest());
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* @notice Updates oustanding principal and notifies the original position that a portion of the total
|
|
546
|
+
* minted has been repaid.
|
|
547
|
+
*/
|
|
548
|
+
function _notifyRepaid(uint256 amount) internal {
|
|
549
|
+
if (amount > principal) revert RepaidTooMuch(amount - principal);
|
|
550
|
+
Position(original).notifyRepaid(amount);
|
|
551
|
+
principal -= amount;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* @notice Updates outstanding interest and notifies the minting hub gateway that interest has been paid.
|
|
556
|
+
*/
|
|
557
|
+
function _notifyInterestPaid(uint256 amount) internal {
|
|
558
|
+
if (amount > interest) revert RepaidTooMuch(amount - interest);
|
|
559
|
+
if (IERC165(hub).supportsInterface(type(IMintingHubGateway).interfaceId)) {
|
|
560
|
+
IMintingHubGateway(hub).notifyInterestPaid(amount);
|
|
561
|
+
}
|
|
562
|
+
interest -= amount;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* @notice Forcefully sells some of the collateral after the position has expired, using the given buyer as the source of proceeds.
|
|
567
|
+
* @dev
|
|
568
|
+
* - Can only be called by the minting hub once the position is expired.
|
|
569
|
+
* - Requires that there are no open challenges, ensuring that a forced sale is not used to circumvent the challenge process.
|
|
570
|
+
* - The proceeds from the sale are first used to repay any accrued interest (treated as profit, collected via `collectProfits`),
|
|
571
|
+
* and then the principal (via `burnFromWithReserve`). This ensures correct accounting, where interest is always realized as profit before principal is returned.
|
|
572
|
+
* - If all debt is fully repaid and there are surplus proceeds, these are transferred to the position owner.
|
|
573
|
+
* - If there is a shortfall (not enough proceeds to fully repay the debt) and no remaining collateral, the system covers the loss.
|
|
574
|
+
*
|
|
575
|
+
* Do not allow a forced sale as long as there is an open challenge. Otherwise, a forced sale by the owner
|
|
576
|
+
* himself could remove any incentive to launch challenges shortly before the expiration. (CS-ZCHF2-001)
|
|
577
|
+
*
|
|
578
|
+
* @param buyer The address buying the collateral. This address provides `proceeds` in JUSD to repay the outstanding debt.
|
|
579
|
+
* @param colAmount The amount of collateral to be forcibly sold and transferred to the `buyer`.
|
|
580
|
+
* @param proceeds The amount of JUSD proceeds provided by the `buyer` to repay the outstanding debt.
|
|
581
|
+
*
|
|
582
|
+
* Emits a {MintingUpdate} event indicating the updated collateral balance, price, and debt after the forced sale.
|
|
583
|
+
*/
|
|
584
|
+
function forceSale(address buyer, uint256 colAmount, uint256 proceeds) external onlyHub expired noChallenge {
|
|
585
|
+
uint256 debt = principal + _accrueInterest();
|
|
586
|
+
uint256 remainingCollateral = _sendCollateral(buyer, colAmount); // Send collateral to buyer
|
|
587
|
+
|
|
588
|
+
// No debt, everything goes to owner if proceeds > 0
|
|
589
|
+
if (debt == 0) {
|
|
590
|
+
if (proceeds > 0) {
|
|
591
|
+
jusd.transferFrom(buyer, owner(), proceeds);
|
|
592
|
+
}
|
|
593
|
+
emit MintingUpdate(_collateralBalance(), price, principal);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Note: A postcondition of _repayPrincipalNet is `principal + interest > 0 => proceeds == 0` (see assert below).
|
|
598
|
+
proceeds = _repayInterest(buyer, proceeds);
|
|
599
|
+
proceeds = _repayPrincipalNet(buyer, proceeds);
|
|
600
|
+
|
|
601
|
+
// If remaining collateral is 0 and `principal + interest` > 0, cover the shortfall with the system.
|
|
602
|
+
if (remainingCollateral == 0 && principal + interest > 0) {
|
|
603
|
+
assert(proceeds == 0);
|
|
604
|
+
jusd.coverLoss(address(this), principal + interest);
|
|
605
|
+
if (interest > 0) {
|
|
606
|
+
jusd.collectProfits(address(this), interest);
|
|
607
|
+
_notifyInterestPaid(interest);
|
|
608
|
+
}
|
|
609
|
+
jusd.burnWithoutReserve(principal, reserveContribution);
|
|
610
|
+
_notifyRepaid(principal);
|
|
611
|
+
} else if (proceeds > 0) {
|
|
612
|
+
// All debt paid, leftover proceeds is profit for owner
|
|
613
|
+
jusd.transferFrom(buyer, owner(), proceeds);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
emit MintingUpdate(_collateralBalance(), price, principal);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* @notice Withdraw any ERC20 token that might have ended up on this address.
|
|
621
|
+
* Withdrawing collateral is subject to the same restrictions as withdrawCollateral(...).
|
|
622
|
+
*/
|
|
623
|
+
function withdraw(address token, address target, uint256 amount) external onlyOwner {
|
|
624
|
+
if (token == address(collateral)) {
|
|
625
|
+
withdrawCollateral(target, amount);
|
|
626
|
+
} else {
|
|
627
|
+
uint256 balance = _collateralBalance();
|
|
628
|
+
IERC20(token).transfer(target, amount);
|
|
629
|
+
require(balance == _collateralBalance()); // guard against double-entry-point tokens
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* @notice Withdraw collateral from the position up to the extent that it is still well collateralized afterwards.
|
|
635
|
+
* Not possible as long as there is an open challenge or the contract is subject to a cooldown.
|
|
636
|
+
*
|
|
637
|
+
* Withdrawing collateral below the minimum collateral amount formally closes the position.
|
|
638
|
+
*/
|
|
639
|
+
function withdrawCollateral(address target, uint256 amount) public ownerOrRoller {
|
|
640
|
+
uint256 balance = _withdrawCollateral(target, amount);
|
|
641
|
+
emit MintingUpdate(balance, price, principal);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function _withdrawCollateral(address target, uint256 amount) internal noCooldown noChallenge returns (uint256) {
|
|
645
|
+
uint256 balance = _sendCollateral(target, amount);
|
|
646
|
+
_checkCollateral(balance, price);
|
|
647
|
+
return balance;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* @notice Transfer the challenged collateral to the bidder. Only callable by minting hub.
|
|
652
|
+
*/
|
|
653
|
+
function transferChallengedCollateral(address target, uint256 amount) external onlyHub {
|
|
654
|
+
uint256 newBalance = _sendCollateral(target, amount);
|
|
655
|
+
emit MintingUpdate(newBalance, price, principal);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function _sendCollateral(address target, uint256 amount) internal returns (uint256) {
|
|
659
|
+
// Some weird tokens fail when trying to transfer 0 amounts
|
|
660
|
+
if (amount > 0) {
|
|
661
|
+
IERC20(collateral).transfer(target, amount);
|
|
662
|
+
}
|
|
663
|
+
uint256 balance = _collateralBalance();
|
|
664
|
+
if (balance < minimumCollateral) {
|
|
665
|
+
_close();
|
|
666
|
+
}
|
|
667
|
+
return balance;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* @notice This invariant must always hold and must always be checked when any of the three
|
|
672
|
+
* variables change in an adverse way. Ensures that the position overcollateralizes interest
|
|
673
|
+
* by the same percentage as the reserve contribution.
|
|
674
|
+
*/
|
|
675
|
+
function _checkCollateral(uint256 collateralReserve, uint256 atPrice) internal view {
|
|
676
|
+
uint256 relevantCollateral = collateralReserve < minimumCollateral ? 0 : collateralReserve;
|
|
677
|
+
uint256 collateralRequirement = _getCollateralRequirement();
|
|
678
|
+
|
|
679
|
+
if (relevantCollateral * atPrice < collateralRequirement * ONE_DEC18) {
|
|
680
|
+
revert InsufficientCollateral(relevantCollateral * atPrice, collateralRequirement * ONE_DEC18);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* @notice Repays a specified amount of debt from `msg.sender`, prioritizing accrued interest first and then principal.
|
|
686
|
+
* @return The actual amount of JUSD used for interest and principal repayment.
|
|
687
|
+
*/
|
|
688
|
+
function _payDownDebt(uint256 amount) internal returns (uint256) {
|
|
689
|
+
_accrueInterest();
|
|
690
|
+
if (amount == 0) return 0;
|
|
691
|
+
|
|
692
|
+
uint256 remaining = amount;
|
|
693
|
+
remaining = _repayInterest(msg.sender, remaining); // Repay interest
|
|
694
|
+
remaining = _repayPrincipal(msg.sender, remaining); // Repay principal
|
|
695
|
+
|
|
696
|
+
return amount - remaining;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* @notice Repays a specified amount of interest from `msg.sender`.
|
|
701
|
+
* @dev Assumes that _accrueInterest has been called before this function.
|
|
702
|
+
* @return `amount` remaining after interest repayment.
|
|
703
|
+
*/
|
|
704
|
+
function _repayInterest(address payer, uint256 amount) internal returns (uint256) {
|
|
705
|
+
uint256 repayment = (interest > amount) ? amount : interest;
|
|
706
|
+
if (repayment > 0) {
|
|
707
|
+
jusd.collectProfits(payer, repayment);
|
|
708
|
+
_notifyInterestPaid(repayment);
|
|
709
|
+
return amount - repayment;
|
|
710
|
+
}
|
|
711
|
+
return amount;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* @notice Repays a specified amount of principal from `msg.sender`.
|
|
716
|
+
* @param payer The address of the entity repaying the debt.
|
|
717
|
+
* @param amount The repayment amount, including the reserve portion.
|
|
718
|
+
* @return amount remaining after principal repayment
|
|
719
|
+
*/
|
|
720
|
+
function _repayPrincipal(address payer, uint256 amount) internal returns (uint256) {
|
|
721
|
+
uint256 repayment = (principal > amount) ? amount : principal;
|
|
722
|
+
if (repayment > 0) {
|
|
723
|
+
uint256 returnedReserve = jusd.burnFromWithReserve(payer, repayment, reserveContribution);
|
|
724
|
+
_notifyRepaid(repayment);
|
|
725
|
+
return amount - (repayment - returnedReserve);
|
|
726
|
+
}
|
|
727
|
+
return amount;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* @notice Repays principal from `payer` using the net repayment amount (excluding reserves).
|
|
732
|
+
* To repay an exact amount including reserves, use `_repayPrincipal(address payer, uint256 amount)`.
|
|
733
|
+
*
|
|
734
|
+
* @param payer The address of the entity repaying the debt.
|
|
735
|
+
* @param amount The repayment amount, excluding the reserve portion, i.e. the net amount.
|
|
736
|
+
* @return amount remaining after principal repayment.
|
|
737
|
+
*/
|
|
738
|
+
function _repayPrincipalNet(address payer, uint256 amount) internal returns (uint256) {
|
|
739
|
+
uint256 availableReserve = jusd.calculateAssignedReserve(principal, reserveContribution);
|
|
740
|
+
uint256 maxRepayment = principal - availableReserve;
|
|
741
|
+
uint256 repayment = amount > maxRepayment ? maxRepayment : amount;
|
|
742
|
+
if (repayment > 0) {
|
|
743
|
+
uint256 freedAmount = jusd.calculateFreedAmount(repayment, reserveContribution);
|
|
744
|
+
uint256 returnedReserve = jusd.burnFromWithReserve(payer, freedAmount, reserveContribution);
|
|
745
|
+
assert(returnedReserve == freedAmount - repayment);
|
|
746
|
+
_notifyRepaid(freedAmount);
|
|
747
|
+
return amount - repayment;
|
|
748
|
+
}
|
|
749
|
+
return amount;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* @notice Returns the liquidation price and the durations for phase1 and phase2 of the challenge.
|
|
754
|
+
* Both phases are usually of equal duration, but near expiration, phase one is adjusted such that
|
|
755
|
+
* it cannot last beyond the expiration date of the position.
|
|
756
|
+
*/
|
|
757
|
+
function challengeData() external view returns (uint256 liqPrice, uint40 phase) {
|
|
758
|
+
return (_virtualPrice(_collateralBalance(), price), challengePeriod);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function notifyChallengeStarted(uint256 size, uint256 _price) external onlyHub alive {
|
|
762
|
+
// Require minimum size. Collateral balance can be below minimum if it was partially challenged before.
|
|
763
|
+
if (size < minimumCollateral && size < _collateralBalance()) revert ChallengeTooSmall();
|
|
764
|
+
if (size == 0) revert ChallengeTooSmall();
|
|
765
|
+
|
|
766
|
+
if (challengedAmount == 0) challengedPrice = _price;
|
|
767
|
+
challengedAmount += size;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* @param size amount of collateral challenged (dec18)
|
|
772
|
+
*/
|
|
773
|
+
function notifyChallengeAverted(uint256 size) external onlyHub {
|
|
774
|
+
challengedAmount -= size;
|
|
775
|
+
|
|
776
|
+
// Don't allow minter to close the position immediately so challenge can be repeated before
|
|
777
|
+
// the owner has a chance to mint more on an undercollateralized position
|
|
778
|
+
_restrictMinting(1 days);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* @notice Notifies the position that a challenge was successful.
|
|
783
|
+
* Everything else is assumed to be handled by the hub.
|
|
784
|
+
*
|
|
785
|
+
* @param _size amount of the collateral bid for
|
|
786
|
+
* @return (position owner, effective challenge size in JUSD, amount of principal to repay, amount of interest to pay, reserve ppm)
|
|
787
|
+
*/
|
|
788
|
+
function notifyChallengeSucceeded(
|
|
789
|
+
uint256 _size
|
|
790
|
+
) external onlyHub returns (address, uint256, uint256, uint256, uint32) {
|
|
791
|
+
_accrueInterest();
|
|
792
|
+
|
|
793
|
+
challengedAmount -= _size;
|
|
794
|
+
uint256 colBal = _collateralBalance();
|
|
795
|
+
if (colBal < _size) {
|
|
796
|
+
_size = colBal;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Determine how much of the debt must be repaid based on challenged collateral
|
|
800
|
+
uint256 interestToPay = (colBal == 0) ? 0 : (interest * _size) / colBal;
|
|
801
|
+
uint256 principalToPay = (colBal == 0) ? 0 : (principal * _size) / colBal;
|
|
802
|
+
_notifyInterestPaid(interestToPay);
|
|
803
|
+
_notifyRepaid(principalToPay);
|
|
804
|
+
|
|
805
|
+
// Give time for additional challenges before the owner can mint again.
|
|
806
|
+
_restrictMinting(3 days);
|
|
807
|
+
|
|
808
|
+
return (owner(), _size, principalToPay, interestToPay, reserveContribution);
|
|
809
|
+
}
|
|
810
|
+
}
|