@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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +356 -0
  3. package/contracts/Equity.sol +457 -0
  4. package/contracts/JuiceDollar.sol +363 -0
  5. package/contracts/Leadrate.sol +79 -0
  6. package/contracts/MintingHubV2/MintingHub.sol +445 -0
  7. package/contracts/MintingHubV2/Position.sol +810 -0
  8. package/contracts/MintingHubV2/PositionFactory.sol +69 -0
  9. package/contracts/MintingHubV2/PositionRoller.sol +159 -0
  10. package/contracts/MintingHubV2/interface/IMintingHub.sol +26 -0
  11. package/contracts/MintingHubV2/interface/IPosition.sol +90 -0
  12. package/contracts/MintingHubV2/interface/IPositionFactory.sol +20 -0
  13. package/contracts/Savings.sol +141 -0
  14. package/contracts/SavingsVaultJUSD.sol +140 -0
  15. package/contracts/StablecoinBridge.sol +109 -0
  16. package/contracts/StartUSD.sol +16 -0
  17. package/contracts/gateway/CoinLendingGateway.sol +223 -0
  18. package/contracts/gateway/FrontendGateway.sol +224 -0
  19. package/contracts/gateway/MintingHubGateway.sol +87 -0
  20. package/contracts/gateway/SavingsGateway.sol +51 -0
  21. package/contracts/gateway/interface/ICoinLendingGateway.sol +73 -0
  22. package/contracts/gateway/interface/IFrontendGateway.sol +49 -0
  23. package/contracts/gateway/interface/IMintingHubGateway.sol +12 -0
  24. package/contracts/impl/ERC3009.sol +171 -0
  25. package/contracts/interface/IJuiceDollar.sol +54 -0
  26. package/contracts/interface/ILeadrate.sol +7 -0
  27. package/contracts/interface/IReserve.sol +9 -0
  28. package/contracts/interface/ISavingsJUSD.sol +49 -0
  29. package/contracts/test/FreakToken.sol +25 -0
  30. package/contracts/test/Math.sol +339 -0
  31. package/contracts/test/MockEquity.sol +15 -0
  32. package/contracts/test/PositionExpirationTest.sol +75 -0
  33. package/contracts/test/PositionRollingTest.sol +65 -0
  34. package/contracts/test/TestFlashLoan.sol +84 -0
  35. package/contracts/test/TestFlashLoanGateway.sol +49 -0
  36. package/contracts/test/TestMathUtil.sol +40 -0
  37. package/contracts/test/TestToken.sol +45 -0
  38. package/contracts/test/TestWcBTC.sol +35 -0
  39. package/contracts/utils/MathUtil.sol +61 -0
  40. package/dist/index.d.mts +8761 -0
  41. package/dist/index.d.ts +8761 -0
  42. package/dist/index.js +11119 -0
  43. package/dist/index.mjs +11073 -0
  44. package/exports/abis/MintingHubV2/PositionFactoryV2.ts +90 -0
  45. package/exports/abis/MintingHubV2/PositionRoller.ts +183 -0
  46. package/exports/abis/MintingHubV2/PositionV2.ts +999 -0
  47. package/exports/abis/core/CoinLendingGateway.ts +427 -0
  48. package/exports/abis/core/Equity.ts +1286 -0
  49. package/exports/abis/core/FrontendGateway.ts +906 -0
  50. package/exports/abis/core/JuiceDollar.ts +1366 -0
  51. package/exports/abis/core/MintingHubGateway.ts +865 -0
  52. package/exports/abis/core/SavingsGateway.ts +559 -0
  53. package/exports/abis/core/SavingsVaultJUSD.ts +920 -0
  54. package/exports/abis/utils/ERC20.ts +310 -0
  55. package/exports/abis/utils/ERC20PermitLight.ts +520 -0
  56. package/exports/abis/utils/Leadrate.ts +175 -0
  57. package/exports/abis/utils/MintingHubV2.ts +682 -0
  58. package/exports/abis/utils/Ownable.ts +76 -0
  59. package/exports/abis/utils/Savings.ts +453 -0
  60. package/exports/abis/utils/StablecoinBridge.ts +209 -0
  61. package/exports/abis/utils/StartUSD.ts +315 -0
  62. package/exports/abis/utils/UniswapV3Pool.ts +638 -0
  63. package/exports/address.config.ts +48 -0
  64. package/exports/index.ts +28 -0
  65. package/package.json +87 -0
