@bananapus/core-v6 0.0.18 → 0.0.19
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/CHANGE_LOG.md +16 -1
- package/SKILLS.md +6 -0
- package/USER_JOURNEYS.md +4 -0
- package/package.json +1 -1
- package/src/JBTerminalStore.sol +357 -171
- package/src/interfaces/IJBTerminalStore.sol +70 -0
- package/test/fork/TestSequencerPriceFeedFork.sol +168 -0
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +215 -0
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +383 -0
- package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +415 -0
|
@@ -65,6 +65,22 @@ interface IJBTerminalStore {
|
|
|
65
65
|
view
|
|
66
66
|
returns (uint256);
|
|
67
67
|
|
|
68
|
+
/// @notice Returns the reclaimable surplus for a project across all terminals using all accounting contexts.
|
|
69
|
+
/// @param projectId The ID of the project.
|
|
70
|
+
/// @param cashOutCount The number of tokens being cashed out.
|
|
71
|
+
/// @param decimals The number of decimals to express the result with.
|
|
72
|
+
/// @param currency The currency to express the result in.
|
|
73
|
+
/// @return The reclaimable surplus amount.
|
|
74
|
+
function currentTotalReclaimableSurplusOf(
|
|
75
|
+
uint256 projectId,
|
|
76
|
+
uint256 cashOutCount,
|
|
77
|
+
uint256 decimals,
|
|
78
|
+
uint256 currency
|
|
79
|
+
)
|
|
80
|
+
external
|
|
81
|
+
view
|
|
82
|
+
returns (uint256);
|
|
83
|
+
|
|
68
84
|
/// @notice Returns the current surplus for a terminal and project.
|
|
69
85
|
/// @param terminal The terminal to get the surplus of.
|
|
70
86
|
/// @param projectId The ID of the project.
|
|
@@ -97,6 +113,60 @@ interface IJBTerminalStore {
|
|
|
97
113
|
view
|
|
98
114
|
returns (uint256);
|
|
99
115
|
|
|
116
|
+
/// @notice Simulates a cash out without modifying state.
|
|
117
|
+
/// @param terminal The terminal address to simulate the cash out from.
|
|
118
|
+
/// @param holder The address cashing out.
|
|
119
|
+
/// @param projectId The ID of the project being cashed out from.
|
|
120
|
+
/// @param cashOutCount The number of project tokens being cashed out.
|
|
121
|
+
/// @param accountingContext The accounting context of the token being reclaimed.
|
|
122
|
+
/// @param balanceAccountingContexts The accounting contexts to include in the balance calculation.
|
|
123
|
+
/// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address.
|
|
124
|
+
/// @param metadata Extra data to pass along to the data hook.
|
|
125
|
+
/// @return ruleset The project's current ruleset.
|
|
126
|
+
/// @return reclaimAmount The amount that would be reclaimed.
|
|
127
|
+
/// @return cashOutTaxRate The cash out tax rate that would be applied.
|
|
128
|
+
/// @return hookSpecifications Any cash out hook specifications from the data hook.
|
|
129
|
+
function previewCashOutFrom(
|
|
130
|
+
address terminal,
|
|
131
|
+
address holder,
|
|
132
|
+
uint256 projectId,
|
|
133
|
+
uint256 cashOutCount,
|
|
134
|
+
JBAccountingContext calldata accountingContext,
|
|
135
|
+
JBAccountingContext[] calldata balanceAccountingContexts,
|
|
136
|
+
bool beneficiaryIsFeeless,
|
|
137
|
+
bytes calldata metadata
|
|
138
|
+
)
|
|
139
|
+
external
|
|
140
|
+
view
|
|
141
|
+
returns (
|
|
142
|
+
JBRuleset memory ruleset,
|
|
143
|
+
uint256 reclaimAmount,
|
|
144
|
+
uint256 cashOutTaxRate,
|
|
145
|
+
JBCashOutHookSpecification[] memory hookSpecifications
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
/// @notice Simulates a payment without modifying state.
|
|
149
|
+
/// @param terminal The terminal address to simulate the payment from.
|
|
150
|
+
/// @param payer The address of the payer.
|
|
151
|
+
/// @param amount The amount being paid.
|
|
152
|
+
/// @param projectId The ID of the project being paid.
|
|
153
|
+
/// @param beneficiary The address to mint project tokens to.
|
|
154
|
+
/// @param metadata Extra data to pass along to the data hook.
|
|
155
|
+
/// @return ruleset The project's current ruleset.
|
|
156
|
+
/// @return tokenCount The number of project tokens that would be minted.
|
|
157
|
+
/// @return hookSpecifications Any pay hook specifications from the data hook.
|
|
158
|
+
function previewPayFrom(
|
|
159
|
+
address terminal,
|
|
160
|
+
address payer,
|
|
161
|
+
JBTokenAmount memory amount,
|
|
162
|
+
uint256 projectId,
|
|
163
|
+
address beneficiary,
|
|
164
|
+
bytes calldata metadata
|
|
165
|
+
)
|
|
166
|
+
external
|
|
167
|
+
view
|
|
168
|
+
returns (JBRuleset memory ruleset, uint256 tokenCount, JBPayHookSpecification[] memory hookSpecifications);
|
|
169
|
+
|
|
100
170
|
/// @notice Returns the amount of payout limit used by a terminal for a project in a given cycle.
|
|
101
171
|
/// @param terminal The terminal to get the used payout limit of.
|
|
102
172
|
/// @param projectId The ID of the project.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
|
|
6
|
+
import {AggregatorV2V3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol";
|
|
7
|
+
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
|
|
8
|
+
|
|
9
|
+
import {JBChainlinkV3SequencerPriceFeed} from "../../src/JBChainlinkV3SequencerPriceFeed.sol";
|
|
10
|
+
|
|
11
|
+
/// @notice Fork tests for JBChainlinkV3SequencerPriceFeed against the live Arbitrum sequencer uptime feed and
|
|
12
|
+
/// ETH/USD Chainlink oracle.
|
|
13
|
+
contract TestSequencerPriceFeedFork is Test {
|
|
14
|
+
// Chainlink feed addresses (Arbitrum mainnet).
|
|
15
|
+
address constant ARB_ETH_USD_FEED = 0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612;
|
|
16
|
+
address constant ARB_SEQUENCER_FEED = 0xFdB631F5EE196F0ed6FAa767959853A9F217697D;
|
|
17
|
+
|
|
18
|
+
// Staleness threshold (1 hour).
|
|
19
|
+
uint256 constant THRESHOLD = 3600;
|
|
20
|
+
|
|
21
|
+
// Grace period after sequencer restart (1 hour).
|
|
22
|
+
uint256 constant GRACE_PERIOD = 3600;
|
|
23
|
+
|
|
24
|
+
// Pinned block for reproducibility (sequencer is up at this block).
|
|
25
|
+
uint256 constant FORK_BLOCK = 300_000_000;
|
|
26
|
+
|
|
27
|
+
JBChainlinkV3SequencerPriceFeed feed;
|
|
28
|
+
|
|
29
|
+
function setUp() public {
|
|
30
|
+
string memory rpc = vm.envOr("RPC_ARBITRUM_MAINNET", string(""));
|
|
31
|
+
if (bytes(rpc).length == 0) {
|
|
32
|
+
// Skip all tests if no Arbitrum RPC is configured.
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
vm.createSelectFork(rpc, FORK_BLOCK);
|
|
37
|
+
|
|
38
|
+
feed = new JBChainlinkV3SequencerPriceFeed(
|
|
39
|
+
AggregatorV3Interface(ARB_ETH_USD_FEED),
|
|
40
|
+
THRESHOLD,
|
|
41
|
+
AggregatorV2V3Interface(ARB_SEQUENCER_FEED),
|
|
42
|
+
GRACE_PERIOD
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ------------------------------------------------------------------
|
|
47
|
+
// Helpers
|
|
48
|
+
// ------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
modifier skipIfNoRpc() {
|
|
51
|
+
if (address(feed) == address(0)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
_;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ------------------------------------------------------------------
|
|
58
|
+
// 1. Normal operation — valid price returned
|
|
59
|
+
// ------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
/// @notice Under normal conditions (sequencer up, grace period elapsed), currentUnitPrice returns a sane price.
|
|
62
|
+
function test_normalOperation_returnsValidPrice() public skipIfNoRpc {
|
|
63
|
+
uint256 price18 = feed.currentUnitPrice(18);
|
|
64
|
+
|
|
65
|
+
// ETH price should be between $500 and $50,000.
|
|
66
|
+
assertGt(price18, 500e18, "ETH price too low");
|
|
67
|
+
assertLt(price18, 50_000e18, "ETH price too high");
|
|
68
|
+
|
|
69
|
+
// Cross-check against raw latestRoundData from the price feed.
|
|
70
|
+
(, int256 rawPrice,,,) = AggregatorV3Interface(ARB_ETH_USD_FEED).latestRoundData();
|
|
71
|
+
uint256 feedDecimals = AggregatorV3Interface(ARB_ETH_USD_FEED).decimals();
|
|
72
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
73
|
+
uint256 expected18 = uint256(rawPrice) * 10 ** (18 - feedDecimals);
|
|
74
|
+
assertEq(price18, expected18, "Price mismatch vs raw feed");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ------------------------------------------------------------------
|
|
78
|
+
// 2. Sequencer down — reverts
|
|
79
|
+
// ------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
/// @notice When the sequencer feed reports answer=1 (down), currentUnitPrice reverts.
|
|
82
|
+
function test_sequencerDown_reverts() public skipIfNoRpc {
|
|
83
|
+
// Mock the sequencer feed to report answer=1 (down).
|
|
84
|
+
// latestRoundData() selector = 0xfeaf968c
|
|
85
|
+
vm.mockCall(
|
|
86
|
+
ARB_SEQUENCER_FEED,
|
|
87
|
+
abi.encodeWithSelector(AggregatorV3Interface.latestRoundData.selector),
|
|
88
|
+
abi.encode(
|
|
89
|
+
uint80(1), // roundId
|
|
90
|
+
int256(1), // answer = 1 → sequencer down
|
|
91
|
+
block.timestamp - GRACE_PERIOD - 100, // startedAt (irrelevant when answer=1)
|
|
92
|
+
block.timestamp, // updatedAt
|
|
93
|
+
uint80(1) // answeredInRound
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
vm.expectRevert(
|
|
98
|
+
abi.encodeWithSelector(
|
|
99
|
+
JBChainlinkV3SequencerPriceFeed.JBChainlinkV3SequencerPriceFeed_SequencerDownOrRestarting.selector,
|
|
100
|
+
block.timestamp,
|
|
101
|
+
GRACE_PERIOD,
|
|
102
|
+
block.timestamp - GRACE_PERIOD - 100
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
feed.currentUnitPrice(18);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ------------------------------------------------------------------
|
|
109
|
+
// 3. Grace period active — reverts
|
|
110
|
+
// ------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/// @notice When the sequencer just came back up (within grace period), currentUnitPrice reverts.
|
|
113
|
+
function test_gracePeriodActive_reverts() public skipIfNoRpc {
|
|
114
|
+
// Mock the sequencer feed: answer=0 (up) but startedAt is 1 second ago (within grace period).
|
|
115
|
+
uint256 startedAt = block.timestamp - 1;
|
|
116
|
+
|
|
117
|
+
vm.mockCall(
|
|
118
|
+
ARB_SEQUENCER_FEED,
|
|
119
|
+
abi.encodeWithSelector(AggregatorV3Interface.latestRoundData.selector),
|
|
120
|
+
abi.encode(
|
|
121
|
+
uint80(1), // roundId
|
|
122
|
+
int256(0), // answer = 0 → sequencer up
|
|
123
|
+
startedAt, // startedAt = very recent
|
|
124
|
+
block.timestamp, // updatedAt
|
|
125
|
+
uint80(1) // answeredInRound
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
vm.expectRevert(
|
|
130
|
+
abi.encodeWithSelector(
|
|
131
|
+
JBChainlinkV3SequencerPriceFeed.JBChainlinkV3SequencerPriceFeed_SequencerDownOrRestarting.selector,
|
|
132
|
+
block.timestamp,
|
|
133
|
+
GRACE_PERIOD,
|
|
134
|
+
startedAt
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
feed.currentUnitPrice(18);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ------------------------------------------------------------------
|
|
141
|
+
// 4. Post-grace recovery — succeeds
|
|
142
|
+
// ------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
/// @notice After the grace period has elapsed, currentUnitPrice succeeds again.
|
|
145
|
+
function test_postGraceRecovery_succeeds() public skipIfNoRpc {
|
|
146
|
+
// Mock the sequencer feed: answer=0 (up), startedAt is well before the grace period ended.
|
|
147
|
+
uint256 startedAt = block.timestamp - GRACE_PERIOD - 100;
|
|
148
|
+
|
|
149
|
+
vm.mockCall(
|
|
150
|
+
ARB_SEQUENCER_FEED,
|
|
151
|
+
abi.encodeWithSelector(AggregatorV3Interface.latestRoundData.selector),
|
|
152
|
+
abi.encode(
|
|
153
|
+
uint80(1), // roundId
|
|
154
|
+
int256(0), // answer = 0 → sequencer up
|
|
155
|
+
startedAt, // startedAt = well in the past
|
|
156
|
+
block.timestamp, // updatedAt
|
|
157
|
+
uint80(1) // answeredInRound
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// The price feed itself is still the real Chainlink feed, so this should succeed.
|
|
162
|
+
uint256 price18 = feed.currentUnitPrice(18);
|
|
163
|
+
|
|
164
|
+
// Same sanity check: ETH price between $500 and $50,000.
|
|
165
|
+
assertGt(price18, 500e18, "ETH price too low after recovery");
|
|
166
|
+
assertLt(price18, 50_000e18, "ETH price too high after recovery");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -444,4 +444,219 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
|
|
|
444
444
|
uint256 reclaimable = _store.currentReclaimableSurplusOf(_projectId, _tokenCount, 1e18, 1e18);
|
|
445
445
|
assertEq(1e18, reclaimable);
|
|
446
446
|
}
|
|
447
|
+
|
|
448
|
+
function test_GivenTotalReclaimableWithSurplus() external whenProjectHasBalance {
|
|
449
|
+
// it will default to all terminals and all accounting contexts and return the reclaimable surplus
|
|
450
|
+
|
|
451
|
+
// setup calldata
|
|
452
|
+
JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
|
|
453
|
+
_contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
|
|
454
|
+
|
|
455
|
+
JBRulesetMetadata memory _metadata = JBRulesetMetadata({
|
|
456
|
+
reservedPercent: 0,
|
|
457
|
+
cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE / 2,
|
|
458
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
459
|
+
pausePay: false,
|
|
460
|
+
pauseCreditTransfers: false,
|
|
461
|
+
allowOwnerMinting: false,
|
|
462
|
+
allowSetCustomToken: false,
|
|
463
|
+
allowTerminalMigration: false,
|
|
464
|
+
allowSetTerminals: false,
|
|
465
|
+
ownerMustSendPayouts: false,
|
|
466
|
+
allowSetController: false,
|
|
467
|
+
allowAddAccountingContext: true,
|
|
468
|
+
allowAddPriceFeed: false,
|
|
469
|
+
holdFees: false,
|
|
470
|
+
useTotalSurplusForCashOuts: false,
|
|
471
|
+
useDataHookForPay: false,
|
|
472
|
+
useDataHookForCashOut: false,
|
|
473
|
+
dataHook: address(0),
|
|
474
|
+
metadata: 0
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
|
|
478
|
+
|
|
479
|
+
// JBRulesets return calldata
|
|
480
|
+
JBRuleset memory _returnedRuleset = JBRuleset({
|
|
481
|
+
cycleNumber: uint48(block.timestamp),
|
|
482
|
+
id: uint48(block.timestamp),
|
|
483
|
+
basedOnId: 0,
|
|
484
|
+
start: uint48(block.timestamp),
|
|
485
|
+
duration: uint32(block.timestamp + 1000),
|
|
486
|
+
weight: 1e18,
|
|
487
|
+
weightCutPercent: 0,
|
|
488
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
489
|
+
metadata: _packedMetadata
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// mock call to JBRulesets currentOf
|
|
493
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
|
|
494
|
+
|
|
495
|
+
// mock call to JBDirectory controllerOf
|
|
496
|
+
mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
|
|
497
|
+
|
|
498
|
+
uint256 _supply = 1e19;
|
|
499
|
+
uint256 _surplus = 1e18;
|
|
500
|
+
uint256 _cashoutAmount = 1e18;
|
|
501
|
+
|
|
502
|
+
// mock JBDirectory terminalsOf to return the terminal
|
|
503
|
+
IJBTerminal[] memory _terminals = new IJBTerminal[](1);
|
|
504
|
+
_terminals[0] = _terminal;
|
|
505
|
+
mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
|
|
506
|
+
|
|
507
|
+
// surplus call to the terminal (empty accounting contexts passed through)
|
|
508
|
+
JBAccountingContext[] memory _emptyContexts = new JBAccountingContext[](0);
|
|
509
|
+
mockExpect(
|
|
510
|
+
address(_terminal),
|
|
511
|
+
abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
|
|
512
|
+
abi.encode(_surplus)
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
// mock JBController totalTokenSupplyWithReservedTokensOf
|
|
516
|
+
mockExpect(
|
|
517
|
+
address(_controller),
|
|
518
|
+
abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
|
|
519
|
+
abi.encode(_supply)
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
// Call the new convenience function (no terminals, no accounting contexts).
|
|
523
|
+
uint256 reclaimable = _store.currentTotalReclaimableSurplusOf(_projectId, _cashoutAmount, 18, _currency);
|
|
524
|
+
|
|
525
|
+
// Should match the 6-param overload result.
|
|
526
|
+
uint256 assumed =
|
|
527
|
+
JBCashOuts.cashOutFrom(_surplus, _cashoutAmount, _supply, JBConstants.MAX_CASH_OUT_TAX_RATE / 2);
|
|
528
|
+
|
|
529
|
+
assertEq(assumed, reclaimable);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function test_GivenTotalReclaimableWithZeroSurplus() external {
|
|
533
|
+
// it will return zero when there is no surplus
|
|
534
|
+
|
|
535
|
+
JBAccountingContext[] memory _emptyContexts = new JBAccountingContext[](0);
|
|
536
|
+
|
|
537
|
+
// JBRulesets return calldata
|
|
538
|
+
JBRuleset memory _returnedRuleset = JBRuleset({
|
|
539
|
+
cycleNumber: uint48(block.timestamp),
|
|
540
|
+
id: uint48(block.timestamp),
|
|
541
|
+
basedOnId: 0,
|
|
542
|
+
start: uint48(block.timestamp),
|
|
543
|
+
duration: uint32(block.timestamp + 1000),
|
|
544
|
+
weight: 1e18,
|
|
545
|
+
weightCutPercent: 0,
|
|
546
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
547
|
+
metadata: 0
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// mock call to JBRulesets currentOf
|
|
551
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
|
|
552
|
+
|
|
553
|
+
// mock JBDirectory terminalsOf to return the terminal
|
|
554
|
+
IJBTerminal[] memory _terminals = new IJBTerminal[](1);
|
|
555
|
+
_terminals[0] = _terminal;
|
|
556
|
+
mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
|
|
557
|
+
|
|
558
|
+
// mock current surplus as zero
|
|
559
|
+
mockExpect(
|
|
560
|
+
address(_terminal),
|
|
561
|
+
abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
|
|
562
|
+
abi.encode(0)
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
uint256 reclaimable = _store.currentTotalReclaimableSurplusOf(_projectId, _tokenCount, 18, _currency);
|
|
566
|
+
assertEq(0, reclaimable);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function test_GivenTotalReclaimableMatchesSixParamOverload() external whenProjectHasBalance {
|
|
570
|
+
// it will produce the same result as calling the 6-param overload with empty arrays
|
|
571
|
+
|
|
572
|
+
JBAccountingContext[] memory _emptyContexts = new JBAccountingContext[](0);
|
|
573
|
+
|
|
574
|
+
JBRulesetMetadata memory _metadata = JBRulesetMetadata({
|
|
575
|
+
reservedPercent: 0,
|
|
576
|
+
cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE / 2,
|
|
577
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
578
|
+
pausePay: false,
|
|
579
|
+
pauseCreditTransfers: false,
|
|
580
|
+
allowOwnerMinting: false,
|
|
581
|
+
allowSetCustomToken: false,
|
|
582
|
+
allowTerminalMigration: false,
|
|
583
|
+
allowSetTerminals: false,
|
|
584
|
+
ownerMustSendPayouts: false,
|
|
585
|
+
allowSetController: false,
|
|
586
|
+
allowAddAccountingContext: true,
|
|
587
|
+
allowAddPriceFeed: false,
|
|
588
|
+
holdFees: false,
|
|
589
|
+
useTotalSurplusForCashOuts: false,
|
|
590
|
+
useDataHookForPay: false,
|
|
591
|
+
useDataHookForCashOut: false,
|
|
592
|
+
dataHook: address(0),
|
|
593
|
+
metadata: 0
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
|
|
597
|
+
|
|
598
|
+
JBRuleset memory _returnedRuleset = JBRuleset({
|
|
599
|
+
cycleNumber: uint48(block.timestamp),
|
|
600
|
+
id: uint48(block.timestamp),
|
|
601
|
+
basedOnId: 0,
|
|
602
|
+
start: uint48(block.timestamp),
|
|
603
|
+
duration: uint32(block.timestamp + 1000),
|
|
604
|
+
weight: 1e18,
|
|
605
|
+
weightCutPercent: 0,
|
|
606
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
607
|
+
metadata: _packedMetadata
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
uint256 _supply = 1e19;
|
|
611
|
+
uint256 _surplus = 5e17;
|
|
612
|
+
uint256 _cashoutAmount = 1e18;
|
|
613
|
+
|
|
614
|
+
IJBTerminal[] memory _terminals = new IJBTerminal[](1);
|
|
615
|
+
_terminals[0] = _terminal;
|
|
616
|
+
|
|
617
|
+
// The new overload calls the 6-param via `this`, so JBRulesets.currentOf gets called twice.
|
|
618
|
+
// Mock it to return the same ruleset both times.
|
|
619
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
|
|
620
|
+
|
|
621
|
+
mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
|
|
622
|
+
|
|
623
|
+
mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
|
|
624
|
+
|
|
625
|
+
mockExpect(
|
|
626
|
+
address(_terminal),
|
|
627
|
+
abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
|
|
628
|
+
abi.encode(_surplus)
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
mockExpect(
|
|
632
|
+
address(_controller),
|
|
633
|
+
abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
|
|
634
|
+
abi.encode(_supply)
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
// Call the new convenience function.
|
|
638
|
+
uint256 reclaimableDefault = _store.currentTotalReclaimableSurplusOf(_projectId, _cashoutAmount, 18, _currency);
|
|
639
|
+
|
|
640
|
+
// Re-mock for the 6-param call (mocks are consumed).
|
|
641
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
|
|
642
|
+
mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
|
|
643
|
+
mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
|
|
644
|
+
mockExpect(
|
|
645
|
+
address(_terminal),
|
|
646
|
+
abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
|
|
647
|
+
abi.encode(_surplus)
|
|
648
|
+
);
|
|
649
|
+
mockExpect(
|
|
650
|
+
address(_controller),
|
|
651
|
+
abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
|
|
652
|
+
abi.encode(_supply)
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
// Call the 6-param overload with empty arrays.
|
|
656
|
+
uint256 reclaimableExplicit = _store.currentReclaimableSurplusOf(
|
|
657
|
+
_projectId, _cashoutAmount, new IJBTerminal[](0), new JBAccountingContext[](0), 18, _currency
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
assertEq(reclaimableDefault, reclaimableExplicit);
|
|
661
|
+
}
|
|
447
662
|
}
|