@bananapus/721-hook-v6 0.0.32 → 0.0.34
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/USER_JOURNEYS.md +11 -0
- package/package.json +3 -3
- package/script/Deploy.s.sol +53 -19
- package/src/JB721Checkpoints.sol +92 -0
- package/src/JB721CheckpointsDeployer.sol +45 -0
- package/src/JB721TiersHook.sol +90 -116
- package/src/abstract/JB721Hook.sol +5 -0
- package/src/interfaces/IJB721Checkpoints.sol +34 -0
- package/src/interfaces/IJB721CheckpointsDeployer.sol +20 -0
- package/src/interfaces/IJB721TiersHook.sol +8 -0
- package/src/libraries/JB721Constants.sol +6 -0
- package/src/libraries/JB721TiersHookLib.sol +353 -146
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +11 -1
- package/test/Fork.t.sol +11 -2
- package/test/TestAuditGaps.sol +1 -1
- package/test/TestCheckpoints.t.sol +329 -0
- package/test/audit/CodexNemesisRepoFindings.t.sol +270 -0
- package/test/audit/CodexRetroactiveReserveBeneficiaryDilution.t.sol +161 -0
- package/test/audit/CodexSplitCreditsMismatch.t.sol +2 -1
- package/test/audit/CrossCurrencySplitNoPrices.t.sol +1 -0
- package/test/audit/SameCurrencyDecimalMismatch.t.sol +249 -0
- package/test/audit/SplitFailureRedistribution.t.sol +2 -1
- package/test/fork/ERC20CashOutFork.t.sol +11 -2
- package/test/fork/ERC20TierSplitFork.t.sol +11 -2
- package/test/fork/IssueTokensForSplitsFork.t.sol +11 -2
- package/test/regression/BrokenTerminalDoesNotDos.t.sol +2 -2
- package/test/regression/SplitDistributionBugs.t.sol +5 -5
- package/test/regression/SplitNoBeneficiary.t.sol +1 -1
- package/test/unit/AuditFixes_Unit.t.sol +5 -5
- package/test/unit/pay_CrossCurrency_Unit.t.sol +1 -0
- package/test/unit/pay_Unit.t.sol +1 -0
- package/test/unit/redeem_Unit.t.sol +3 -3
- package/test/unit/relayBeneficiary_Unit.t.sol +182 -0
- package/test/unit/splitHookDistribution_Unit.t.sol +6 -6
- package/test/unit/tierSplitRouting_Unit.t.sol +2 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
+
import "../utils/UnitTestSetup.sol";
|
|
6
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
7
|
+
import {JBAfterPayRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterPayRecordedContext.sol";
|
|
8
|
+
import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
|
|
9
|
+
|
|
10
|
+
contract CodexRetroactiveReserveBeneficiaryDilution is UnitTestSetup {
|
|
11
|
+
function _buildPayMetadata(address hookAddress, uint16[] memory tierIdsToMint)
|
|
12
|
+
internal
|
|
13
|
+
view
|
|
14
|
+
returns (bytes memory)
|
|
15
|
+
{
|
|
16
|
+
bytes[] memory data = new bytes[](1);
|
|
17
|
+
data[0] = abi.encode(true, tierIdsToMint);
|
|
18
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
19
|
+
ids[0] = metadataHelper.getId("pay", hookAddress);
|
|
20
|
+
return metadataHelper.createMetadata(ids, data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function _afterPayContext(
|
|
24
|
+
address hookAddress,
|
|
25
|
+
uint256 amountValue,
|
|
26
|
+
uint16[] memory tierIdsToMint
|
|
27
|
+
)
|
|
28
|
+
internal
|
|
29
|
+
view
|
|
30
|
+
returns (JBAfterPayRecordedContext memory)
|
|
31
|
+
{
|
|
32
|
+
return JBAfterPayRecordedContext({
|
|
33
|
+
payer: beneficiary,
|
|
34
|
+
projectId: projectId,
|
|
35
|
+
rulesetId: 0,
|
|
36
|
+
amount: JBTokenAmount({
|
|
37
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
38
|
+
value: amountValue,
|
|
39
|
+
decimals: 18,
|
|
40
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
41
|
+
}),
|
|
42
|
+
forwardedAmount: JBTokenAmount({
|
|
43
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
44
|
+
value: 0,
|
|
45
|
+
decimals: 18,
|
|
46
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
47
|
+
}),
|
|
48
|
+
weight: 10e18,
|
|
49
|
+
newlyIssuedTokenCount: 0,
|
|
50
|
+
beneficiary: beneficiary,
|
|
51
|
+
hookMetadata: "",
|
|
52
|
+
payerMetadata: _buildPayMetadata(hookAddress, tierIdsToMint)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function test_adjustTier_can_retroactively_create_reserves_for_existing_supply() public {
|
|
57
|
+
ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
|
|
58
|
+
|
|
59
|
+
JB721TierConfig[] memory tier1 = new JB721TierConfig[](1);
|
|
60
|
+
tier1[0] = JB721TierConfig({
|
|
61
|
+
price: 1 ether,
|
|
62
|
+
initialSupply: 100,
|
|
63
|
+
votingUnits: 0,
|
|
64
|
+
reserveFrequency: 2,
|
|
65
|
+
reserveBeneficiary: address(0),
|
|
66
|
+
encodedIPFSUri: bytes32(uint256(0x1234)),
|
|
67
|
+
category: 1,
|
|
68
|
+
discountPercent: 0,
|
|
69
|
+
flags: JB721TierConfigFlags({
|
|
70
|
+
allowOwnerMint: false,
|
|
71
|
+
useReserveBeneficiaryAsDefault: false,
|
|
72
|
+
transfersPausable: false,
|
|
73
|
+
useVotingUnits: false,
|
|
74
|
+
cantBeRemoved: false,
|
|
75
|
+
cantIncreaseDiscountPercent: false,
|
|
76
|
+
cantBuyWithCredits: false
|
|
77
|
+
}),
|
|
78
|
+
splitPercent: 0,
|
|
79
|
+
splits: new JBSplit[](0)
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
vm.prank(owner);
|
|
83
|
+
testHook.adjustTiers(tier1, new uint256[](0));
|
|
84
|
+
|
|
85
|
+
assertEq(
|
|
86
|
+
testHook.STORE().numberOfPendingReservesFor(address(testHook), 1),
|
|
87
|
+
0,
|
|
88
|
+
"no beneficiary means no reserves are pending"
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
vm.mockCall(
|
|
92
|
+
mockJBDirectory,
|
|
93
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
94
|
+
abi.encode(true)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
uint16[] memory mintIds = new uint16[](3);
|
|
98
|
+
mintIds[0] = 1;
|
|
99
|
+
mintIds[1] = 1;
|
|
100
|
+
mintIds[2] = 1;
|
|
101
|
+
|
|
102
|
+
JBAfterPayRecordedContext memory payContext = _afterPayContext(address(testHook), 3 ether, mintIds);
|
|
103
|
+
vm.prank(mockTerminalAddress);
|
|
104
|
+
testHook.afterPayRecordedWith(payContext);
|
|
105
|
+
|
|
106
|
+
assertEq(testHook.balanceOf(beneficiary), 3, "beneficiary should own the three paid NFTs");
|
|
107
|
+
assertEq(testHook.totalCashOutWeight(), 3 ether, "denominator initially reflects only sold NFTs");
|
|
108
|
+
|
|
109
|
+
JB721TierConfig[] memory tier2 = new JB721TierConfig[](1);
|
|
110
|
+
tier2[0] = JB721TierConfig({
|
|
111
|
+
price: 2 ether,
|
|
112
|
+
initialSupply: 100,
|
|
113
|
+
votingUnits: 0,
|
|
114
|
+
reserveFrequency: 1,
|
|
115
|
+
reserveBeneficiary: owner,
|
|
116
|
+
encodedIPFSUri: bytes32(uint256(0x5678)),
|
|
117
|
+
category: 2,
|
|
118
|
+
discountPercent: 0,
|
|
119
|
+
flags: JB721TierConfigFlags({
|
|
120
|
+
allowOwnerMint: false,
|
|
121
|
+
useReserveBeneficiaryAsDefault: true,
|
|
122
|
+
transfersPausable: false,
|
|
123
|
+
useVotingUnits: false,
|
|
124
|
+
cantBeRemoved: false,
|
|
125
|
+
cantIncreaseDiscountPercent: false,
|
|
126
|
+
cantBuyWithCredits: false
|
|
127
|
+
}),
|
|
128
|
+
splitPercent: 0,
|
|
129
|
+
splits: new JBSplit[](0)
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
vm.prank(owner);
|
|
133
|
+
testHook.adjustTiers(tier2, new uint256[](0));
|
|
134
|
+
|
|
135
|
+
assertEq(
|
|
136
|
+
testHook.STORE().reserveBeneficiaryOf(address(testHook), 1),
|
|
137
|
+
owner,
|
|
138
|
+
"new default beneficiary retroactively applies to the old tier"
|
|
139
|
+
);
|
|
140
|
+
assertEq(
|
|
141
|
+
testHook.STORE().numberOfPendingReservesFor(address(testHook), 1),
|
|
142
|
+
2,
|
|
143
|
+
"historical sales now generate retroactive pending reserves"
|
|
144
|
+
);
|
|
145
|
+
assertEq(
|
|
146
|
+
testHook.totalCashOutWeight(),
|
|
147
|
+
5 ether,
|
|
148
|
+
"existing holders are diluted before any new payment enters the system"
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
vm.prank(address(0xBEEF));
|
|
152
|
+
testHook.mintPendingReservesFor(1, 2);
|
|
153
|
+
|
|
154
|
+
assertEq(testHook.balanceOf(owner), 2, "retroactive reserves mint directly to the new default beneficiary");
|
|
155
|
+
assertEq(
|
|
156
|
+
testHook.STORE().numberOfPendingReservesFor(address(testHook), 1),
|
|
157
|
+
0,
|
|
158
|
+
"all retroactive reserve entitlement can be extracted"
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -175,7 +175,8 @@ contract CodexSplitCreditsMismatch is UnitTestSetup {
|
|
|
175
175
|
assertEq(specs[0].amount, 1, "forwarded amount should be capped to actual payment");
|
|
176
176
|
|
|
177
177
|
// ...and proportionally scales the encoded per-tier split amounts to match the capped total.
|
|
178
|
-
(
|
|
178
|
+
(,, bytes memory splitData) = abi.decode(specs[0].metadata, (address, address, bytes));
|
|
179
|
+
(, uint256[] memory encodedAmounts) = abi.decode(splitData, (uint16[], uint256[]));
|
|
179
180
|
assertEq(encodedAmounts.length, 1, "expected one encoded split amount");
|
|
180
181
|
assertEq(encodedAmounts[0], 1, "hook metadata should be scaled down to match forwarded amount");
|
|
181
182
|
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {UnitTestSetup} from "../utils/UnitTestSetup.sol";
|
|
5
|
+
import {IJB721TokenUriResolver} from "../../src/interfaces/IJB721TokenUriResolver.sol";
|
|
6
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
7
|
+
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
8
|
+
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
9
|
+
import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
10
|
+
import {IJBSplits} from "@bananapus/core-v6/src/interfaces/IJBSplits.sol";
|
|
11
|
+
import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
|
|
12
|
+
import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
|
|
13
|
+
import {JBTokenAmount} from "@bananapus/core-v6/src/structs/JBTokenAmount.sol";
|
|
14
|
+
import {JB721TiersHook} from "../../src/JB721TiersHook.sol";
|
|
15
|
+
import {JB721CheckpointsDeployer} from "../../src/JB721CheckpointsDeployer.sol";
|
|
16
|
+
import {IJB721CheckpointsDeployer} from "../../src/interfaces/IJB721CheckpointsDeployer.sol";
|
|
17
|
+
import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
|
|
18
|
+
import {JB721InitTiersConfig} from "../../src/structs/JB721InitTiersConfig.sol";
|
|
19
|
+
import {JB721TiersHookFlags} from "../../src/structs/JB721TiersHookFlags.sol";
|
|
20
|
+
|
|
21
|
+
/// @notice Regression test for: same-currency decimal mismatch in split forwarding.
|
|
22
|
+
/// @dev When pricing decimals differ from payment decimals but the currency is the same,
|
|
23
|
+
/// `convertAndCapSplitAmounts` must rescale split amounts before comparing to `amountValue`.
|
|
24
|
+
/// Without the fix, split amounts stay in pricing decimals (e.g. 18), the cap comparison uses
|
|
25
|
+
/// payment decimals (e.g. 6), and the cap clips the split to 100% of the payment.
|
|
26
|
+
contract SameCurrencyDecimalMismatch is UnitTestSetup {
|
|
27
|
+
// Shared constants.
|
|
28
|
+
address constant MOCK_TOKEN = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
|
|
29
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
30
|
+
uint32 constant CURRENCY = uint32(uint160(MOCK_TOKEN));
|
|
31
|
+
|
|
32
|
+
/// @notice Prove that a 50% split with same currency but different decimals (pricing=18, payment=6)
|
|
33
|
+
/// correctly forwards ~50% of the payment, not 100%.
|
|
34
|
+
function test_sameCurrency_differentDecimals_splitAmountScaledCorrectly() public {
|
|
35
|
+
// Deploy hook with PRICES=address(0), tier priced at 1e18 (18-decimal), 50% split.
|
|
36
|
+
JB721TiersHook testHook;
|
|
37
|
+
{
|
|
38
|
+
JB721TiersHook origin = new JB721TiersHook(
|
|
39
|
+
IJBDirectory(mockJBDirectory),
|
|
40
|
+
IJBPermissions(mockJBPermissions),
|
|
41
|
+
IJBPrices(address(0)),
|
|
42
|
+
IJBRulesets(mockJBRulesets),
|
|
43
|
+
store,
|
|
44
|
+
IJBSplits(mockJBSplits),
|
|
45
|
+
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
|
|
46
|
+
trustedForwarder
|
|
47
|
+
);
|
|
48
|
+
address hookAddr = makeAddr("hook18to6");
|
|
49
|
+
vm.etch(hookAddr, address(origin).code);
|
|
50
|
+
testHook = JB721TiersHook(hookAddr);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
(JB721TierConfig[] memory tierConfigs,) = _createTiers(defaultTierConfig, 1);
|
|
55
|
+
tierConfigs[0].price = 1e18;
|
|
56
|
+
tierConfigs[0].splitPercent = 500_000_000; // 50%.
|
|
57
|
+
testHook.initialize(
|
|
58
|
+
projectId,
|
|
59
|
+
name,
|
|
60
|
+
symbol,
|
|
61
|
+
baseUri,
|
|
62
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
63
|
+
contractUri,
|
|
64
|
+
JB721InitTiersConfig({tiers: tierConfigs, currency: CURRENCY, decimals: 18}),
|
|
65
|
+
JB721TiersHookFlags({
|
|
66
|
+
preventOverspending: false,
|
|
67
|
+
issueTokensForSplits: false,
|
|
68
|
+
noNewTiersWithReserves: false,
|
|
69
|
+
noNewTiersWithVotes: false,
|
|
70
|
+
noNewTiersWithOwnerMinting: false
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build payer metadata requesting tier 1.
|
|
76
|
+
bytes memory payerMetadata;
|
|
77
|
+
{
|
|
78
|
+
uint16[] memory tierIdsToMint = new uint16[](1);
|
|
79
|
+
tierIdsToMint[0] = 1;
|
|
80
|
+
bytes[] memory data = new bytes[](1);
|
|
81
|
+
data[0] = abi.encode(true, tierIdsToMint);
|
|
82
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
83
|
+
ids[0] = metadataHelper.getId("pay", testHook.METADATA_ID_TARGET());
|
|
84
|
+
payerMetadata = metadataHelper.createMetadata(ids, data);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Pay 1.0 token reported as 6 decimals (value = 1e6). Same currency, different decimals.
|
|
88
|
+
(uint256 weight, JBPayHookSpecification[] memory hookSpecs) = testHook.beforePayRecordedWith(
|
|
89
|
+
JBBeforePayRecordedContext({
|
|
90
|
+
terminal: mockTerminalAddress,
|
|
91
|
+
payer: beneficiary,
|
|
92
|
+
amount: JBTokenAmount({token: MOCK_TOKEN, value: 1e6, decimals: 6, currency: CURRENCY}),
|
|
93
|
+
projectId: projectId,
|
|
94
|
+
rulesetId: 0,
|
|
95
|
+
beneficiary: beneficiary,
|
|
96
|
+
weight: 10e18,
|
|
97
|
+
reservedPercent: 0,
|
|
98
|
+
metadata: payerMetadata
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Without the fix: split amount (5e17 in 18-decimal pricing) is compared to amountValue (1e6),
|
|
103
|
+
// causing the cap to clip it to 1e6 (100% of payment) and weight becomes 0.
|
|
104
|
+
// With the fix: split is rescaled to 5e5 (50% of 1e6) and weight is 5e18.
|
|
105
|
+
assertEq(hookSpecs[0].amount, 5e5, "split should be 50% of payment (5e5), not capped to 100%");
|
|
106
|
+
assertEq(weight, 5e18, "weight should be 50% (half goes to splits)");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// @notice Sanity check: same currency AND same decimals — no rescaling needed.
|
|
110
|
+
function test_sameCurrency_sameDecimals_splitAmountUnchanged() public {
|
|
111
|
+
JB721TiersHook testHook;
|
|
112
|
+
{
|
|
113
|
+
JB721TiersHook origin = new JB721TiersHook(
|
|
114
|
+
IJBDirectory(mockJBDirectory),
|
|
115
|
+
IJBPermissions(mockJBPermissions),
|
|
116
|
+
IJBPrices(address(0)),
|
|
117
|
+
IJBRulesets(mockJBRulesets),
|
|
118
|
+
store,
|
|
119
|
+
IJBSplits(mockJBSplits),
|
|
120
|
+
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
|
|
121
|
+
trustedForwarder
|
|
122
|
+
);
|
|
123
|
+
address hookAddr = makeAddr("hook18to18");
|
|
124
|
+
vm.etch(hookAddr, address(origin).code);
|
|
125
|
+
testHook = JB721TiersHook(hookAddr);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
(JB721TierConfig[] memory tierConfigs,) = _createTiers(defaultTierConfig, 1);
|
|
130
|
+
tierConfigs[0].price = 1e18;
|
|
131
|
+
tierConfigs[0].splitPercent = 500_000_000;
|
|
132
|
+
testHook.initialize(
|
|
133
|
+
projectId,
|
|
134
|
+
name,
|
|
135
|
+
symbol,
|
|
136
|
+
baseUri,
|
|
137
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
138
|
+
contractUri,
|
|
139
|
+
JB721InitTiersConfig({tiers: tierConfigs, currency: CURRENCY, decimals: 18}),
|
|
140
|
+
JB721TiersHookFlags({
|
|
141
|
+
preventOverspending: false,
|
|
142
|
+
issueTokensForSplits: false,
|
|
143
|
+
noNewTiersWithReserves: false,
|
|
144
|
+
noNewTiersWithVotes: false,
|
|
145
|
+
noNewTiersWithOwnerMinting: false
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
bytes memory payerMetadata;
|
|
151
|
+
{
|
|
152
|
+
uint16[] memory tierIdsToMint = new uint16[](1);
|
|
153
|
+
tierIdsToMint[0] = 1;
|
|
154
|
+
bytes[] memory data = new bytes[](1);
|
|
155
|
+
data[0] = abi.encode(true, tierIdsToMint);
|
|
156
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
157
|
+
ids[0] = metadataHelper.getId("pay", testHook.METADATA_ID_TARGET());
|
|
158
|
+
payerMetadata = metadataHelper.createMetadata(ids, data);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
(uint256 weight, JBPayHookSpecification[] memory hookSpecs) = testHook.beforePayRecordedWith(
|
|
162
|
+
JBBeforePayRecordedContext({
|
|
163
|
+
terminal: mockTerminalAddress,
|
|
164
|
+
payer: beneficiary,
|
|
165
|
+
amount: JBTokenAmount({token: MOCK_TOKEN, value: 1e18, decimals: 18, currency: CURRENCY}),
|
|
166
|
+
projectId: projectId,
|
|
167
|
+
rulesetId: 0,
|
|
168
|
+
beneficiary: beneficiary,
|
|
169
|
+
weight: 10e18,
|
|
170
|
+
reservedPercent: 0,
|
|
171
|
+
metadata: payerMetadata
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
assertEq(hookSpecs[0].amount, 5e17, "split should be 50% of payment");
|
|
176
|
+
assertEq(weight, 5e18, "weight should be 50%");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// @notice Same currency, payment has MORE decimals than pricing (pricing=6, payment=18).
|
|
180
|
+
function test_sameCurrency_paymentMoreDecimals_splitScaledUp() public {
|
|
181
|
+
JB721TiersHook testHook;
|
|
182
|
+
{
|
|
183
|
+
JB721TiersHook origin = new JB721TiersHook(
|
|
184
|
+
IJBDirectory(mockJBDirectory),
|
|
185
|
+
IJBPermissions(mockJBPermissions),
|
|
186
|
+
IJBPrices(address(0)),
|
|
187
|
+
IJBRulesets(mockJBRulesets),
|
|
188
|
+
store,
|
|
189
|
+
IJBSplits(mockJBSplits),
|
|
190
|
+
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
|
|
191
|
+
trustedForwarder
|
|
192
|
+
);
|
|
193
|
+
address hookAddr = makeAddr("hook6to18");
|
|
194
|
+
vm.etch(hookAddr, address(origin).code);
|
|
195
|
+
testHook = JB721TiersHook(hookAddr);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
{
|
|
199
|
+
(JB721TierConfig[] memory tierConfigs,) = _createTiers(defaultTierConfig, 1);
|
|
200
|
+
tierConfigs[0].price = 1e6; // 1.0 token in 6-decimal pricing.
|
|
201
|
+
tierConfigs[0].splitPercent = 500_000_000;
|
|
202
|
+
testHook.initialize(
|
|
203
|
+
projectId,
|
|
204
|
+
name,
|
|
205
|
+
symbol,
|
|
206
|
+
baseUri,
|
|
207
|
+
IJB721TokenUriResolver(mockTokenUriResolver),
|
|
208
|
+
contractUri,
|
|
209
|
+
JB721InitTiersConfig({tiers: tierConfigs, currency: CURRENCY, decimals: 6}),
|
|
210
|
+
JB721TiersHookFlags({
|
|
211
|
+
preventOverspending: false,
|
|
212
|
+
issueTokensForSplits: false,
|
|
213
|
+
noNewTiersWithReserves: false,
|
|
214
|
+
noNewTiersWithVotes: false,
|
|
215
|
+
noNewTiersWithOwnerMinting: false
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
bytes memory payerMetadata;
|
|
221
|
+
{
|
|
222
|
+
uint16[] memory tierIdsToMint = new uint16[](1);
|
|
223
|
+
tierIdsToMint[0] = 1;
|
|
224
|
+
bytes[] memory data = new bytes[](1);
|
|
225
|
+
data[0] = abi.encode(true, tierIdsToMint);
|
|
226
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
227
|
+
ids[0] = metadataHelper.getId("pay", testHook.METADATA_ID_TARGET());
|
|
228
|
+
payerMetadata = metadataHelper.createMetadata(ids, data);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
(uint256 weight, JBPayHookSpecification[] memory hookSpecs) = testHook.beforePayRecordedWith(
|
|
232
|
+
JBBeforePayRecordedContext({
|
|
233
|
+
terminal: mockTerminalAddress,
|
|
234
|
+
payer: beneficiary,
|
|
235
|
+
amount: JBTokenAmount({token: MOCK_TOKEN, value: 1e18, decimals: 18, currency: CURRENCY}),
|
|
236
|
+
projectId: projectId,
|
|
237
|
+
rulesetId: 0,
|
|
238
|
+
beneficiary: beneficiary,
|
|
239
|
+
weight: 10e18,
|
|
240
|
+
reservedPercent: 0,
|
|
241
|
+
metadata: payerMetadata
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// 50% of 1.0 token in 18-decimal payment = 5e17.
|
|
246
|
+
assertEq(hookSpecs[0].amount, 5e17, "split scaled up to 18-decimal payment");
|
|
247
|
+
assertEq(weight, 5e18, "weight should be 50%");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -59,7 +59,8 @@ contract SplitFailureRedistribution is UnitTestSetup {
|
|
|
59
59
|
);
|
|
60
60
|
|
|
61
61
|
bytes memory payerMetadata = _buildPayMetadata(address(testHook), uint16(tierIds[0]));
|
|
62
|
-
bytes memory hookMetadata =
|
|
62
|
+
bytes memory hookMetadata =
|
|
63
|
+
abi.encode(beneficiary, beneficiary, abi.encode(_singleTierId(uint16(tierIds[0])), _singleAmount(1 ether)));
|
|
63
64
|
|
|
64
65
|
JBAfterPayRecordedContext memory payContext = JBAfterPayRecordedContext({
|
|
65
66
|
payer: beneficiary,
|
|
@@ -70,6 +70,8 @@ import "../../src/JB721TiersHookDeployer.sol";
|
|
|
70
70
|
import "../../src/JB721TiersHookProjectDeployer.sol";
|
|
71
71
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
72
72
|
import "../../src/JB721TiersHookStore.sol";
|
|
73
|
+
import {JB721CheckpointsDeployer} from "../../src/JB721CheckpointsDeployer.sol";
|
|
74
|
+
import {IJB721CheckpointsDeployer} from "../../src/interfaces/IJB721CheckpointsDeployer.sol";
|
|
73
75
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
74
76
|
import "../../src/interfaces/IJB721TiersHook.sol";
|
|
75
77
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
@@ -176,7 +178,7 @@ contract ERC20CashOutFork is Test {
|
|
|
176
178
|
jbPermissions = new JBPermissions(address(0));
|
|
177
179
|
jbProjects = new JBProjects(multisig, address(0), address(0));
|
|
178
180
|
jbDirectory = new JBDirectory(jbPermissions, jbProjects, multisig);
|
|
179
|
-
JBERC20 jbErc20 = new JBERC20();
|
|
181
|
+
JBERC20 jbErc20 = new JBERC20(jbPermissions, jbProjects);
|
|
180
182
|
jbTokens = new JBTokens(jbDirectory, jbErc20);
|
|
181
183
|
jbRulesets = new JBRulesets(jbDirectory);
|
|
182
184
|
jbPrices = new JBPrices(jbDirectory, jbPermissions, jbProjects, multisig, address(0));
|
|
@@ -217,7 +219,14 @@ contract ERC20CashOutFork is Test {
|
|
|
217
219
|
function _deploy721Hook() internal {
|
|
218
220
|
store = new JB721TiersHookStore();
|
|
219
221
|
hookImpl = new JB721TiersHook(
|
|
220
|
-
jbDirectory,
|
|
222
|
+
jbDirectory,
|
|
223
|
+
jbPermissions,
|
|
224
|
+
jbPrices,
|
|
225
|
+
jbRulesets,
|
|
226
|
+
store,
|
|
227
|
+
IJBSplits(address(jbSplits)),
|
|
228
|
+
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
|
|
229
|
+
address(0)
|
|
221
230
|
);
|
|
222
231
|
addressRegistry = new JBAddressRegistry();
|
|
223
232
|
hookDeployer = new JB721TiersHookDeployer(hookImpl, store, addressRegistry, address(0));
|
|
@@ -70,6 +70,8 @@ import "../../src/JB721TiersHookDeployer.sol";
|
|
|
70
70
|
import "../../src/JB721TiersHookProjectDeployer.sol";
|
|
71
71
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
72
72
|
import "../../src/JB721TiersHookStore.sol";
|
|
73
|
+
import {JB721CheckpointsDeployer} from "../../src/JB721CheckpointsDeployer.sol";
|
|
74
|
+
import {IJB721CheckpointsDeployer} from "../../src/interfaces/IJB721CheckpointsDeployer.sol";
|
|
73
75
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
74
76
|
import "../../src/interfaces/IJB721TiersHook.sol";
|
|
75
77
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
@@ -153,7 +155,7 @@ contract ERC20TierSplitFork is Test {
|
|
|
153
155
|
jbPermissions = new JBPermissions(address(0));
|
|
154
156
|
jbProjects = new JBProjects(multisig, address(0), address(0));
|
|
155
157
|
jbDirectory = new JBDirectory(jbPermissions, jbProjects, multisig);
|
|
156
|
-
JBERC20 jbErc20 = new JBERC20();
|
|
158
|
+
JBERC20 jbErc20 = new JBERC20(jbPermissions, jbProjects);
|
|
157
159
|
jbTokens = new JBTokens(jbDirectory, jbErc20);
|
|
158
160
|
jbRulesets = new JBRulesets(jbDirectory);
|
|
159
161
|
jbPrices = new JBPrices(jbDirectory, jbPermissions, jbProjects, multisig, address(0));
|
|
@@ -194,7 +196,14 @@ contract ERC20TierSplitFork is Test {
|
|
|
194
196
|
function _deploy721Hook() internal {
|
|
195
197
|
store = new JB721TiersHookStore();
|
|
196
198
|
hookImpl = new JB721TiersHook(
|
|
197
|
-
jbDirectory,
|
|
199
|
+
jbDirectory,
|
|
200
|
+
jbPermissions,
|
|
201
|
+
jbPrices,
|
|
202
|
+
jbRulesets,
|
|
203
|
+
store,
|
|
204
|
+
IJBSplits(address(jbSplits)),
|
|
205
|
+
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
|
|
206
|
+
address(0)
|
|
198
207
|
);
|
|
199
208
|
addressRegistry = new JBAddressRegistry();
|
|
200
209
|
hookDeployer = new JB721TiersHookDeployer(hookImpl, store, addressRegistry, address(0));
|
|
@@ -68,6 +68,8 @@ import "../../src/JB721TiersHookDeployer.sol";
|
|
|
68
68
|
import "../../src/JB721TiersHookProjectDeployer.sol";
|
|
69
69
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
70
70
|
import "../../src/JB721TiersHookStore.sol";
|
|
71
|
+
import {JB721CheckpointsDeployer} from "../../src/JB721CheckpointsDeployer.sol";
|
|
72
|
+
import {IJB721CheckpointsDeployer} from "../../src/interfaces/IJB721CheckpointsDeployer.sol";
|
|
71
73
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
72
74
|
import "../../src/interfaces/IJB721TiersHook.sol";
|
|
73
75
|
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
@@ -160,7 +162,7 @@ contract IssueTokensForSplitsFork is Test {
|
|
|
160
162
|
jbPermissions = new JBPermissions(address(0));
|
|
161
163
|
jbProjects = new JBProjects(multisig, address(0), address(0));
|
|
162
164
|
jbDirectory = new JBDirectory(jbPermissions, jbProjects, multisig);
|
|
163
|
-
JBERC20 jbErc20 = new JBERC20();
|
|
165
|
+
JBERC20 jbErc20 = new JBERC20(jbPermissions, jbProjects);
|
|
164
166
|
jbTokens = new JBTokens(jbDirectory, jbErc20);
|
|
165
167
|
jbRulesets = new JBRulesets(jbDirectory);
|
|
166
168
|
jbPrices = new JBPrices(jbDirectory, jbPermissions, jbProjects, multisig, address(0));
|
|
@@ -208,7 +210,14 @@ contract IssueTokensForSplitsFork is Test {
|
|
|
208
210
|
function _deploy721Hook() internal {
|
|
209
211
|
store = new JB721TiersHookStore();
|
|
210
212
|
hookImpl = new JB721TiersHook(
|
|
211
|
-
jbDirectory,
|
|
213
|
+
jbDirectory,
|
|
214
|
+
jbPermissions,
|
|
215
|
+
jbPrices,
|
|
216
|
+
jbRulesets,
|
|
217
|
+
store,
|
|
218
|
+
IJBSplits(address(jbSplits)),
|
|
219
|
+
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
|
|
220
|
+
address(0)
|
|
212
221
|
);
|
|
213
222
|
addressRegistry = new JBAddressRegistry();
|
|
214
223
|
hookDeployer = new JB721TiersHookDeployer(hookImpl, store, addressRegistry, address(0));
|
|
@@ -69,7 +69,7 @@ contract Test_BrokenTerminalDoesNotDos is UnitTestSetup {
|
|
|
69
69
|
weight: 10e18,
|
|
70
70
|
newlyIssuedTokenCount: 0,
|
|
71
71
|
beneficiary: beneficiary,
|
|
72
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
72
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
73
73
|
payerMetadata: payerMetadata
|
|
74
74
|
});
|
|
75
75
|
}
|
|
@@ -163,7 +163,7 @@ contract Test_BrokenTerminalDoesNotDos is UnitTestSetup {
|
|
|
163
163
|
weight: 10e18,
|
|
164
164
|
newlyIssuedTokenCount: 0,
|
|
165
165
|
beneficiary: beneficiary,
|
|
166
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
166
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
167
167
|
payerMetadata: payerMetadata
|
|
168
168
|
});
|
|
169
169
|
|
|
@@ -133,7 +133,7 @@ contract Test_SplitDistributionBugs is UnitTestSetup {
|
|
|
133
133
|
weight: 10e18,
|
|
134
134
|
newlyIssuedTokenCount: 0,
|
|
135
135
|
beneficiary: beneficiary,
|
|
136
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
136
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
137
137
|
payerMetadata: payerMetadata
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -350,7 +350,7 @@ contract Test_SplitDistributionBugs is UnitTestSetup {
|
|
|
350
350
|
weight: 10e18,
|
|
351
351
|
newlyIssuedTokenCount: 0,
|
|
352
352
|
beneficiary: beneficiary,
|
|
353
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
353
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
354
354
|
payerMetadata: payerMetadata
|
|
355
355
|
});
|
|
356
356
|
|
|
@@ -455,7 +455,7 @@ contract Test_SplitDistributionBugs is UnitTestSetup {
|
|
|
455
455
|
weight: 10e18,
|
|
456
456
|
newlyIssuedTokenCount: 0,
|
|
457
457
|
beneficiary: beneficiary,
|
|
458
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
458
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
459
459
|
payerMetadata: payerMetadata
|
|
460
460
|
});
|
|
461
461
|
|
|
@@ -566,7 +566,7 @@ contract Test_SplitDistributionBugs is UnitTestSetup {
|
|
|
566
566
|
weight: 10e18,
|
|
567
567
|
newlyIssuedTokenCount: 0,
|
|
568
568
|
beneficiary: beneficiary,
|
|
569
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
569
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
570
570
|
payerMetadata: payerMetadata
|
|
571
571
|
});
|
|
572
572
|
}
|
|
@@ -673,7 +673,7 @@ contract Test_SplitDistributionBugs is UnitTestSetup {
|
|
|
673
673
|
weight: 10e18,
|
|
674
674
|
newlyIssuedTokenCount: 0,
|
|
675
675
|
beneficiary: beneficiary,
|
|
676
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
676
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
677
677
|
payerMetadata: payerMetadata
|
|
678
678
|
});
|
|
679
679
|
|
|
@@ -112,7 +112,7 @@ contract Test_L36_SplitNoBeneficiary is UnitTestSetup {
|
|
|
112
112
|
weight: 10e18,
|
|
113
113
|
newlyIssuedTokenCount: 0,
|
|
114
114
|
beneficiary: beneficiary,
|
|
115
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
115
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
116
116
|
payerMetadata: payerMetadata
|
|
117
117
|
});
|
|
118
118
|
|
|
@@ -115,9 +115,9 @@ contract Test_AuditFixes_Unit is UnitTestSetup {
|
|
|
115
115
|
// The total forwarded amount must be capped at the payment value (may be up to 1 wei less due to rounding).
|
|
116
116
|
assertApproxEqAbs(specs[0].amount, 2 ether, 1, "Total split should be capped at payment value");
|
|
117
117
|
|
|
118
|
-
// Decode the per-tier breakdown from hookMetadata.
|
|
119
|
-
(
|
|
120
|
-
|
|
118
|
+
// Decode the per-tier breakdown from hookMetadata (unwrap beneficiary/payer wrapper first).
|
|
119
|
+
(,, bytes memory splitData) = abi.decode(specs[0].metadata, (address, address, bytes));
|
|
120
|
+
(uint16[] memory resultTierIds, uint256[] memory resultAmounts) = abi.decode(splitData, (uint16[], uint256[]));
|
|
121
121
|
|
|
122
122
|
// Both tiers should be present in the metadata.
|
|
123
123
|
assertEq(resultTierIds.length, 2, "Should have 2 tier entries");
|
|
@@ -260,7 +260,7 @@ contract Test_AuditFixes_Unit is UnitTestSetup {
|
|
|
260
260
|
weight: 10e18,
|
|
261
261
|
newlyIssuedTokenCount: 0,
|
|
262
262
|
beneficiary: beneficiary,
|
|
263
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
263
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
264
264
|
payerMetadata: payerMetadata
|
|
265
265
|
});
|
|
266
266
|
|
|
@@ -341,7 +341,7 @@ contract Test_AuditFixes_Unit is UnitTestSetup {
|
|
|
341
341
|
weight: 10e18,
|
|
342
342
|
newlyIssuedTokenCount: 0,
|
|
343
343
|
beneficiary: beneficiary,
|
|
344
|
-
hookMetadata: abi.encode(splitTierIds, splitAmounts),
|
|
344
|
+
hookMetadata: abi.encode(beneficiary, beneficiary, abi.encode(splitTierIds, splitAmounts)),
|
|
345
345
|
payerMetadata: payerMetadata
|
|
346
346
|
});
|
|
347
347
|
|
package/test/unit/pay_Unit.t.sol
CHANGED
|
@@ -1006,6 +1006,7 @@ contract Test_afterPayRecorded_Unit is UnitTestSetup {
|
|
|
1006
1006
|
IJBRulesets(mockJBRulesets),
|
|
1007
1007
|
IJB721TiersHookStore(store),
|
|
1008
1008
|
IJBSplits(mockJBSplits),
|
|
1009
|
+
IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
|
|
1009
1010
|
trustedForwarder
|
|
1010
1011
|
);
|
|
1011
1012
|
address noPricesProxy = makeAddr("noPricesProxy2");
|