@@ -0,0 +1,457 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+
5
+ import {JuiceDollar} from "./JuiceDollar.sol";
6
+ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
7
+ import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
8
+ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
9
+ import {ERC3009} from "./impl/ERC3009.sol";
10
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11
+ import {IReserve} from "./interface/IReserve.sol";
12
+ import {MathUtil} from "./utils/MathUtil.sol";
13
+
14
+ /**
15
+ * @title Equity
16
+ * @notice If the JuiceDollar system was a bank, this contract would represent the equity on its balance sheet.
17
+ * Like a corporation, the owners of the equity capital are the shareholders, or in this case the holders
18
+ * of Juice Protocol (JUICE) tokens. Anyone can mint additional JUICE tokens by adding JuiceDollars to the
19
+ * reserve pool. Also, JUICE tokens can be redeemed for JuiceDollars again, with only a one-block delay to
20
+ * prevent flash loan attacks.
21
+ *
22
+ * Furthermore, the JUICE shares come with voting power based on holding duration. Anyone that held at least
23
+ * 2% of the holding-period-weighted reserve pool shares gains veto power and can veto new proposals.
24
+ */
25
+ contract Equity is ERC20Permit, ERC3009, MathUtil, IReserve, ERC165 {
26
+ /**
27
+ * The VALUATION_FACTOR determines the market cap of the reserve pool shares relative to the equity reserves.
28
+ * The following always holds: Market Cap = Valuation Factor * Equity Reserve = Price * Supply
29
+ *
30
+ * In the absence of fees, profits and losses, the variables grow as follows when JUICE tokens are minted:
31
+ *
32
+ * | Reserve | Market Cap | Price | Supply |
33
+ * | 1_000 | 10_000 | 0.001 | 10_000_000 |
34
+ * | 100_000_000 | 1_000_000_000 | 31.62 | 31_622_777 |
35
+ * | 10_000_000_000_000 |100_000_000_000_000 | 1_000_000 | 100_000_000 |
36
+ *
37
+ * i.e., the supply is proportional to the tenth root of the reserve and the price is proportional to
38
+ * the ninth power of the tenth root (or Reserve^0.9). When profits accumulate or losses materialize,
39
+ * the reserve, market cap, and price adjust according to their respective power laws.
40
+ */
41
+ uint32 public constant VALUATION_FACTOR = 10;
42
+
43
+ uint256 private constant MINIMUM_EQUITY = 1_000 * ONE_DEC18;
44
+
45
+ /**
46
+ * @notice The quorum in basis points. 100 is 1%.
47
+ */
48
+ uint32 private constant QUORUM = 200;
49
+
50
+ /**
51
+ * @notice The number of digits to store the average holding time of share tokens.
52
+ */
53
+ uint8 private constant TIME_RESOLUTION_BITS = 20;
54
+
55
+
56
+ JuiceDollar public immutable JUSD;
57
+
58
+ /**
59
+ * @dev To track the total number of votes we need to know the number of votes at the anchor time and when the
60
+ * anchor time was. This is (hopefully) stored in one 256 bit slot, with the anchor time taking 64 Bits and
61
+ * the total vote count 192 Bits. Given the sub-second resolution of 20 Bits, the implicit assumption is
62
+ * that the timestamp can always be stored in 44 Bits (i.e., it does not exceed half a million years). Further,
63
+ * given 18 decimals (about 60 Bits), this implies that the total supply cannot exceed
64
+ * 192 - 60 - 44 - 20 = 68 Bits
65
+ * Here, we are also safe, as 68 Bits would imply more than a trillion outstanding shares. In fact,
66
+ * a limit of about 2**36 shares (that's about 2**96 Bits when taking into account the decimals) is imposed
67
+ * when minting. This means that the maximum supply is billions of shares, which could only be reached in
68
+ * a scenario with hyperinflation, in which case the stablecoin is worthless anyway.
69
+ */
70
+ uint192 private totalVotesAtAnchor; // Total number of votes at the anchor time
71
+ uint64 private totalVotesAnchorTime; // 44 Bits for the time stamp, 20 Bit sub-second resolution
72
+
73
+ /**
74
+ * @notice Keeping track of who delegated votes to whom.
75
+ * Note that delegation does not mean you cannot vote / veto anymore; it just means that the delegate can
76
+ * benefit from your votes when invoking a veto. Circular delegations are valid but do not help when voting.
77
+ */
78
+ mapping(address owner => address delegate) public delegates;
79
+
80
+ /**
81
+ * @notice A time stamp in the past such that: votes = balance * (time passed since anchor was set).
82
+ */
83
+ mapping(address owner => uint64 timestamp) private voteAnchor; // 44 bits for time stamp, 20 sub-second resolution
84
+
85
+ /**
86
+ * @notice Block number when an address last received JUICE shares (via mint, transfer, or any inbound path).
87
+ * Used to prevent same-block redemptions and flash loan attacks.
88
+ */
89
+ mapping(address owner => uint256 blockNumber) public lastInboundBlock;
90
+
91
+ event Delegation(address indexed from, address indexed to); // indicates a delegation
92
+ event Trade(address who, int256 amount, uint256 totPrice, uint256 newprice); // amount pos or neg for mint or redemption
93
+
94
+ error NotQualified();
95
+ error NotMinter();
96
+ error InsufficientEquity();
97
+ error TooManyShares();
98
+ error TotalSupplyExceeded();
99
+ error SameBlockRedemption();
100
+
101
+ constructor(
102
+ JuiceDollar JUSD_
103
+ )
104
+ ERC20Permit("Juice Protocol")
105
+ ERC20("Juice Protocol", "JUICE")
106
+ {
107
+ JUSD = JUSD_;
108
+ }
109
+
110
+ /**
111
+ * @notice Prevents same-block redemptions to protect against flash loan and atomic MEV attacks.
112
+ * @param owner The address whose shares are being redeemed
113
+ */
114
+ modifier notSameBlock(address owner) {
115
+ if (block.number <= lastInboundBlock[owner]) revert SameBlockRedemption();
116
+ _;
117
+ }
118
+
119
+ /**
120
+ * @notice Returns the price of one JUICE in JUSD with 18 decimals precision.
121
+ */
122
+ function price() public view returns (uint256) {
123
+ uint256 equity = JUSD.equity();
124
+ if (equity == 0 || totalSupply() == 0) {
125
+ return 10 ** 14;
126
+ } else {
127
+ return (VALUATION_FACTOR * JUSD.equity() * ONE_DEC18) / totalSupply();
128
+ }
129
+ }
130
+
131
+ function _update(address from, address to, uint256 value) internal override {
132
+ if (value > 0) {
133
+ // No need to adjust the sender's votes. When they send out 10% of their shares, they also lose 10% of
134
+ // their votes, so everything falls nicely into place. Recipient votes should stay the same, but grow
135
+ // faster in the future, requiring an adjustment of the anchor.
136
+ uint256 roundingLoss = _adjustRecipientVoteAnchor(to, value);
137
+ // The total also must be adjusted and kept accurate by taking into account the rounding error.
138
+ _adjustTotalVotes(from, value, roundingLoss);
139
+
140
+ // Flash loan protection: Track when shares are received to prevent same-block redemptions.
141
+ // This covers mints (from == address(0)), transfers, and ERC3009 transferWithAuthorization.
142
+ if (to != address(0)) {
143
+ lastInboundBlock[to] = block.number;
144
+ }
145
+ }
146
+ super._update(from, to, value);
147
+ }
148
+
149
+ /**
150
+ * @notice Decrease the total votes anchor when tokens lose their voting power due to being moved.
151
+ * @param from sender
152
+ * @param amount amount to be sent
153
+ */
154
+ function _adjustTotalVotes(address from, uint256 amount, uint256 roundingLoss) internal {
155
+ uint64 time = _anchorTime();
156
+ uint256 lostVotes = from == address(0x0) ? 0 : (time - voteAnchor[from]) * amount;
157
+ totalVotesAtAnchor = uint192(totalVotes() - roundingLoss - lostVotes);
158
+ totalVotesAnchorTime = time;
159
+ }
160
+
161
+ /**
162
+ * @notice The vote anchor of the recipient is moved forward such that the number of calculated
163
+ * votes does not change despite the higher balance.
164
+ * @param to receiver address
165
+ * @param amount amount to be received
166
+ * @return the number of votes lost due to rounding errors
167
+ */
168
+ function _adjustRecipientVoteAnchor(address to, uint256 amount) internal returns (uint256) {
169
+ if (to != address(0x0)) {
170
+ uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 seconds
171
+ uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added
172
+ // new example: anchor is only 21 / 11 = ~1 second in the past
173
+ voteAnchor[to] = uint64(_anchorTime() - recipientVotes / newbalance);
174
+ return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes
175
+ } else {
176
+ // optimization for burn, vote anchor of null address does not matter
177
+ return 0;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * @notice Time stamp with some additional bits for higher resolution.
183
+ */
184
+ function _anchorTime() internal view returns (uint64) {
185
+ return uint64(block.timestamp << TIME_RESOLUTION_BITS);
186
+ }
187
+
188
+ /**
189
+ * @notice The relative voting power of the address.
190
+ * @return A percentage with 1e18 being 100%
191
+ */
192
+ function relativeVotes(address holder) external view returns (uint256) {
193
+ return (ONE_DEC18 * votes(holder)) / totalVotes();
194
+ }
195
+
196
+ /**
197
+ * @notice The votes of the holder, excluding votes from delegates.
198
+ */
199
+ function votes(address holder) public view returns (uint256) {
200
+ return balanceOf(holder) * (_anchorTime() - voteAnchor[holder]);
201
+ }
202
+
203
+ /**
204
+ * @notice How long the holder already held onto their average JUICE in seconds.
205
+ */
206
+ function holdingDuration(address holder) public view returns (uint256) {
207
+ return (_anchorTime() - voteAnchor[holder]) >> TIME_RESOLUTION_BITS;
208
+ }
209
+
210
+ /**
211
+ * @notice Total number of votes in the system.
212
+ */
213
+ function totalVotes() public view returns (uint256) {
214
+ return totalVotesAtAnchor + totalSupply() * (_anchorTime() - totalVotesAnchorTime);
215
+ }
216
+
217
+ /**
218
+ * @notice The number of votes the sender commands when taking the support of the helpers into account.
219
+ * @param sender The address whose total voting power is of interest
220
+ * @param helpers An incrementally sorted list of helpers without duplicates and without the sender.
221
+ * The call fails if the list contains an address that does not delegate to sender.
222
+ * For indirect delegates, i.e. a -> b -> c, both a and b must be included for both to count.
223
+ * @return The total number of votes of sender at the current point in time.
224
+ */
225
+ function votesDelegated(address sender, address[] calldata helpers) public view returns (uint256) {
226
+ uint256 _votes = votes(sender);
227
+ require(_checkDuplicatesAndSorted(helpers));
228
+ for (uint i = 0; i < helpers.length; i++) {
229
+ address current = helpers[i];
230
+ require(current != sender);
231
+ require(_canVoteFor(sender, current));
232
+ _votes += votes(current);
233
+ }
234
+ return _votes;
235
+ }
236
+
237
+ function _checkDuplicatesAndSorted(address[] calldata helpers) internal pure returns (bool ok) {
238
+ if (helpers.length <= 1) {
239
+ return true;
240
+ } else {
241
+ address prevAddress = helpers[0];
242
+ for (uint i = 1; i < helpers.length; i++) {
243
+ if (helpers[i] <= prevAddress) {
244
+ return false;
245
+ }
246
+ prevAddress = helpers[i];
247
+ }
248
+ return true;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * @notice Checks whether the sender address is qualified given a list of helpers that delegated their votes
254
+ * directly or indirectly to the sender. It is the responsibility of the caller to figure out whether
255
+ * helpers are necessary and to identify them by scanning the blockchain for Delegation events.
256
+ */
257
+ function checkQualified(address sender, address[] calldata helpers) public view override {
258
+ uint256 _votes = votesDelegated(sender, helpers);
259
+ if (_votes * 10_000 < QUORUM * totalVotes()) revert NotQualified();
260
+ }
261
+
262
+ /**
263
+ * @notice Increases the voting power of the delegate by your number of votes without taking away any voting power
264
+ * from the sender.
265
+ */
266
+ function delegateVoteTo(address delegate) external {
267
+ delegates[msg.sender] = delegate;
268
+ emit Delegation(msg.sender, delegate);
269
+ }
270
+
271
+ function _canVoteFor(address delegate, address owner) internal view returns (bool) {
272
+ if (owner == delegate) {
273
+ return true;
274
+ } else if (owner == address(0x0)) {
275
+ return false;
276
+ } else {
277
+ return _canVoteFor(delegate, delegates[owner]);
278
+ }
279
+ }
280
+
281
+ /**
282
+ * @notice Since quorum is rather low, it is important to have a way to prevent malicious minority holders
283
+ * from blocking the whole system. This method provides a way for the good guys to team up and destroy
284
+ * the bad guy's votes (at the cost of also reducing their own votes). This mechanism potentially
285
+ * gives full control over the system to whoever has 51% of the votes.
286
+ *
287
+ * Since this is a rather aggressive measure, delegation is not supported. Every holder must call this
288
+ * method on their own.
289
+ * @param targets The target addresses to remove votes from
290
+ * @param votesToDestroy The maximum number of votes the caller is willing to sacrifice
291
+ */
292
+ function kamikaze(address[] calldata targets, uint256 votesToDestroy) external {
293
+ uint256 budget = _reduceVotes(msg.sender, votesToDestroy);
294
+ uint256 destroyedVotes = 0;
295
+ for (uint256 i = 0; i < targets.length && destroyedVotes < budget; i++) {
296
+ destroyedVotes += _reduceVotes(targets[i], budget - destroyedVotes);
297
+ }
298
+ require(destroyedVotes > 0); // sanity check
299
+ totalVotesAtAnchor = uint192(totalVotes() - destroyedVotes - budget);
300
+ totalVotesAnchorTime = _anchorTime();
301
+ }
302
+
303
+ function _reduceVotes(address target, uint256 amount) internal returns (uint256) {
304
+ uint256 votesBefore = votes(target);
305
+ if (amount >= votesBefore) {
306
+ amount = votesBefore;
307
+ voteAnchor[target] = _anchorTime();
308
+ return votesBefore;
309
+ } else {
310
+ voteAnchor[target] = uint64(_anchorTime() - (votesBefore - amount) / balanceOf(target));
311
+ return votesBefore - votes(target);
312
+ }
313
+ }
314
+
315
+ /**
316
+ * @notice Call this method to obtain newly minted pool shares in exchange for JuiceDollars.
317
+ * No allowance required (i.e., it is hard-coded in the JuiceDollar token contract).
318
+ * Make sure to invest at least 10e-12 * market cap to avoid rounding losses.
319
+ *
320
+ * @dev If equity is close to zero or negative, you need to send enough JUSD to bring equity back to 1_000 JUSD.
321
+ *
322
+ * @param amount JuiceDollars to invest
323
+ * @param expectedShares Minimum amount of expected shares for front running protection
324
+ */
325
+ function invest(uint256 amount, uint256 expectedShares) external returns (uint256) {
326
+ return _invest(_msgSender(), amount, expectedShares);
327
+ }
328
+
329
+ function investFor(address investor, uint256 amount, uint256 expectedShares) external returns (uint256) {
330
+ if (!JUSD.isMinter(_msgSender())) revert NotMinter();
331
+ return _invest(investor, amount, expectedShares);
332
+ }
333
+
334
+ function _invest(address investor, uint256 amount, uint256 expectedShares) internal returns (uint256) {
335
+ JUSD.transferFrom(investor, address(this), amount);
336
+ uint256 equity = JUSD.equity();
337
+ if (equity < MINIMUM_EQUITY) revert InsufficientEquity(); // ensures that the initial deposit is at least 1_000 JUSD
338
+
339
+ uint256 shares = _calculateShares(equity <= amount ? 0 : equity - amount, amount);
340
+ require(shares >= expectedShares);
341
+ _mint(investor, shares);
342
+ emit Trade(investor, int(shares), amount, price());
343
+
344
+ // limit the total supply to a reasonable amount to guard against overflows with price and vote calculations
345
+ if(totalSupply() > type(uint96).max) revert TotalSupplyExceeded();
346
+ return shares;
347
+ }
348
+
349
+ /**
350
+ * @notice Calculate shares received when investing JuiceDollars
351
+ * @param investment JUSD to be invested
352
+ * @return shares to be received in return
353
+ */
354
+ function calculateShares(uint256 investment) external view returns (uint256) {
355
+ return _calculateShares(JUSD.equity(), investment);
356
+ }
357
+
358
+ function _calculateShares(uint256 capitalBefore, uint256 investment) internal view returns (uint256) {
359
+ uint256 totalShares = totalSupply();
360
+ uint256 investmentExFees = (investment * 980) / 1_000; // remove 2% fee
361
+ // Assign 10_000_000 JUICE for the initial deposit, calculate the amount otherwise
362
+ uint256 newTotalShares = (capitalBefore < MINIMUM_EQUITY || totalShares == 0)
363
+ ? totalShares + 10_000_000 * ONE_DEC18
364
+ : _mulD18(totalShares, _tenthRoot(_divD18(capitalBefore + investmentExFees, capitalBefore)));
365
+ return newTotalShares - totalShares;
366
+ }
367
+
368
+ /**
369
+ * @notice Redeem the given amount of shares owned by the sender and transfer the proceeds to the target.
370
+ * @return The amount of JUSD transferred to the target
371
+ */
372
+ function redeem(address target, uint256 shares) external notSameBlock(msg.sender) returns (uint256) {
373
+ return _redeemFrom(msg.sender, target, shares);
374
+ }
375
+
376
+ /**
377
+ * @notice Like redeem(...), but with an extra parameter to protect against front running.
378
+ * @param expectedProceeds The minimum acceptable redemption proceeds.
379
+ */
380
+ function redeemExpected(address target, uint256 shares, uint256 expectedProceeds) external notSameBlock(msg.sender) returns (uint256) {
381
+ uint256 proceeds = _redeemFrom(msg.sender, target, shares);
382
+ require(proceeds >= expectedProceeds);
383
+ return proceeds;
384
+ }
385
+
386
+ /**
387
+ * @notice Redeem JUICE based on an allowance from the owner to the caller.
388
+ * See also redeemExpected(...).
389
+ */
390
+ function redeemFrom(
391
+ address owner,
392
+ address target,
393
+ uint256 shares,
394
+ uint256 expectedProceeds
395
+ ) external notSameBlock(owner) returns (uint256) {
396
+ _spendAllowance(owner, msg.sender, shares);
397
+ uint256 proceeds = _redeemFrom(owner, target, shares);
398
+ require(proceeds >= expectedProceeds);
399
+ return proceeds;
400
+ }
401
+
402
+ function _redeemFrom(address owner, address target, uint256 shares) internal returns (uint256) {
403
+ uint256 proceeds = calculateProceeds(shares);
404
+ _burn(owner, shares);
405
+ JUSD.transfer(target, proceeds);
406
+ emit Trade(owner, -int(shares), proceeds, price());
407
+ return proceeds;
408
+ }
409
+
410
+ /**
411
+ * @notice Calculate JUSD received when depositing shares
412
+ * @param shares number of shares we want to exchange for JUSD,
413
+ * in dec18 format
414
+ * @return amount of JUSD received for the shares
415
+ */
416
+ function calculateProceeds(uint256 shares) public view returns (uint256) {
417
+ uint256 totalShares = totalSupply();
418
+ if (shares + ONE_DEC18 >= totalShares) revert TooManyShares(); // make sure there is always at least one share
419
+ uint256 capital = JUSD.equity();
420
+ uint256 reductionAfterFees = (shares * 980) / 1_000; // remove 2% fee
421
+ uint256 newCapital = _mulD18(capital, _power10(_divD18(totalShares - reductionAfterFees, totalShares)));
422
+ return capital - newCapital;
423
+ }
424
+
425
+ /**
426
+ * @notice If there is less than 1_000 JUSD in equity left (maybe even negative), the system is at risk
427
+ * and we should allow qualified JUICE holders to restructure the system.
428
+ *
429
+ * Example: there was a devastating loss and equity stands at -1'000'000. Most shareholders have lost hope in the
430
+ * JuiceDollar system except for a group of small JUICE holders who still believe in it and are willing to provide
431
+ * 2'000'000 JUSD to save it. These brave souls are essentially donating 1'000'000 to the minter reserve and it
432
+ * would be wrong to force them to share the other million with the passive JUICE holders. Instead, they will get
433
+ * the possibility to bootstrap the system again owning 100% of all JUICE shares.
434
+ *
435
+ * @param helpers A list of addresses that delegate to the caller in incremental order
436
+ * @param addressesToWipe A list of addresses whose JUICE will be burned to zero
437
+ */
438
+ function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) external {
439
+ require(JUSD.equity() < MINIMUM_EQUITY);
440
+ checkQualified(msg.sender, helpers);
441
+ for (uint256 i = 0; i < addressesToWipe.length; i++) {
442
+ address current = addressesToWipe[i];
443
+ _burn(current, balanceOf(current));
444
+ }
445
+ }
446
+
447
+ /**
448
+ * @dev See {IERC165-supportsInterface}.
449
+ */
450
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
451
+ return
452
+ interfaceId == type(IERC20).interfaceId ||
453
+ interfaceId == type(ERC20Permit).interfaceId ||
454
+ interfaceId == type(ERC3009).interfaceId ||
455
+ super.supportsInterface(interfaceId);
456
+ }
457
+ }