@bananapus/core-v6 0.0.16 → 0.0.18
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/ADMINISTRATION.md +1 -1
- package/ARCHITECTURE.md +2 -1
- package/AUDIT_INSTRUCTIONS.md +342 -0
- package/CHANGE_LOG.md +400 -0
- package/README.md +4 -4
- package/RISKS.md +171 -50
- package/SKILLS.md +9 -6
- package/USER_JOURNEYS.md +622 -0
- package/package.json +2 -2
- package/script/DeployPeriphery.s.sol +7 -1
- package/src/JBController.sol +5 -0
- package/src/JBDeadline.sol +3 -0
- package/src/JBDirectory.sol +2 -1
- package/src/JBMultiTerminal.sol +50 -9
- package/src/JBPermissions.sol +2 -0
- package/src/JBPrices.sol +8 -2
- package/src/JBRulesets.sol +3 -0
- package/src/JBSplits.sol +9 -5
- package/src/JBTerminalStore.sol +54 -47
- package/src/JBTokens.sol +3 -0
- package/src/interfaces/IJBTerminalStore.sol +3 -0
- package/src/libraries/JBFees.sol +2 -0
- package/src/libraries/JBMetadataResolver.sol +17 -4
- package/src/structs/JBBeforeCashOutRecordedContext.sol +4 -0
- package/test/TestAuditResponseDesignProofs.sol +434 -0
- package/test/TestDataHookFuzzing.sol +520 -0
- package/test/TestFeeFreeCashOutBypass.sol +617 -0
- package/test/TestL2SequencerPriceFeed.sol +292 -0
- package/test/TestMetadataOffsetOverflow.sol +179 -0
- package/test/TestMultiTerminalSurplus.sol +348 -0
- package/test/TestPermit2DataHook.t.sol +360 -0
- package/test/TestRulesetQueueing.sol +1 -2
- package/test/TestRulesetWeightCaching.sol +122 -124
- package/test/WeirdTokenTests.t.sol +37 -0
- package/test/regression/HoldFeesCashOutReserved.t.sol +415 -0
- package/test/regression/WeightCacheBoundary.t.sol +291 -0
- package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +2 -2
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +18 -17
- package/test/units/static/JBMultiTerminal/TestPay.sol +6 -4
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +206 -18
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +280 -0
- package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +55 -12
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +72 -0
- package/docs/book.css +0 -13
- package/docs/book.toml +0 -12
- package/docs/solidity.min.js +0 -74
- package/docs/src/README.md +0 -703
- package/docs/src/SUMMARY.md +0 -94
- package/docs/src/src/JBChainlinkV3PriceFeed.sol/contract.JBChainlinkV3PriceFeed.md +0 -83
- package/docs/src/src/JBChainlinkV3SequencerPriceFeed.sol/contract.JBChainlinkV3SequencerPriceFeed.md +0 -88
- package/docs/src/src/JBController.sol/contract.JBController.md +0 -1121
- package/docs/src/src/JBDeadline.sol/contract.JBDeadline.md +0 -84
- package/docs/src/src/JBDirectory.sol/contract.JBDirectory.md +0 -294
- package/docs/src/src/JBERC20.sol/contract.JBERC20.md +0 -190
- package/docs/src/src/JBFeelessAddresses.sol/contract.JBFeelessAddresses.md +0 -80
- package/docs/src/src/JBFundAccessLimits.sol/contract.JBFundAccessLimits.md +0 -253
- package/docs/src/src/JBMultiTerminal.sol/contract.JBMultiTerminal.md +0 -1472
- package/docs/src/src/JBPermissions.sol/contract.JBPermissions.md +0 -199
- package/docs/src/src/JBPrices.sol/contract.JBPrices.md +0 -154
- package/docs/src/src/JBProjects.sol/contract.JBProjects.md +0 -131
- package/docs/src/src/JBRulesets.sol/contract.JBRulesets.md +0 -677
- package/docs/src/src/JBSplits.sol/contract.JBSplits.md +0 -237
- package/docs/src/src/JBTerminalStore.sol/contract.JBTerminalStore.md +0 -591
- package/docs/src/src/JBTokens.sol/contract.JBTokens.md +0 -353
- package/docs/src/src/README.md +0 -25
- package/docs/src/src/abstract/JBControlled.sol/abstract.JBControlled.md +0 -64
- package/docs/src/src/abstract/JBPermissioned.sol/abstract.JBPermissioned.md +0 -84
- package/docs/src/src/abstract/README.md +0 -5
- package/docs/src/src/enums/JBApprovalStatus.sol/enum.JBApprovalStatus.md +0 -17
- package/docs/src/src/enums/README.md +0 -4
- package/docs/src/src/interfaces/IJBCashOutHook.sol/interface.IJBCashOutHook.md +0 -29
- package/docs/src/src/interfaces/IJBCashOutTerminal.sol/interface.IJBCashOutTerminal.md +0 -57
- package/docs/src/src/interfaces/IJBControlled.sol/interface.IJBControlled.md +0 -12
- package/docs/src/src/interfaces/IJBController.sol/interface.IJBController.md +0 -334
- package/docs/src/src/interfaces/IJBDirectory.sol/interface.IJBDirectory.md +0 -108
- package/docs/src/src/interfaces/IJBDirectoryAccessControl.sol/interface.IJBDirectoryAccessControl.md +0 -19
- package/docs/src/src/interfaces/IJBFeeTerminal.sol/interface.IJBFeeTerminal.md +0 -91
- package/docs/src/src/interfaces/IJBFeelessAddresses.sol/interface.IJBFeelessAddresses.md +0 -26
- package/docs/src/src/interfaces/IJBFundAccessLimits.sol/interface.IJBFundAccessLimits.md +0 -88
- package/docs/src/src/interfaces/IJBMigratable.sol/interface.IJBMigratable.md +0 -29
- package/docs/src/src/interfaces/IJBMultiTerminal.sol/interface.IJBMultiTerminal.md +0 -50
- package/docs/src/src/interfaces/IJBPayHook.sol/interface.IJBPayHook.md +0 -28
- package/docs/src/src/interfaces/IJBPayoutTerminal.sol/interface.IJBPayoutTerminal.md +0 -105
- package/docs/src/src/interfaces/IJBPermissioned.sol/interface.IJBPermissioned.md +0 -12
- package/docs/src/src/interfaces/IJBPermissions.sol/interface.IJBPermissions.md +0 -74
- package/docs/src/src/interfaces/IJBPermitTerminal.sol/interface.IJBPermitTerminal.md +0 -15
- package/docs/src/src/interfaces/IJBPriceFeed.sol/interface.IJBPriceFeed.md +0 -12
- package/docs/src/src/interfaces/IJBPrices.sol/interface.IJBPrices.md +0 -74
- package/docs/src/src/interfaces/IJBProjectUriRegistry.sol/interface.IJBProjectUriRegistry.md +0 -19
- package/docs/src/src/interfaces/IJBProjects.sol/interface.IJBProjects.md +0 -49
- package/docs/src/src/interfaces/IJBRulesetApprovalHook.sol/interface.IJBRulesetApprovalHook.md +0 -35
- package/docs/src/src/interfaces/IJBRulesetDataHook.sol/interface.IJBRulesetDataHook.md +0 -97
- package/docs/src/src/interfaces/IJBRulesets.sol/interface.IJBRulesets.md +0 -165
- package/docs/src/src/interfaces/IJBSplitHook.sol/interface.IJBSplitHook.md +0 -31
- package/docs/src/src/interfaces/IJBSplits.sol/interface.IJBSplits.md +0 -35
- package/docs/src/src/interfaces/IJBTerminal.sol/interface.IJBTerminal.md +0 -141
- package/docs/src/src/interfaces/IJBTerminalStore.sol/interface.IJBTerminalStore.md +0 -198
- package/docs/src/src/interfaces/IJBToken.sol/interface.IJBToken.md +0 -54
- package/docs/src/src/interfaces/IJBTokenUriResolver.sol/interface.IJBTokenUriResolver.md +0 -12
- package/docs/src/src/interfaces/IJBTokens.sol/interface.IJBTokens.md +0 -151
- package/docs/src/src/interfaces/README.md +0 -33
- package/docs/src/src/libraries/JBCashOuts.sol/library.JBCashOuts.md +0 -40
- package/docs/src/src/libraries/JBConstants.sol/library.JBConstants.md +0 -52
- package/docs/src/src/libraries/JBCurrencyIds.sol/library.JBCurrencyIds.md +0 -19
- package/docs/src/src/libraries/JBFees.sol/library.JBFees.md +0 -52
- package/docs/src/src/libraries/JBFixedPointNumber.sol/library.JBFixedPointNumber.md +0 -12
- package/docs/src/src/libraries/JBMetadataResolver.sol/library.JBMetadataResolver.md +0 -242
- package/docs/src/src/libraries/JBRulesetMetadataResolver.sol/library.JBRulesetMetadataResolver.md +0 -180
- package/docs/src/src/libraries/JBSplitGroupIds.sol/library.JBSplitGroupIds.md +0 -14
- package/docs/src/src/libraries/JBSurplus.sol/library.JBSurplus.md +0 -44
- package/docs/src/src/libraries/README.md +0 -12
- package/docs/src/src/periphery/JBDeadline1Day.sol/contract.JBDeadline1Day.md +0 -15
- package/docs/src/src/periphery/JBDeadline3Days.sol/contract.JBDeadline3Days.md +0 -15
- package/docs/src/src/periphery/JBDeadline3Hours.sol/contract.JBDeadline3Hours.md +0 -15
- package/docs/src/src/periphery/JBDeadline7Days.sol/contract.JBDeadline7Days.md +0 -15
- package/docs/src/src/periphery/JBMatchingPriceFeed.sol/contract.JBMatchingPriceFeed.md +0 -22
- package/docs/src/src/periphery/README.md +0 -8
- package/docs/src/src/structs/JBAccountingContext.sol/struct.JBAccountingContext.md +0 -20
- package/docs/src/src/structs/JBAfterCashOutRecordedContext.sol/struct.JBAfterCashOutRecordedContext.md +0 -43
- package/docs/src/src/structs/JBAfterPayRecordedContext.sol/struct.JBAfterPayRecordedContext.md +0 -42
- package/docs/src/src/structs/JBBeforeCashOutRecordedContext.sol/struct.JBBeforeCashOutRecordedContext.md +0 -45
- package/docs/src/src/structs/JBBeforePayRecordedContext.sol/struct.JBBeforePayRecordedContext.md +0 -41
- package/docs/src/src/structs/JBCashOutHookSpecification.sol/struct.JBCashOutHookSpecification.md +0 -22
- package/docs/src/src/structs/JBCurrencyAmount.sol/struct.JBCurrencyAmount.md +0 -17
- package/docs/src/src/structs/JBFee.sol/struct.JBFee.md +0 -20
- package/docs/src/src/structs/JBFundAccessLimitGroup.sol/struct.JBFundAccessLimitGroup.md +0 -39
- package/docs/src/src/structs/JBPayHookSpecification.sol/struct.JBPayHookSpecification.md +0 -22
- package/docs/src/src/structs/JBPermissionsData.sol/struct.JBPermissionsData.md +0 -21
- package/docs/src/src/structs/JBRuleset.sol/struct.JBRuleset.md +0 -55
- package/docs/src/src/structs/JBRulesetConfig.sol/struct.JBRulesetConfig.md +0 -51
- package/docs/src/src/structs/JBRulesetMetadata.sol/struct.JBRulesetMetadata.md +0 -79
- package/docs/src/src/structs/JBRulesetWeightCache.sol/struct.JBRulesetWeightCache.md +0 -16
- package/docs/src/src/structs/JBRulesetWithMetadata.sol/struct.JBRulesetWithMetadata.md +0 -16
- package/docs/src/src/structs/JBSingleAllowance.sol/struct.JBSingleAllowance.md +0 -26
- package/docs/src/src/structs/JBSplit.sol/struct.JBSplit.md +0 -49
- package/docs/src/src/structs/JBSplitGroup.sol/struct.JBSplitGroup.md +0 -17
- package/docs/src/src/structs/JBSplitHookContext.sol/struct.JBSplitHookContext.md +0 -29
- package/docs/src/src/structs/JBTerminalConfig.sol/struct.JBTerminalConfig.md +0 -16
- package/docs/src/src/structs/JBTokenAmount.sol/struct.JBTokenAmount.md +0 -23
- package/docs/src/src/structs/README.md +0 -25
|
@@ -1,28 +1,216 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {JBMultiTerminal} from "../../../../src/JBMultiTerminal.sol";
|
|
5
|
+
import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
|
|
6
|
+
import {IJBFeeTerminal} from "../../../../src/interfaces/IJBFeeTerminal.sol";
|
|
7
|
+
import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
|
|
8
|
+
import {IJBPermissions} from "../../../../src/interfaces/IJBPermissions.sol";
|
|
9
|
+
import {IJBProjects} from "../../../../src/interfaces/IJBProjects.sol";
|
|
10
|
+
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
11
|
+
import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
|
|
12
|
+
import {IJBSplits} from "../../../../src/interfaces/IJBSplits.sol";
|
|
13
|
+
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
14
|
+
import {IJBTokens} from "../../../../src/interfaces/IJBTokens.sol";
|
|
15
|
+
import {JBFees} from "../../../../src/libraries/JBFees.sol";
|
|
16
|
+
import {JBFee} from "../../../../src/structs/JBFee.sol";
|
|
17
|
+
import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
|
|
18
|
+
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
19
|
+
import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
|
|
20
|
+
import {JBTest} from "../../../helpers/JBTest.sol";
|
|
5
21
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
22
|
+
/// @dev Harness that exposes internal held fee storage for direct manipulation in tests.
|
|
23
|
+
contract ForTest_JBMultiTerminal is JBMultiTerminal {
|
|
24
|
+
constructor(
|
|
25
|
+
IJBFeelessAddresses feelessAddresses,
|
|
26
|
+
IJBPermissions permissions,
|
|
27
|
+
IJBProjects projects,
|
|
28
|
+
IJBSplits splits,
|
|
29
|
+
IJBTerminalStore store,
|
|
30
|
+
IJBTokens tokens,
|
|
31
|
+
IPermit2 permit2,
|
|
32
|
+
address trustedForwarder
|
|
33
|
+
)
|
|
34
|
+
JBMultiTerminal(feelessAddresses, permissions, projects, splits, store, tokens, permit2, trustedForwarder)
|
|
35
|
+
{}
|
|
11
36
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
37
|
+
function forTestAddHeldFee(uint256 projectId, address token, JBFee memory fee) external {
|
|
38
|
+
_heldFeesOf[projectId][token].push(fee);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function forTestSetNextHeldFeeIndex(uint256 projectId, address token, uint256 index) external {
|
|
42
|
+
_nextHeldFeeIndexOf[projectId][token] = index;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
contract TestProcessHeldFeesOf_Local is JBTest {
|
|
47
|
+
// Target Contract (harness)
|
|
48
|
+
ForTest_JBMultiTerminal public _terminal;
|
|
49
|
+
|
|
50
|
+
// Mocks
|
|
51
|
+
IJBPermissions public permissions = IJBPermissions(makeAddr("permissions"));
|
|
52
|
+
IJBProjects public projects = IJBProjects(makeAddr("projects"));
|
|
53
|
+
IJBDirectory public directory = IJBDirectory(makeAddr("directory"));
|
|
54
|
+
IJBRulesets public rulesets = IJBRulesets(makeAddr("rulesets"));
|
|
55
|
+
IJBTokens public tokens = IJBTokens(makeAddr("tokens"));
|
|
56
|
+
IJBSplits public splits = IJBSplits(makeAddr("splits"));
|
|
57
|
+
IJBTerminalStore public store = IJBTerminalStore(makeAddr("store"));
|
|
58
|
+
IJBFeelessAddresses public feelessAddresses = IJBFeelessAddresses(makeAddr("feeless"));
|
|
59
|
+
IPermit2 public permit2 = IPermit2(makeAddr("permit2"));
|
|
60
|
+
address trustedForwarder = makeAddr("forwarder");
|
|
61
|
+
|
|
62
|
+
uint256 _projectId = 2;
|
|
63
|
+
address _mockToken = makeAddr("token");
|
|
64
|
+
address _beneficiary = makeAddr("beneficiary");
|
|
65
|
+
|
|
66
|
+
function setUp() public {
|
|
67
|
+
// Constructor will call to find directory and rulesets from the terminal store
|
|
68
|
+
mockExpect(address(store), abi.encodeCall(IJBTerminalStore.DIRECTORY, ()), abi.encode(address(directory)));
|
|
69
|
+
mockExpect(address(store), abi.encodeCall(IJBTerminalStore.RULESETS, ()), abi.encode(address(rulesets)));
|
|
70
|
+
|
|
71
|
+
_terminal = new ForTest_JBMultiTerminal(
|
|
72
|
+
feelessAddresses, permissions, projects, splits, store, tokens, permit2, trustedForwarder
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function test_WhenHeldFeeUnlockTimestampGTBlocktimestamp() external {
|
|
77
|
+
// it will not process the fee (fee remains held)
|
|
78
|
+
|
|
79
|
+
// Add a held fee with unlockTimestamp in the future
|
|
80
|
+
uint48 futureTimestamp = uint48(block.timestamp + 1000);
|
|
81
|
+
_terminal.forTestAddHeldFee(
|
|
82
|
+
_projectId, _mockToken, JBFee({amount: 100, beneficiary: _beneficiary, unlockTimestamp: futureTimestamp})
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Mock the directory call to find the fee terminal (project 1 is the fee beneficiary)
|
|
86
|
+
mockExpect(
|
|
87
|
+
address(directory),
|
|
88
|
+
abi.encodeCall(IJBDirectory.primaryTerminalOf, (1, _mockToken)),
|
|
89
|
+
abi.encode(address(_terminal))
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Call processHeldFeesOf - the fee should NOT be processed because it's still locked
|
|
93
|
+
_terminal.processHeldFeesOf(_projectId, _mockToken, 1);
|
|
94
|
+
|
|
95
|
+
// Verify the fee is still held
|
|
96
|
+
JBFee[] memory remaining = _terminal.heldFeesOf(_projectId, _mockToken, 10);
|
|
97
|
+
assertEq(remaining.length, 1, "fee should still be held");
|
|
98
|
+
assertEq(remaining[0].amount, 100, "fee amount should be unchanged");
|
|
99
|
+
assertEq(remaining[0].unlockTimestamp, futureTimestamp, "unlock timestamp should be unchanged");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
modifier whenHeldFeeIsUnlocked() {
|
|
103
|
+
_;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function test_GivenExecuteProcessFeeSucceeds() external whenHeldFeeIsUnlocked {
|
|
107
|
+
// it will process the fee and emit ProcessFee
|
|
108
|
+
|
|
109
|
+
// Add a held fee that is already unlocked
|
|
110
|
+
uint48 pastTimestamp = uint48(block.timestamp - 1);
|
|
111
|
+
uint256 heldAmount = 1000;
|
|
112
|
+
_terminal.forTestAddHeldFee(
|
|
113
|
+
_projectId,
|
|
114
|
+
_mockToken,
|
|
115
|
+
JBFee({amount: heldAmount, beneficiary: _beneficiary, unlockTimestamp: pastTimestamp})
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// The fee amount that will be calculated from the held amount
|
|
119
|
+
uint256 expectedFeeAmount = JBFees.feeAmountFrom({amountBeforeFee: heldAmount, feePercent: _terminal.FEE()});
|
|
120
|
+
|
|
121
|
+
// Mock the directory call to find the fee terminal - return _terminal itself so it uses internal _pay
|
|
122
|
+
mockExpect(
|
|
123
|
+
address(directory),
|
|
124
|
+
abi.encodeCall(IJBDirectory.primaryTerminalOf, (1, _mockToken)),
|
|
125
|
+
abi.encode(address(_terminal))
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Mock executeProcessFee: when the terminal calls itself, it will call recordPaymentFrom on the store.
|
|
129
|
+
// Since executeProcessFee is external and calls pay on the feeTerminal (which is _terminal itself),
|
|
130
|
+
// we need to mock the internal pay path: recordPaymentFrom on the store.
|
|
131
|
+
// The token amount struct for the fee payment
|
|
132
|
+
// Note: accounting context for project 1 on _mockToken is unset, so decimals=0 and currency=0.
|
|
133
|
+
vm.mockCall(
|
|
134
|
+
address(store),
|
|
135
|
+
abi.encodeWithSelector(IJBTerminalStore.recordPaymentFrom.selector),
|
|
136
|
+
abi.encode(
|
|
137
|
+
JBRuleset({
|
|
138
|
+
cycleNumber: 1,
|
|
139
|
+
id: 1,
|
|
140
|
+
basedOnId: 0,
|
|
141
|
+
start: 0,
|
|
142
|
+
duration: 0,
|
|
143
|
+
weight: 0,
|
|
144
|
+
weightCutPercent: 0,
|
|
145
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
146
|
+
metadata: 0
|
|
147
|
+
}),
|
|
148
|
+
uint256(0),
|
|
149
|
+
new JBPayHookSpecification[](0)
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Expect ProcessFee event
|
|
154
|
+
vm.expectEmit();
|
|
155
|
+
emit IJBFeeTerminal.ProcessFee({
|
|
156
|
+
projectId: _projectId,
|
|
157
|
+
token: _mockToken,
|
|
158
|
+
amount: expectedFeeAmount,
|
|
159
|
+
wasHeld: true,
|
|
160
|
+
beneficiary: _beneficiary,
|
|
161
|
+
caller: address(this)
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
_terminal.processHeldFeesOf(_projectId, _mockToken, 1);
|
|
165
|
+
|
|
166
|
+
// Verify held fees are cleaned up
|
|
167
|
+
JBFee[] memory remaining = _terminal.heldFeesOf(_projectId, _mockToken, 10);
|
|
168
|
+
assertEq(remaining.length, 0, "held fees should be empty after processing");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function test_GivenExecuteProcessFeeFails() external whenHeldFeeIsUnlocked {
|
|
172
|
+
// it will readd balance and emit FeeReverted
|
|
173
|
+
|
|
174
|
+
// Add a held fee that is already unlocked
|
|
175
|
+
uint48 pastTimestamp = uint48(block.timestamp - 1);
|
|
176
|
+
uint256 heldAmount = 1000;
|
|
177
|
+
_terminal.forTestAddHeldFee(
|
|
178
|
+
_projectId,
|
|
179
|
+
_mockToken,
|
|
180
|
+
JBFee({amount: heldAmount, beneficiary: _beneficiary, unlockTimestamp: pastTimestamp})
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// The fee amount that will be calculated from the held amount
|
|
184
|
+
uint256 expectedFeeAmount = JBFees.feeAmountFrom({amountBeforeFee: heldAmount, feePercent: _terminal.FEE()});
|
|
185
|
+
|
|
186
|
+
// Mock the directory call to find the fee terminal - return address(0) which will cause
|
|
187
|
+
// executeProcessFee to revert with FeeTerminalNotFound
|
|
188
|
+
mockExpect(
|
|
189
|
+
address(directory), abi.encodeCall(IJBDirectory.primaryTerminalOf, (1, _mockToken)), abi.encode(address(0))
|
|
190
|
+
);
|
|
15
191
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
192
|
+
// Mock the recordAddedBalanceFor call that happens on fee revert (balance returned to project)
|
|
193
|
+
mockExpect(
|
|
194
|
+
address(store),
|
|
195
|
+
abi.encodeCall(IJBTerminalStore.recordAddedBalanceFor, (_projectId, _mockToken, expectedFeeAmount)),
|
|
196
|
+
abi.encode()
|
|
197
|
+
);
|
|
19
198
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
199
|
+
// Expect FeeReverted event
|
|
200
|
+
vm.expectEmit(true, true, true, false);
|
|
201
|
+
emit IJBFeeTerminal.FeeReverted({
|
|
202
|
+
projectId: _projectId,
|
|
203
|
+
token: _mockToken,
|
|
204
|
+
feeProjectId: 1,
|
|
205
|
+
amount: expectedFeeAmount,
|
|
206
|
+
reason: "",
|
|
207
|
+
caller: address(this)
|
|
208
|
+
});
|
|
23
209
|
|
|
24
|
-
|
|
25
|
-
// it will readd balance and emit FeeReverted
|
|
26
|
-
} */
|
|
210
|
+
_terminal.processHeldFeesOf(_projectId, _mockToken, 1);
|
|
27
211
|
|
|
212
|
+
// Verify held fees are cleaned up (entry was deleted even though fee processing failed)
|
|
213
|
+
JBFee[] memory remaining = _terminal.heldFeesOf(_projectId, _mockToken, 10);
|
|
214
|
+
assertEq(remaining.length, 0, "held fees should be empty after failed processing");
|
|
28
215
|
}
|
|
216
|
+
}
|
|
@@ -4,6 +4,7 @@ pragma solidity 0.8.26;
|
|
|
4
4
|
import {JBMultiTerminal} from "../../../../src/JBMultiTerminal.sol";
|
|
5
5
|
import {IJBController} from "../../../../src/interfaces/IJBController.sol";
|
|
6
6
|
import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
|
|
7
|
+
import {IJBFeeTerminal} from "../../../../src/interfaces/IJBFeeTerminal.sol";
|
|
7
8
|
import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
|
|
8
9
|
import {IJBPayoutTerminal} from "../../../../src/interfaces/IJBPayoutTerminal.sol";
|
|
9
10
|
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
@@ -12,6 +13,9 @@ import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol"
|
|
|
12
13
|
import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
|
|
13
14
|
import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
|
|
14
15
|
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
16
|
+
import {JBRulesetMetadata} from "../../../../src/structs/JBRulesetMetadata.sol";
|
|
17
|
+
import {JBRulesetMetadataResolver} from "../../../../src/libraries/JBRulesetMetadataResolver.sol";
|
|
18
|
+
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
15
19
|
import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
|
|
16
20
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
17
21
|
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
@@ -19,6 +23,8 @@ import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
|
19
23
|
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
20
24
|
|
|
21
25
|
contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
|
|
26
|
+
using JBRulesetMetadataResolver for JBRulesetMetadata;
|
|
27
|
+
|
|
22
28
|
uint256 _projectId = 1;
|
|
23
29
|
|
|
24
30
|
function setUp() public {
|
|
@@ -310,10 +316,284 @@ contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
|
|
|
310
316
|
|
|
311
317
|
function test_GivenRulesetHoldFeesEQTrue() external whenMsgSenderDNEQFeeless {
|
|
312
318
|
// it will hold fees and emit HoldFee
|
|
319
|
+
address mockToken = makeAddr("token");
|
|
320
|
+
address beneficiary = makeAddr("bene");
|
|
321
|
+
|
|
322
|
+
// Mock controller (needed for addAccountingContextsFor permission check)
|
|
323
|
+
address controller = makeAddr("controller");
|
|
324
|
+
|
|
325
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
326
|
+
uint32 currencyId = uint32(uint160(mockToken));
|
|
327
|
+
|
|
328
|
+
// Mock controllerOf for addAccountingContextsFor permission check
|
|
329
|
+
mockExpect(
|
|
330
|
+
address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(address(controller))
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
// mock owner call
|
|
334
|
+
mockExpect(address(projects), abi.encodeCall(IERC721.ownerOf, (_projectId)), abi.encode(address(this)));
|
|
335
|
+
|
|
336
|
+
// Build a ruleset with holdFees=true via packed metadata
|
|
337
|
+
JBRulesetMetadata memory _rulesMetadata = JBRulesetMetadata({
|
|
338
|
+
reservedPercent: 0,
|
|
339
|
+
cashOutTaxRate: 0,
|
|
340
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
341
|
+
pausePay: false,
|
|
342
|
+
pauseCreditTransfers: false,
|
|
343
|
+
allowOwnerMinting: false,
|
|
344
|
+
allowSetCustomToken: false,
|
|
345
|
+
allowTerminalMigration: false,
|
|
346
|
+
allowSetTerminals: false,
|
|
347
|
+
ownerMustSendPayouts: false,
|
|
348
|
+
allowSetController: false,
|
|
349
|
+
allowAddAccountingContext: true,
|
|
350
|
+
allowAddPriceFeed: false,
|
|
351
|
+
holdFees: true,
|
|
352
|
+
useTotalSurplusForCashOuts: false,
|
|
353
|
+
useDataHookForPay: false,
|
|
354
|
+
useDataHookForCashOut: false,
|
|
355
|
+
dataHook: address(0),
|
|
356
|
+
metadata: 0
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
uint256 packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_rulesMetadata);
|
|
360
|
+
|
|
361
|
+
JBRuleset memory returnedRuleset = JBRuleset({
|
|
362
|
+
cycleNumber: 1,
|
|
363
|
+
id: 1,
|
|
364
|
+
basedOnId: 0,
|
|
365
|
+
start: 0,
|
|
366
|
+
duration: 0,
|
|
367
|
+
weight: 0,
|
|
368
|
+
weightCutPercent: 0,
|
|
369
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
370
|
+
metadata: packedMetadata
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// mock call to tokens decimals()
|
|
374
|
+
mockExpect(mockToken, abi.encodeCall(IERC20Metadata.decimals, ()), abi.encode(18));
|
|
375
|
+
|
|
376
|
+
// mock call to rulesets currentOf
|
|
377
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(returnedRuleset));
|
|
378
|
+
|
|
379
|
+
// Set up accounting context so the token is recognized
|
|
380
|
+
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
381
|
+
_tokens[0] = JBAccountingContext({token: mockToken, decimals: 18, currency: currencyId});
|
|
382
|
+
|
|
383
|
+
_terminal.addAccountingContextsFor(_projectId, _tokens);
|
|
384
|
+
|
|
385
|
+
// recordUsedAllowance
|
|
386
|
+
mockExpect(
|
|
387
|
+
address(store),
|
|
388
|
+
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, _tokens[0], 100, 0)),
|
|
389
|
+
abi.encode(returnedRuleset, 100)
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
// first feeless check: owner is NOT feeless
|
|
393
|
+
mockExpect(
|
|
394
|
+
address(feelessAddresses), abi.encodeCall(IJBFeelessAddresses.isFeeless, (address(this))), abi.encode(false)
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// second feeless check: beneficiary is NOT feeless
|
|
398
|
+
mockExpect(
|
|
399
|
+
address(feelessAddresses), abi.encodeCall(IJBFeelessAddresses.isFeeless, (beneficiary)), abi.encode(false)
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// Fee is 2.5% of 100 = 2, so net = 98
|
|
403
|
+
mockExpect(mockToken, abi.encodeCall(IERC20.transfer, (beneficiary, 98)), abi.encode(true));
|
|
404
|
+
|
|
405
|
+
// Expect HoldFee event (fee is held, not processed)
|
|
406
|
+
vm.expectEmit(true, true, true, true);
|
|
407
|
+
emit IJBFeeTerminal.HoldFee({
|
|
408
|
+
projectId: _projectId,
|
|
409
|
+
token: mockToken,
|
|
410
|
+
amount: 100,
|
|
411
|
+
fee: 25,
|
|
412
|
+
beneficiary: address(this),
|
|
413
|
+
caller: address(this)
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Expect UseAllowance event
|
|
417
|
+
vm.expectEmit();
|
|
418
|
+
emit IJBPayoutTerminal.UseAllowance({
|
|
419
|
+
rulesetId: returnedRuleset.id,
|
|
420
|
+
rulesetCycleNumber: returnedRuleset.cycleNumber,
|
|
421
|
+
projectId: _projectId,
|
|
422
|
+
beneficiary: beneficiary,
|
|
423
|
+
feeBeneficiary: address(this),
|
|
424
|
+
amount: 100,
|
|
425
|
+
amountPaidOut: 100,
|
|
426
|
+
netAmountPaidOut: 98,
|
|
427
|
+
memo: "",
|
|
428
|
+
caller: address(this)
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
_terminal.useAllowanceOf({
|
|
432
|
+
projectId: _projectId,
|
|
433
|
+
token: mockToken,
|
|
434
|
+
amount: 100,
|
|
435
|
+
currency: 0,
|
|
436
|
+
minTokensPaidOut: 97,
|
|
437
|
+
beneficiary: payable(beneficiary),
|
|
438
|
+
feeBeneficiary: payable(address(this)),
|
|
439
|
+
memo: ""
|
|
440
|
+
});
|
|
313
441
|
}
|
|
314
442
|
|
|
315
443
|
function test_GivenRulesetHoldFeesDNEQTrue() external whenMsgSenderDNEQFeeless {
|
|
316
444
|
// it will not hold fees and emit ProcessFee
|
|
445
|
+
address mockToken = makeAddr("token2");
|
|
446
|
+
address beneficiary = makeAddr("bene2");
|
|
447
|
+
|
|
448
|
+
// Mock controller for mint call on fee payments
|
|
449
|
+
address controller = makeAddr("controller2");
|
|
450
|
+
|
|
451
|
+
// Weight for a fee calculation that would take place in terminal store
|
|
452
|
+
uint112 weight = 1000 * 10 ** 18;
|
|
453
|
+
|
|
454
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
455
|
+
uint32 currencyId = uint32(uint160(mockToken));
|
|
456
|
+
|
|
457
|
+
// Build a ruleset with holdFees=false explicitly via packed metadata
|
|
458
|
+
JBRulesetMetadata memory _rulesMetadata = JBRulesetMetadata({
|
|
459
|
+
reservedPercent: 0,
|
|
460
|
+
cashOutTaxRate: 0,
|
|
461
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
462
|
+
pausePay: false,
|
|
463
|
+
pauseCreditTransfers: false,
|
|
464
|
+
allowOwnerMinting: false,
|
|
465
|
+
allowSetCustomToken: false,
|
|
466
|
+
allowTerminalMigration: false,
|
|
467
|
+
allowSetTerminals: false,
|
|
468
|
+
ownerMustSendPayouts: false,
|
|
469
|
+
allowSetController: false,
|
|
470
|
+
allowAddAccountingContext: true,
|
|
471
|
+
allowAddPriceFeed: false,
|
|
472
|
+
holdFees: false,
|
|
473
|
+
useTotalSurplusForCashOuts: false,
|
|
474
|
+
useDataHookForPay: false,
|
|
475
|
+
useDataHookForCashOut: false,
|
|
476
|
+
dataHook: address(0),
|
|
477
|
+
metadata: 0
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
uint256 packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_rulesMetadata);
|
|
481
|
+
|
|
482
|
+
// Start the cascade of issuing project tokens to the fee beneficiary.
|
|
483
|
+
mockExpect(
|
|
484
|
+
address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(address(controller))
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// mock owner call
|
|
488
|
+
mockExpect(address(projects), abi.encodeCall(IERC721.ownerOf, (_projectId)), abi.encode(address(this)));
|
|
489
|
+
|
|
490
|
+
JBRuleset memory returnedRuleset = JBRuleset({
|
|
491
|
+
cycleNumber: 1,
|
|
492
|
+
id: 1,
|
|
493
|
+
basedOnId: 0,
|
|
494
|
+
start: 0,
|
|
495
|
+
duration: 0,
|
|
496
|
+
weight: weight,
|
|
497
|
+
weightCutPercent: 0,
|
|
498
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
499
|
+
metadata: packedMetadata
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// mock call to tokens decimals()
|
|
503
|
+
mockExpect(mockToken, abi.encodeCall(IERC20Metadata.decimals, ()), abi.encode(18));
|
|
504
|
+
|
|
505
|
+
// mock call to rulesets currentOf
|
|
506
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(returnedRuleset));
|
|
507
|
+
|
|
508
|
+
// Set up accounting context
|
|
509
|
+
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
510
|
+
_tokens[0] = JBAccountingContext({token: mockToken, decimals: 18, currency: currencyId});
|
|
511
|
+
|
|
512
|
+
_terminal.addAccountingContextsFor(_projectId, _tokens);
|
|
513
|
+
|
|
514
|
+
// recordUsedAllowance
|
|
515
|
+
mockExpect(
|
|
516
|
+
address(store),
|
|
517
|
+
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, _tokens[0], 100, 0)),
|
|
518
|
+
abi.encode(returnedRuleset, 100)
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
// first feeless check: false
|
|
522
|
+
mockExpect(
|
|
523
|
+
address(feelessAddresses), abi.encodeCall(IJBFeelessAddresses.isFeeless, (address(this))), abi.encode(false)
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// second feeless check: false
|
|
527
|
+
mockExpect(
|
|
528
|
+
address(feelessAddresses), abi.encodeCall(IJBFeelessAddresses.isFeeless, (beneficiary)), abi.encode(false)
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
// Fee = 2.5% of 100 = 2, net = 98
|
|
532
|
+
mockExpect(mockToken, abi.encodeCall(IERC20.transfer, (beneficiary, 98)), abi.encode(true));
|
|
533
|
+
|
|
534
|
+
// call to find the primary terminal for fee processing
|
|
535
|
+
mockExpect(
|
|
536
|
+
address(directory),
|
|
537
|
+
abi.encodeCall(IJBDirectory.primaryTerminalOf, (1, mockToken)),
|
|
538
|
+
abi.encode(address(_terminal))
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
JBTokenAmount memory tokenContext =
|
|
542
|
+
JBTokenAmount({token: mockToken, decimals: 18, currency: currencyId, value: 2});
|
|
543
|
+
|
|
544
|
+
// mock call to jbterminalstore recordPaymentFrom for the fee payment
|
|
545
|
+
mockExpect(
|
|
546
|
+
address(store),
|
|
547
|
+
abi.encodeCall(
|
|
548
|
+
IJBTerminalStore.recordPaymentFrom,
|
|
549
|
+
(address(_terminal), tokenContext, _projectId, address(this), bytes(abi.encodePacked(_projectId)))
|
|
550
|
+
),
|
|
551
|
+
abi.encode(returnedRuleset, 1, new JBPayHookSpecification[](0))
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
// Return the mint call as minting one project token for paying a fee.
|
|
555
|
+
mockExpect(
|
|
556
|
+
address(controller),
|
|
557
|
+
abi.encodeCall(IJBController.mintTokensOf, (_projectId, 1, address(this), "", true)),
|
|
558
|
+
abi.encode(2)
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
// Expect ProcessFee event (fee is processed immediately, not held)
|
|
562
|
+
vm.expectEmit(true, true, true, true);
|
|
563
|
+
emit IJBFeeTerminal.ProcessFee({
|
|
564
|
+
projectId: _projectId,
|
|
565
|
+
token: mockToken,
|
|
566
|
+
amount: 2,
|
|
567
|
+
wasHeld: false,
|
|
568
|
+
beneficiary: address(this),
|
|
569
|
+
caller: address(this)
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// Expect UseAllowance event
|
|
573
|
+
vm.expectEmit();
|
|
574
|
+
emit IJBPayoutTerminal.UseAllowance({
|
|
575
|
+
rulesetId: returnedRuleset.id,
|
|
576
|
+
rulesetCycleNumber: returnedRuleset.cycleNumber,
|
|
577
|
+
projectId: _projectId,
|
|
578
|
+
beneficiary: beneficiary,
|
|
579
|
+
feeBeneficiary: address(this),
|
|
580
|
+
amount: 100,
|
|
581
|
+
amountPaidOut: 100,
|
|
582
|
+
netAmountPaidOut: 98,
|
|
583
|
+
memo: "",
|
|
584
|
+
caller: address(this)
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
_terminal.useAllowanceOf({
|
|
588
|
+
projectId: _projectId,
|
|
589
|
+
token: mockToken,
|
|
590
|
+
amount: 100,
|
|
591
|
+
currency: 0,
|
|
592
|
+
minTokensPaidOut: 97,
|
|
593
|
+
beneficiary: payable(beneficiary),
|
|
594
|
+
feeBeneficiary: payable(address(this)),
|
|
595
|
+
memo: ""
|
|
596
|
+
});
|
|
317
597
|
}
|
|
318
598
|
|
|
319
599
|
function test_WhenTokenEQNATIVE_TOKEN() external {
|