@rev-net/core-v6 0.0.36 → 0.0.37
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/RISKS.md +11 -1
- package/package.json +9 -9
- package/src/REVLoans.sol +120 -83
- package/src/REVOwner.sol +3 -3
- package/test/REV.integrations.t.sol +14 -14
- package/test/REVInvincibility.t.sol +16 -16
- package/test/REVLifecycle.t.sol +32 -32
- package/test/REVLoansSourced.t.sol +15 -15
- package/test/TestCashOutCallerValidation.t.sol +8 -8
- package/test/TestConversionDocumentation.t.sol +2 -5
- package/test/TestCrossCurrencyReclaim.t.sol +72 -72
- package/test/TestLongTailEconomics.t.sol +56 -56
- package/test/TestSwapTerminalPermission.t.sol +21 -21
- package/test/audit/HiddenSupplyCashout.t.sol +61 -0
- package/test/audit/NemesisVerification.t.sol +97 -0
- package/test/audit/REVOwnerCurrencyMismatch.t.sol +188 -0
- package/test/audit/{CodexREVOwnerRemoteSurplusCurrencyMismatch.t.sol → REVOwnerRemoteSurplusCurrencyMismatch.t.sol} +4 -6
- package/test/audit/ReallocatePermission.t.sol +363 -0
- package/test/audit/RemoteLoanAccountingGap.t.sol +74 -0
- package/test/fork/TestCashOutFork.t.sol +48 -48
- package/test/fork/TestLoanAdversarialFork.t.sol +744 -0
- package/test/fork/TestLoanERC20Fork.t.sol +2 -8
- package/test/fork/TestPermit2PaymentFork.t.sol +32 -32
- package/test/regression/TestBurnPermissionRequired.t.sol +5 -5
- package/test/regression/TestCashOutBuybackFeeLeak.t.sol +8 -8
- /package/test/audit/{CodexCrossChainBuybackRouteMismatch.t.sol → CrossChainBuybackRouteMismatch.t.sol} +0 -0
- /package/test/audit/{NemesisOperatorDelegation.t.sol → OperatorDelegation.t.sol} +0 -0
- /package/test/audit/{CodexPhantomSurplusTerminal.t.sol → PhantomSurplusTerminal.t.sol} +0 -0
|
@@ -268,14 +268,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
268
268
|
vm.prank(USER1);
|
|
269
269
|
uint256 reclaimed = jbMultiTerminal()
|
|
270
270
|
.cashOutTokensOf({
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
271
|
+
holder: USER1,
|
|
272
|
+
projectId: REVNET_ID,
|
|
273
|
+
cashOutCount: tokens,
|
|
274
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
275
|
+
minTokensReclaimed: 0,
|
|
276
|
+
beneficiary: payable(USER1),
|
|
277
|
+
metadata: ""
|
|
278
|
+
});
|
|
279
279
|
|
|
280
280
|
assertGt(reclaimed, 0, "should reclaim some ETH");
|
|
281
281
|
// With 50% tax and single holder cashing out everything, the bonding curve returns the full surplus.
|
|
@@ -310,14 +310,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
310
310
|
vm.prank(USER1);
|
|
311
311
|
uint256 reclaimedToken = jbMultiTerminal()
|
|
312
312
|
.cashOutTokensOf({
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
313
|
+
holder: USER1,
|
|
314
|
+
projectId: REVNET_ID,
|
|
315
|
+
cashOutCount: revTokens,
|
|
316
|
+
tokenToReclaim: address(TOKEN),
|
|
317
|
+
minTokensReclaimed: 0,
|
|
318
|
+
beneficiary: payable(USER1),
|
|
319
|
+
metadata: ""
|
|
320
|
+
});
|
|
321
321
|
|
|
322
322
|
assertGt(reclaimedToken, 0, "should reclaim some TOKEN");
|
|
323
323
|
assertLe(reclaimedToken, tokenAmount, "should not exceed total TOKEN paid in");
|
|
@@ -358,14 +358,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
358
358
|
vm.expectRevert();
|
|
359
359
|
jbMultiTerminal()
|
|
360
360
|
.cashOutTokensOf({
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
361
|
+
holder: USER1,
|
|
362
|
+
projectId: REVNET_ID,
|
|
363
|
+
cashOutCount: revTokens,
|
|
364
|
+
tokenToReclaim: address(TOKEN),
|
|
365
|
+
minTokensReclaimed: 0,
|
|
366
|
+
beneficiary: payable(USER1),
|
|
367
|
+
metadata: ""
|
|
368
|
+
});
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
/// @notice Pay with TOKEN (so the surplus is in TOKEN), then pay with ETH too.
|
|
@@ -395,14 +395,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
395
395
|
vm.prank(USER1);
|
|
396
396
|
uint256 reclaimedToken = jbMultiTerminal()
|
|
397
397
|
.cashOutTokensOf({
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
398
|
+
holder: USER1,
|
|
399
|
+
projectId: REVNET_ID,
|
|
400
|
+
cashOutCount: cashOutCount,
|
|
401
|
+
tokenToReclaim: address(TOKEN),
|
|
402
|
+
minTokensReclaimed: 0,
|
|
403
|
+
beneficiary: payable(USER1),
|
|
404
|
+
metadata: ""
|
|
405
|
+
});
|
|
406
406
|
|
|
407
407
|
assertGt(reclaimedToken, 0, "should reclaim some TOKEN");
|
|
408
408
|
assertLe(reclaimedToken, tokenPayment, "should not exceed total TOKEN paid in");
|
|
@@ -442,14 +442,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
442
442
|
vm.prank(USER1);
|
|
443
443
|
uint256 reclaimed = jbMultiTerminal()
|
|
444
444
|
.cashOutTokensOf({
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
445
|
+
holder: USER1,
|
|
446
|
+
projectId: REVNET_ID,
|
|
447
|
+
cashOutCount: tokens1,
|
|
448
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
449
|
+
minTokensReclaimed: 0,
|
|
450
|
+
beneficiary: payable(USER1),
|
|
451
|
+
metadata: ""
|
|
452
|
+
});
|
|
453
453
|
assertGt(reclaimed, 0, "should reclaim ETH from mixed-currency surplus");
|
|
454
454
|
|
|
455
455
|
// The reclaimed amount should be less than the original payment (due to bonding curve tax).
|
|
@@ -480,14 +480,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
480
480
|
vm.prank(USER1);
|
|
481
481
|
uint256 reclaimed = jbMultiTerminal()
|
|
482
482
|
.cashOutTokensOf({
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
483
|
+
holder: USER1,
|
|
484
|
+
projectId: REVNET_ID,
|
|
485
|
+
cashOutCount: tokens,
|
|
486
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
487
|
+
minTokensReclaimed: 0,
|
|
488
|
+
beneficiary: payable(USER1),
|
|
489
|
+
metadata: ""
|
|
490
|
+
});
|
|
491
491
|
|
|
492
492
|
assertLe(reclaimed, 1, "tiny payment should not yield more than original amount");
|
|
493
493
|
}
|
|
@@ -517,14 +517,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
517
517
|
vm.prank(USER1);
|
|
518
518
|
uint256 reclaimed = jbMultiTerminal()
|
|
519
519
|
.cashOutTokensOf({
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
520
|
+
holder: USER1,
|
|
521
|
+
projectId: REVNET_ID,
|
|
522
|
+
cashOutCount: revTokens,
|
|
523
|
+
tokenToReclaim: address(TOKEN),
|
|
524
|
+
minTokensReclaimed: 0,
|
|
525
|
+
beneficiary: payable(USER1),
|
|
526
|
+
metadata: ""
|
|
527
|
+
});
|
|
528
528
|
|
|
529
529
|
// Should reclaim some TOKEN (bounded by the original payment amount).
|
|
530
530
|
assertLe(reclaimed, tokenAmount, "should not exceed original TOKEN payment");
|
|
@@ -559,14 +559,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
559
559
|
vm.prank(USER1);
|
|
560
560
|
uint256 reclaimed = jbMultiTerminal()
|
|
561
561
|
.cashOutTokensOf({
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
562
|
+
holder: USER1,
|
|
563
|
+
projectId: REVNET_ID,
|
|
564
|
+
cashOutCount: revTokens,
|
|
565
|
+
tokenToReclaim: address(TOKEN),
|
|
566
|
+
minTokensReclaimed: 0,
|
|
567
|
+
beneficiary: payable(USER1),
|
|
568
|
+
metadata: ""
|
|
569
|
+
});
|
|
570
570
|
|
|
571
571
|
assertLe(reclaimed, tokenAmount, "should not exceed total TOKEN paid");
|
|
572
572
|
}
|
|
@@ -595,14 +595,14 @@ contract TestCrossCurrencyReclaim is TestBaseWorkflow {
|
|
|
595
595
|
vm.prank(USER1);
|
|
596
596
|
uint256 reclaimed = jbMultiTerminal()
|
|
597
597
|
.cashOutTokensOf({
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
598
|
+
holder: USER1,
|
|
599
|
+
projectId: REVNET_ID,
|
|
600
|
+
cashOutCount: tokens,
|
|
601
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
602
|
+
minTokensReclaimed: 0,
|
|
603
|
+
beneficiary: payable(USER1),
|
|
604
|
+
metadata: ""
|
|
605
|
+
});
|
|
606
606
|
|
|
607
607
|
// With 0% cash out tax, no fee is charged on cash outs (per REVDeployer.beforeCashOutRecordedWith).
|
|
608
608
|
assertEq(reclaimed, balanceBefore, "0% tax, single holder should reclaim full balance");
|
|
@@ -363,14 +363,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
363
363
|
vm.prank(users[i]);
|
|
364
364
|
uint256 reclaimed = jbMultiTerminal()
|
|
365
365
|
.cashOutTokensOf({
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
366
|
+
holder: users[i],
|
|
367
|
+
projectId: REVNET_ID,
|
|
368
|
+
cashOutCount: userTokens[i],
|
|
369
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
370
|
+
minTokensReclaimed: 0,
|
|
371
|
+
beneficiary: payable(users[i]),
|
|
372
|
+
metadata: ""
|
|
373
|
+
});
|
|
374
374
|
totalReclaimed += reclaimed;
|
|
375
375
|
}
|
|
376
376
|
|
|
@@ -412,14 +412,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
412
412
|
vm.prank(payer);
|
|
413
413
|
uint256 reclaimedHalf = jbMultiTerminal()
|
|
414
414
|
.cashOutTokensOf({
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
415
|
+
holder: payer,
|
|
416
|
+
projectId: REVNET_ID,
|
|
417
|
+
cashOutCount: halfTokens,
|
|
418
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
419
|
+
minTokensReclaimed: 0,
|
|
420
|
+
beneficiary: payable(payer),
|
|
421
|
+
metadata: ""
|
|
422
|
+
});
|
|
423
423
|
|
|
424
424
|
// With a 50% tax rate and being the only holder, cashing out half the tokens
|
|
425
425
|
// should return less than half the surplus (bonding curve subproportional behavior).
|
|
@@ -452,14 +452,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
452
452
|
vm.prank(users[i]);
|
|
453
453
|
jbMultiTerminal()
|
|
454
454
|
.cashOutTokensOf({
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
455
|
+
holder: users[i],
|
|
456
|
+
projectId: REVNET_ID,
|
|
457
|
+
cashOutCount: userBalance / 2,
|
|
458
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
459
|
+
minTokensReclaimed: 0,
|
|
460
|
+
beneficiary: payable(users[i]),
|
|
461
|
+
metadata: ""
|
|
462
|
+
});
|
|
463
463
|
}
|
|
464
464
|
}
|
|
465
465
|
|
|
@@ -479,14 +479,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
479
479
|
vm.prank(users[3]);
|
|
480
480
|
jbMultiTerminal()
|
|
481
481
|
.cashOutTokensOf({
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
482
|
+
holder: users[3],
|
|
483
|
+
projectId: REVNET_ID,
|
|
484
|
+
cashOutCount: user3Balance,
|
|
485
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
486
|
+
minTokensReclaimed: 0,
|
|
487
|
+
beneficiary: payable(users[3]),
|
|
488
|
+
metadata: ""
|
|
489
|
+
});
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
|
|
@@ -556,14 +556,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
556
556
|
vm.prank(lateUser);
|
|
557
557
|
uint256 reclaimed = jbMultiTerminal()
|
|
558
558
|
.cashOutTokensOf({
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
559
|
+
holder: lateUser,
|
|
560
|
+
projectId: REVNET_ID,
|
|
561
|
+
cashOutCount: lateTokens,
|
|
562
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
563
|
+
minTokensReclaimed: 0,
|
|
564
|
+
beneficiary: payable(lateUser),
|
|
565
|
+
metadata: ""
|
|
566
|
+
});
|
|
567
567
|
|
|
568
568
|
// The late entrant should not extract more than they put in.
|
|
569
569
|
assertLe(reclaimed, 10e18, "late entrant should not extract more than they paid");
|
|
@@ -599,14 +599,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
599
599
|
vm.prank(users[0]);
|
|
600
600
|
jbMultiTerminal()
|
|
601
601
|
.cashOutTokensOf({
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
602
|
+
holder: users[0],
|
|
603
|
+
projectId: REVNET_ID,
|
|
604
|
+
cashOutCount: balance0 / 3,
|
|
605
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
606
|
+
minTokensReclaimed: 0,
|
|
607
|
+
beneficiary: payable(users[0]),
|
|
608
|
+
metadata: ""
|
|
609
|
+
});
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
612
|
|
|
@@ -654,14 +654,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
654
654
|
vm.prank(user);
|
|
655
655
|
jbMultiTerminal()
|
|
656
656
|
.cashOutTokensOf({
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
657
|
+
holder: user,
|
|
658
|
+
projectId: REVNET_ID,
|
|
659
|
+
cashOutCount: portion,
|
|
660
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
661
|
+
minTokensReclaimed: 0,
|
|
662
|
+
beneficiary: payable(user),
|
|
663
|
+
metadata: ""
|
|
664
|
+
});
|
|
665
665
|
|
|
666
666
|
uint256 feeBalanceAfterRound =
|
|
667
667
|
jbTerminalStore().balanceOf(address(jbMultiTerminal()), FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN);
|
|
@@ -206,24 +206,24 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
|
|
|
206
206
|
function test_splitOperator_hasRegistryPermissions() public view {
|
|
207
207
|
bool hasBuybackHook = jbPermissions()
|
|
208
208
|
.hasPermission({
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
operator: multisig(),
|
|
210
|
+
account: address(REV_DEPLOYER),
|
|
211
|
+
projectId: TEST_REVNET_ID,
|
|
212
|
+
permissionId: JBPermissionIds.SET_BUYBACK_HOOK,
|
|
213
|
+
includeRoot: false,
|
|
214
|
+
includeWildcardProjectId: false
|
|
215
|
+
});
|
|
216
216
|
assertTrue(hasBuybackHook, "Split operator should have SET_BUYBACK_HOOK permission");
|
|
217
217
|
|
|
218
218
|
bool hasRouterTerminal = jbPermissions()
|
|
219
219
|
.hasPermission({
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
220
|
+
operator: multisig(),
|
|
221
|
+
account: address(REV_DEPLOYER),
|
|
222
|
+
projectId: TEST_REVNET_ID,
|
|
223
|
+
permissionId: JBPermissionIds.SET_ROUTER_TERMINAL,
|
|
224
|
+
includeRoot: false,
|
|
225
|
+
includeWildcardProjectId: false
|
|
226
|
+
});
|
|
227
227
|
assertTrue(hasRouterTerminal, "Split operator should have SET_ROUTER_TERMINAL permission");
|
|
228
228
|
}
|
|
229
229
|
|
|
@@ -244,13 +244,13 @@ contract TestSwapTerminalPermission is TestBaseWorkflow {
|
|
|
244
244
|
for (uint256 i = 0; i < expectedPermissions.length; i++) {
|
|
245
245
|
bool hasPermission = jbPermissions()
|
|
246
246
|
.hasPermission({
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
247
|
+
operator: multisig(),
|
|
248
|
+
account: address(REV_DEPLOYER),
|
|
249
|
+
projectId: TEST_REVNET_ID,
|
|
250
|
+
permissionId: expectedPermissions[i],
|
|
251
|
+
includeRoot: false,
|
|
252
|
+
includeWildcardProjectId: false
|
|
253
|
+
});
|
|
254
254
|
assertTrue(
|
|
255
255
|
hasPermission,
|
|
256
256
|
string.concat(
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
5
|
+
|
|
6
|
+
import {TestHiddenTokens} from "../TestHiddenTokens.t.sol";
|
|
7
|
+
|
|
8
|
+
contract CodexNemesisHiddenSupplyCashoutTest is TestHiddenTokens {
|
|
9
|
+
function test_hiddenSupplyCanDrainCashoutAndThenBeRevealed() public {
|
|
10
|
+
uint256 payAmount = 10 ether;
|
|
11
|
+
|
|
12
|
+
vm.prank(USER);
|
|
13
|
+
uint256 minted = jbMultiTerminal().pay{value: payAmount}({
|
|
14
|
+
projectId: REVNET_ID,
|
|
15
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
16
|
+
amount: payAmount,
|
|
17
|
+
beneficiary: USER,
|
|
18
|
+
minReturnedTokens: 0,
|
|
19
|
+
memo: "",
|
|
20
|
+
metadata: ""
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
uint256 terminalBalanceBefore =
|
|
24
|
+
jbTerminalStore().balanceOf(address(jbMultiTerminal()), REVNET_ID, JBConstants.NATIVE_TOKEN);
|
|
25
|
+
assertEq(terminalBalanceBefore, payAmount, "setup: revnet terminal balance");
|
|
26
|
+
|
|
27
|
+
_allowHolderToHide(USER, REVNET_ID);
|
|
28
|
+
|
|
29
|
+
uint256 hiddenCount = minted / 2;
|
|
30
|
+
vm.prank(USER);
|
|
31
|
+
HIDDEN_TOKENS.hideTokensOf(REVNET_ID, hiddenCount, USER);
|
|
32
|
+
|
|
33
|
+
uint256 visibleSupply = jbController().TOKENS().totalSupplyOf(REVNET_ID);
|
|
34
|
+
assertEq(visibleSupply, minted - hiddenCount, "hidden tokens left the cash-out denominator");
|
|
35
|
+
|
|
36
|
+
vm.prank(USER);
|
|
37
|
+
jbMultiTerminal()
|
|
38
|
+
.cashOutTokensOf({
|
|
39
|
+
holder: USER,
|
|
40
|
+
projectId: REVNET_ID,
|
|
41
|
+
cashOutCount: visibleSupply,
|
|
42
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
43
|
+
minTokensReclaimed: 0,
|
|
44
|
+
beneficiary: payable(USER),
|
|
45
|
+
metadata: ""
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
uint256 terminalBalanceAfter =
|
|
49
|
+
jbTerminalStore().balanceOf(address(jbMultiTerminal()), REVNET_ID, JBConstants.NATIVE_TOKEN);
|
|
50
|
+
assertEq(terminalBalanceAfter, 0, "visible tranche drained the revnet balance");
|
|
51
|
+
|
|
52
|
+
vm.prank(USER);
|
|
53
|
+
HIDDEN_TOKENS.revealTokensOf(REVNET_ID, hiddenCount, USER);
|
|
54
|
+
|
|
55
|
+
assertEq(
|
|
56
|
+
jbController().TOKENS().totalBalanceOf(USER, REVNET_ID),
|
|
57
|
+
hiddenCount,
|
|
58
|
+
"hidden tranche was restored after the cash out"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
|
|
6
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
7
|
+
import {JBCashOuts} from "@bananapus/core-v6/src/libraries/JBCashOuts.sol";
|
|
8
|
+
|
|
9
|
+
contract CodexNemesisVerificationTest is Test {
|
|
10
|
+
function testConfigurationHashDoesNotCommitSplitOperatorSplitsOrExtraMetadata() public pure {
|
|
11
|
+
bytes32 hashA = _revDeployerEncodedConfigurationHash();
|
|
12
|
+
bytes32 hashB = _revDeployerEncodedConfigurationHash();
|
|
13
|
+
|
|
14
|
+
assertEq(hashA, hashB, "actual REVDeployer hash collides");
|
|
15
|
+
|
|
16
|
+
bytes32 fullCommitmentA = keccak256(
|
|
17
|
+
abi.encode(hashA, address(0x1111), uint32(7000), address(0xAAAA), uint48(30 days), uint16(0x0004))
|
|
18
|
+
);
|
|
19
|
+
bytes32 fullCommitmentB = keccak256(
|
|
20
|
+
abi.encode(hashB, address(0x2222), uint32(7000), address(0xBBBB), uint48(90 days), uint16(0x0000))
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
assertNotEq(fullCommitmentA, fullCommitmentB, "omitted fields are economically distinct");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function testRemoteLoanStateOmissionOverstatesCrossChainCashOutValue() public pure {
|
|
27
|
+
uint256 localSupply = 100 ether;
|
|
28
|
+
uint256 localSurplus = 100 ether;
|
|
29
|
+
uint256 remoteVisibleSupply = 1 ether;
|
|
30
|
+
uint256 remoteVisibleSurplus = 1 ether;
|
|
31
|
+
uint256 omittedRemoteLoanCollateral = 99 ether;
|
|
32
|
+
uint256 omittedRemoteLoanDebt = 99 ether;
|
|
33
|
+
uint256 cashOutCount = 100 ether;
|
|
34
|
+
uint256 cashOutTaxRate = 1000;
|
|
35
|
+
|
|
36
|
+
uint256 current = JBCashOuts.cashOutFrom({
|
|
37
|
+
surplus: localSurplus + remoteVisibleSurplus,
|
|
38
|
+
cashOutCount: cashOutCount,
|
|
39
|
+
totalSupply: localSupply + remoteVisibleSupply,
|
|
40
|
+
cashOutTaxRate: cashOutTaxRate
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
uint256 withRemoteLoans = JBCashOuts.cashOutFrom({
|
|
44
|
+
surplus: localSurplus + remoteVisibleSurplus + omittedRemoteLoanDebt,
|
|
45
|
+
cashOutCount: cashOutCount,
|
|
46
|
+
totalSupply: localSupply + remoteVisibleSupply + omittedRemoteLoanCollateral,
|
|
47
|
+
cashOutTaxRate: cashOutTaxRate
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
assertGt(current, withRemoteLoans, "omitting remote loan state should not increase cash-out value");
|
|
51
|
+
assertGt(current - withRemoteLoans, 4 ether, "drift is material");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function testRemoteLoanStateOmissionCanHitFullSupplyBranch() public pure {
|
|
55
|
+
uint256 localSupply = 100 ether;
|
|
56
|
+
uint256 localSurplus = 100 ether;
|
|
57
|
+
uint256 omittedRemoteLoanCollateral = 100 ether;
|
|
58
|
+
uint256 omittedRemoteLoanDebt = 100 ether;
|
|
59
|
+
uint256 cashOutCount = 100 ether;
|
|
60
|
+
uint256 cashOutTaxRate = 1000;
|
|
61
|
+
|
|
62
|
+
uint256 current = JBCashOuts.cashOutFrom({
|
|
63
|
+
surplus: localSurplus, cashOutCount: cashOutCount, totalSupply: localSupply, cashOutTaxRate: cashOutTaxRate
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
uint256 withRemoteLoans = JBCashOuts.cashOutFrom({
|
|
67
|
+
surplus: localSurplus + omittedRemoteLoanDebt,
|
|
68
|
+
cashOutCount: cashOutCount,
|
|
69
|
+
totalSupply: localSupply + omittedRemoteLoanCollateral,
|
|
70
|
+
cashOutTaxRate: cashOutTaxRate
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
assertEq(current, localSurplus, "current path enters full-supply branch");
|
|
74
|
+
assertEq(withRemoteLoans, 95 ether, "true omnichain curve should remain partial");
|
|
75
|
+
assertGt(current, withRemoteLoans, "full-supply branch overstates cash-out value");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function _revDeployerEncodedConfigurationHash() internal pure returns (bytes32) {
|
|
79
|
+
bytes memory encodedConfiguration = abi.encode(uint32(1), "REV", "REV", bytes32("codex-nemesis"));
|
|
80
|
+
|
|
81
|
+
encodedConfiguration = abi.encode(encodedConfiguration, address(0x1234));
|
|
82
|
+
|
|
83
|
+
encodedConfiguration = abi.encode(
|
|
84
|
+
encodedConfiguration,
|
|
85
|
+
uint48(1_740_089_444),
|
|
86
|
+
uint16(7000),
|
|
87
|
+
uint112(1_000_000 ether),
|
|
88
|
+
uint32(30 days),
|
|
89
|
+
uint32(0),
|
|
90
|
+
uint16(JBConstants.MAX_CASH_OUT_TAX_RATE / 2)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
encodedConfiguration = abi.encode(encodedConfiguration, uint32(1), address(0xBEEF), uint104(100_000 ether));
|
|
94
|
+
|
|
95
|
+
return keccak256(encodedConfiguration);
|
|
96
|
+
}
|
|
97
|
+
}
|