@bananapus/721-hook-v6 0.0.1
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/.gas-snapshot +152 -0
- package/LICENSE +21 -0
- package/README.md +253 -0
- package/SKILLS.md +140 -0
- package/docs/book.css +13 -0
- package/docs/book.toml +12 -0
- package/docs/solidity.min.js +74 -0
- package/docs/src/README.md +253 -0
- package/docs/src/SUMMARY.md +38 -0
- package/docs/src/src/JB721TiersHook.sol/contract.JB721TiersHook.md +645 -0
- package/docs/src/src/JB721TiersHookDeployer.sol/contract.JB721TiersHookDeployer.md +99 -0
- package/docs/src/src/JB721TiersHookProjectDeployer.sol/contract.JB721TiersHookProjectDeployer.md +288 -0
- package/docs/src/src/JB721TiersHookStore.sol/contract.JB721TiersHookStore.md +1096 -0
- package/docs/src/src/README.md +11 -0
- package/docs/src/src/abstract/ERC721.sol/abstract.ERC721.md +430 -0
- package/docs/src/src/abstract/JB721Hook.sol/abstract.JB721Hook.md +309 -0
- package/docs/src/src/abstract/README.md +5 -0
- package/docs/src/src/interfaces/IJB721Hook.sol/interface.IJB721Hook.md +29 -0
- package/docs/src/src/interfaces/IJB721TiersHook.sol/interface.IJB721TiersHook.md +203 -0
- package/docs/src/src/interfaces/IJB721TiersHookDeployer.sol/interface.IJB721TiersHookDeployer.md +25 -0
- package/docs/src/src/interfaces/IJB721TiersHookProjectDeployer.sol/interface.IJB721TiersHookProjectDeployer.md +64 -0
- package/docs/src/src/interfaces/IJB721TiersHookStore.sol/interface.IJB721TiersHookStore.md +265 -0
- package/docs/src/src/interfaces/IJB721TokenUriResolver.sol/interface.IJB721TokenUriResolver.md +12 -0
- package/docs/src/src/interfaces/README.md +9 -0
- package/docs/src/src/libraries/JB721Constants.sol/library.JB721Constants.md +14 -0
- package/docs/src/src/libraries/JB721TiersRulesetMetadataResolver.sol/library.JB721TiersRulesetMetadataResolver.md +68 -0
- package/docs/src/src/libraries/JBBitmap.sol/library.JBBitmap.md +82 -0
- package/docs/src/src/libraries/JBIpfsDecoder.sol/library.JBIpfsDecoder.md +61 -0
- package/docs/src/src/libraries/README.md +7 -0
- package/docs/src/src/structs/JB721InitTiersConfig.sol/struct.JB721InitTiersConfig.md +27 -0
- package/docs/src/src/structs/JB721Tier.sol/struct.JB721Tier.md +59 -0
- package/docs/src/src/structs/JB721TierConfig.sol/struct.JB721TierConfig.md +60 -0
- package/docs/src/src/structs/JB721TiersHookFlags.sol/struct.JB721TiersHookFlags.md +26 -0
- package/docs/src/src/structs/JB721TiersMintReservesConfig.sol/struct.JB721TiersMintReservesConfig.md +16 -0
- package/docs/src/src/structs/JB721TiersRulesetMetadata.sol/struct.JB721TiersRulesetMetadata.md +20 -0
- package/docs/src/src/structs/JB721TiersSetDiscountPercentConfig.sol/struct.JB721TiersSetDiscountPercentConfig.md +16 -0
- package/docs/src/src/structs/JBBitmapWord.sol/struct.JBBitmapWord.md +19 -0
- package/docs/src/src/structs/JBDeploy721TiersHookConfig.sol/struct.JBDeploy721TiersHookConfig.md +34 -0
- package/docs/src/src/structs/JBLaunchProjectConfig.sol/struct.JBLaunchProjectConfig.md +23 -0
- package/docs/src/src/structs/JBLaunchRulesetsConfig.sol/struct.JBLaunchRulesetsConfig.md +22 -0
- package/docs/src/src/structs/JBPayDataHookRulesetConfig.sol/struct.JBPayDataHookRulesetConfig.md +51 -0
- package/docs/src/src/structs/JBPayDataHookRulesetMetadata.sol/struct.JBPayDataHookRulesetMetadata.md +66 -0
- package/docs/src/src/structs/JBQueueRulesetsConfig.sol/struct.JBQueueRulesetsConfig.md +21 -0
- package/docs/src/src/structs/JBStored721Tier.sol/struct.JBStored721Tier.md +42 -0
- package/docs/src/src/structs/README.md +18 -0
- package/foundry.lock +11 -0
- package/foundry.toml +22 -0
- package/package.json +31 -0
- package/remappings.txt +1 -0
- package/script/Deploy.s.sol +140 -0
- package/script/helpers/Hook721DeploymentLib.sol +81 -0
- package/slither-ci.config.json +10 -0
- package/sphinx.lock +476 -0
- package/src/JB721TiersHook.sol +765 -0
- package/src/JB721TiersHookDeployer.sol +114 -0
- package/src/JB721TiersHookProjectDeployer.sol +413 -0
- package/src/JB721TiersHookStore.sol +1195 -0
- package/src/abstract/ERC721.sol +484 -0
- package/src/abstract/JB721Hook.sol +279 -0
- package/src/interfaces/IJB721Hook.sol +21 -0
- package/src/interfaces/IJB721TiersHook.sol +135 -0
- package/src/interfaces/IJB721TiersHookDeployer.sol +22 -0
- package/src/interfaces/IJB721TiersHookProjectDeployer.sol +76 -0
- package/src/interfaces/IJB721TiersHookStore.sol +220 -0
- package/src/interfaces/IJB721TokenUriResolver.sol +10 -0
- package/src/libraries/JB721Constants.sol +7 -0
- package/src/libraries/JB721TiersRulesetMetadataResolver.sol +44 -0
- package/src/libraries/JBBitmap.sol +57 -0
- package/src/libraries/JBIpfsDecoder.sol +95 -0
- package/src/structs/JB721InitTiersConfig.sol +20 -0
- package/src/structs/JB721Tier.sol +39 -0
- package/src/structs/JB721TierConfig.sol +40 -0
- package/src/structs/JB721TiersHookFlags.sol +17 -0
- package/src/structs/JB721TiersMintReservesConfig.sol +9 -0
- package/src/structs/JB721TiersRulesetMetadata.sol +12 -0
- package/src/structs/JB721TiersSetDiscountPercentConfig.sol +9 -0
- package/src/structs/JBBitmapWord.sol +11 -0
- package/src/structs/JBDeploy721TiersHookConfig.sol +25 -0
- package/src/structs/JBLaunchProjectConfig.sol +18 -0
- package/src/structs/JBLaunchRulesetsConfig.sol +17 -0
- package/src/structs/JBPayDataHookRulesetConfig.sol +44 -0
- package/src/structs/JBPayDataHookRulesetMetadata.sol +46 -0
- package/src/structs/JBQueueRulesetsConfig.sol +13 -0
- package/src/structs/JBStored721Tier.sol +24 -0
- package/test/721HookAttacks.t.sol +396 -0
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +944 -0
- package/test/invariants/TierLifecycleInvariant.t.sol +187 -0
- package/test/invariants/TieredHookStoreInvariant.t.sol +81 -0
- package/test/invariants/handlers/TierLifecycleHandler.sol +262 -0
- package/test/invariants/handlers/TierStoreHandler.sol +155 -0
- package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +141 -0
- package/test/unit/JBBitmap.t.sol +169 -0
- package/test/unit/JBIpfsDecoder.t.sol +131 -0
- package/test/unit/M6_TierSupplyCheck.t.sol +220 -0
- package/test/unit/adjustTier_Unit.t.sol +1740 -0
- package/test/unit/deployer_Unit.t.sol +103 -0
- package/test/unit/getters_constructor_Unit.t.sol +548 -0
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +443 -0
- package/test/unit/pay_Unit.t.sol +1537 -0
- package/test/unit/redeem_Unit.t.sol +459 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.23;
|
|
3
|
+
|
|
4
|
+
import "../utils/UnitTestSetup.sol";
|
|
5
|
+
|
|
6
|
+
contract Test_cashOut_Unit is UnitTestSetup {
|
|
7
|
+
using stdStorage for StdStorage;
|
|
8
|
+
|
|
9
|
+
function test_beforeCashOutContext_returnsCorrectAmount() public {
|
|
10
|
+
uint256 weight;
|
|
11
|
+
uint256 totalWeight;
|
|
12
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
13
|
+
|
|
14
|
+
// Set up 10 tiers, with half of the supply minted for each one.
|
|
15
|
+
for (uint256 i = 1; i <= 10; i++) {
|
|
16
|
+
hook.test_store()
|
|
17
|
+
.ForTest_setTier(
|
|
18
|
+
address(hook),
|
|
19
|
+
i,
|
|
20
|
+
JBStored721Tier({
|
|
21
|
+
price: uint104(i * 10),
|
|
22
|
+
remainingSupply: uint32(10 * i - 5 * i),
|
|
23
|
+
initialSupply: uint32(10 * i),
|
|
24
|
+
votingUnits: uint16(0),
|
|
25
|
+
reserveFrequency: uint16(0),
|
|
26
|
+
category: uint24(100),
|
|
27
|
+
discountPercent: uint8(0),
|
|
28
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false)
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
totalWeight += (10 * i - 5 * i) * i * 10;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Cash out as if the beneficiary has 1 NFT from each of the first five tiers.
|
|
35
|
+
uint256[] memory tokenList = new uint256[](5);
|
|
36
|
+
for (uint256 i; i < 5; i++) {
|
|
37
|
+
uint256 tokenId = _generateTokenId(i + 1, 1);
|
|
38
|
+
hook.ForTest_setOwnerOf(tokenId, beneficiary);
|
|
39
|
+
tokenList[i] = tokenId;
|
|
40
|
+
weight += (i + 1) * 10;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Build the metadata with the tiers to cash out.
|
|
44
|
+
bytes[] memory data = new bytes[](1);
|
|
45
|
+
data[0] = abi.encode(tokenList);
|
|
46
|
+
|
|
47
|
+
// Pass the hook ID.
|
|
48
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
49
|
+
ids[0] = metadataHelper.getId("cashOut", address(hookOrigin));
|
|
50
|
+
|
|
51
|
+
// Generate the metadata.
|
|
52
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
53
|
+
(uint256 cashOutTaxRate,,, JBCashOutHookSpecification[] memory returnedHook) = hook.beforeCashOutRecordedWith(
|
|
54
|
+
JBBeforeCashOutRecordedContext({
|
|
55
|
+
terminal: address(0),
|
|
56
|
+
holder: beneficiary,
|
|
57
|
+
projectId: projectId,
|
|
58
|
+
rulesetId: 0,
|
|
59
|
+
cashOutCount: 0,
|
|
60
|
+
totalSupply: 0,
|
|
61
|
+
surplus: JBTokenAmount({
|
|
62
|
+
token: address(0), value: SURPLUS, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
63
|
+
}),
|
|
64
|
+
useTotalSurplus: true,
|
|
65
|
+
cashOutTaxRate: CASH_OUT_TAX_RATE,
|
|
66
|
+
metadata: hookMetadata
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Check: does the reclaim amount match the expected value?
|
|
71
|
+
assertEq(cashOutTaxRate, CASH_OUT_TAX_RATE);
|
|
72
|
+
// Check: does the returned hook address match the expected value?
|
|
73
|
+
assertEq(address(returnedHook[0].hook), address(hook));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function test_beforeCashOutContext_returnsZeroAmountIfReserveFrequencyIsZero() public {
|
|
77
|
+
uint256 surplus = 10e18;
|
|
78
|
+
uint256 cashOutTaxRate = 0;
|
|
79
|
+
uint256 weight;
|
|
80
|
+
uint256 totalWeight;
|
|
81
|
+
JBCashOutHookSpecification[] memory returnedHook;
|
|
82
|
+
|
|
83
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
84
|
+
|
|
85
|
+
// Set up 10 tiers, with half of the supply minted for each one.
|
|
86
|
+
for (uint256 i = 1; i <= 10; i++) {
|
|
87
|
+
hook.test_store()
|
|
88
|
+
.ForTest_setTier(
|
|
89
|
+
address(hook),
|
|
90
|
+
i,
|
|
91
|
+
JBStored721Tier({
|
|
92
|
+
price: uint104(i * 10),
|
|
93
|
+
remainingSupply: uint32(10 * i - 5 * i),
|
|
94
|
+
initialSupply: uint32(10 * i),
|
|
95
|
+
votingUnits: uint16(0),
|
|
96
|
+
reserveFrequency: uint16(0),
|
|
97
|
+
category: uint24(100),
|
|
98
|
+
discountPercent: uint8(0),
|
|
99
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false)
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
totalWeight += (10 * i - 5 * i) * i * 10;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Cash out as if the beneficiary has 1 NFT from each of the first five tiers.
|
|
106
|
+
uint256[] memory tokenList = new uint256[](5);
|
|
107
|
+
for (uint256 i; i < 5; i++) {
|
|
108
|
+
hook.ForTest_setOwnerOf(i + 1, beneficiary);
|
|
109
|
+
tokenList[i] = i + 1;
|
|
110
|
+
weight += (i + 1) * (i + 1) * 10;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
(cashOutTaxRate,,, returnedHook) = hook.beforeCashOutRecordedWith(
|
|
114
|
+
JBBeforeCashOutRecordedContext({
|
|
115
|
+
terminal: address(0),
|
|
116
|
+
holder: beneficiary,
|
|
117
|
+
projectId: projectId,
|
|
118
|
+
rulesetId: 0,
|
|
119
|
+
cashOutCount: 0,
|
|
120
|
+
totalSupply: 0,
|
|
121
|
+
surplus: JBTokenAmount({
|
|
122
|
+
token: address(0), value: surplus, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
123
|
+
}),
|
|
124
|
+
useTotalSurplus: true,
|
|
125
|
+
cashOutTaxRate: cashOutTaxRate,
|
|
126
|
+
metadata: abi.encode(bytes32(0), type(IJB721Hook).interfaceId, tokenList)
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Check: is the cash out tax rate zero?
|
|
131
|
+
assertEq(cashOutTaxRate, 0);
|
|
132
|
+
// Check: does the returned hook address match the expected value?
|
|
133
|
+
assertEq(address(returnedHook[0].hook), address(hook));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function test_beforeCashOutContext_returnsPartOfOverflowOwnedIfCashOutTaxRateIsMaximum() public {
|
|
137
|
+
uint256 weight;
|
|
138
|
+
uint256 totalWeight;
|
|
139
|
+
|
|
140
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(10);
|
|
141
|
+
|
|
142
|
+
// Set up 10 tiers, with half of the supply minted for each one.
|
|
143
|
+
for (uint256 i = 1; i <= 10; i++) {
|
|
144
|
+
hook.test_store()
|
|
145
|
+
.ForTest_setTier(
|
|
146
|
+
address(hook),
|
|
147
|
+
i,
|
|
148
|
+
JBStored721Tier({
|
|
149
|
+
price: uint104(i * 10),
|
|
150
|
+
remainingSupply: uint32(10 * i - 5 * i),
|
|
151
|
+
initialSupply: uint32(10 * i),
|
|
152
|
+
votingUnits: uint16(0),
|
|
153
|
+
reserveFrequency: uint16(0),
|
|
154
|
+
category: uint24(100),
|
|
155
|
+
discountPercent: uint8(0),
|
|
156
|
+
packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false)
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
totalWeight += (10 * i - 5 * i) * i * 10;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Cash out as if the beneficiary has 1 NFT from each of the first five tiers.
|
|
163
|
+
uint256[] memory tokenList = new uint256[](5);
|
|
164
|
+
for (uint256 i; i < 5; i++) {
|
|
165
|
+
hook.ForTest_setOwnerOf(_generateTokenId(i + 1, 1), beneficiary);
|
|
166
|
+
tokenList[i] = _generateTokenId(i + 1, 1);
|
|
167
|
+
weight += (i + 1) * 10;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Build the metadata with the tiers to cash out.
|
|
171
|
+
bytes[] memory data = new bytes[](1);
|
|
172
|
+
data[0] = abi.encode(tokenList);
|
|
173
|
+
|
|
174
|
+
// Pass the hook ID.
|
|
175
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
176
|
+
ids[0] = metadataHelper.getId("cashOut", address(hookOrigin));
|
|
177
|
+
|
|
178
|
+
// Generate the metadata.
|
|
179
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
180
|
+
|
|
181
|
+
JBBeforeCashOutRecordedContext memory beforeCashOutContext = JBBeforeCashOutRecordedContext({
|
|
182
|
+
terminal: address(0),
|
|
183
|
+
holder: beneficiary,
|
|
184
|
+
projectId: projectId,
|
|
185
|
+
rulesetId: 0,
|
|
186
|
+
cashOutCount: 0,
|
|
187
|
+
totalSupply: 0,
|
|
188
|
+
surplus: JBTokenAmount({
|
|
189
|
+
token: address(0), value: SURPLUS, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
190
|
+
}),
|
|
191
|
+
useTotalSurplus: true,
|
|
192
|
+
cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE,
|
|
193
|
+
metadata: hookMetadata
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
(uint256 cashOutTaxRate,,, JBCashOutHookSpecification[] memory returnedHook) =
|
|
197
|
+
hook.beforeCashOutRecordedWith(beforeCashOutContext);
|
|
198
|
+
|
|
199
|
+
// Check: does the cash out tax rate match the expected value?
|
|
200
|
+
assertEq(cashOutTaxRate, JBConstants.MAX_CASH_OUT_TAX_RATE);
|
|
201
|
+
// Check: does the returned hook address match the expected value?
|
|
202
|
+
assertEq(address(returnedHook[0].hook), address(hook));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function test_beforeCashOutContext_revertIfNonZeroTokenCount(uint256 tokenCount) public {
|
|
206
|
+
vm.assume(tokenCount > 0);
|
|
207
|
+
|
|
208
|
+
// Expect a revert on account of the token count being non-zero while the total supply is zero.
|
|
209
|
+
vm.expectRevert(abi.encodeWithSelector(JB721Hook.JB721Hook_UnexpectedTokenCashedOut.selector));
|
|
210
|
+
|
|
211
|
+
hook.beforeCashOutRecordedWith(
|
|
212
|
+
JBBeforeCashOutRecordedContext({
|
|
213
|
+
terminal: address(0),
|
|
214
|
+
holder: beneficiary,
|
|
215
|
+
projectId: projectId,
|
|
216
|
+
rulesetId: 0,
|
|
217
|
+
cashOutCount: tokenCount,
|
|
218
|
+
totalSupply: 0,
|
|
219
|
+
surplus: JBTokenAmount({
|
|
220
|
+
token: address(0), value: 100, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
221
|
+
}),
|
|
222
|
+
useTotalSurplus: true,
|
|
223
|
+
cashOutTaxRate: 100,
|
|
224
|
+
metadata: new bytes(0)
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function test_afterCashOutRecordedWith_burnCashOutNft(uint256 numberOfNfts) public {
|
|
230
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(5);
|
|
231
|
+
|
|
232
|
+
// Has to all fit in tier 1 (excluding reserve mints).
|
|
233
|
+
numberOfNfts = bound(numberOfNfts, 1, 90);
|
|
234
|
+
|
|
235
|
+
// Mock the directory call.
|
|
236
|
+
mockAndExpect(
|
|
237
|
+
address(mockJBDirectory),
|
|
238
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
239
|
+
abi.encode(true)
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
uint256[] memory tokenList = new uint256[](numberOfNfts);
|
|
243
|
+
|
|
244
|
+
bytes memory hookMetadata;
|
|
245
|
+
bytes[] memory data;
|
|
246
|
+
bytes4[] memory ids;
|
|
247
|
+
|
|
248
|
+
for (uint256 i; i < numberOfNfts; i++) {
|
|
249
|
+
uint16[] memory tierIdsToMint = new uint16[](1);
|
|
250
|
+
tierIdsToMint[0] = 1;
|
|
251
|
+
|
|
252
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
253
|
+
data = new bytes[](1);
|
|
254
|
+
data[0] = abi.encode(false, tierIdsToMint);
|
|
255
|
+
|
|
256
|
+
// Pass the hook ID.
|
|
257
|
+
ids = new bytes4[](1);
|
|
258
|
+
ids[0] = metadataHelper.getId("pay", address(hook));
|
|
259
|
+
|
|
260
|
+
// Generate the metadata.
|
|
261
|
+
hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
262
|
+
|
|
263
|
+
// Mint the NFTs. Otherwise, the voting balance is not incremented,
|
|
264
|
+
// which leads to an underflow upon cash out.
|
|
265
|
+
vm.prank(mockTerminalAddress);
|
|
266
|
+
JBAfterPayRecordedContext memory afterPayContext = JBAfterPayRecordedContext({
|
|
267
|
+
payer: beneficiary,
|
|
268
|
+
projectId: projectId,
|
|
269
|
+
rulesetId: 0,
|
|
270
|
+
amount: JBTokenAmount({
|
|
271
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
272
|
+
value: 10,
|
|
273
|
+
decimals: 18,
|
|
274
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
275
|
+
}),
|
|
276
|
+
forwardedAmount: JBTokenAmount({
|
|
277
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
278
|
+
value: 0,
|
|
279
|
+
decimals: 18,
|
|
280
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
281
|
+
}), // 0
|
|
282
|
+
// Forward to the hook.
|
|
283
|
+
weight: 10 ** 18,
|
|
284
|
+
newlyIssuedTokenCount: 0,
|
|
285
|
+
beneficiary: beneficiary,
|
|
286
|
+
hookMetadata: new bytes(0),
|
|
287
|
+
payerMetadata: hookMetadata
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
hook.afterPayRecordedWith(afterPayContext);
|
|
291
|
+
|
|
292
|
+
tokenList[i] = _generateTokenId(1, i + 1);
|
|
293
|
+
|
|
294
|
+
// Check: was a new NFT minted?
|
|
295
|
+
assertEq(hook.balanceOf(beneficiary), i + 1);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Build the metadata with the tiers to cash out.
|
|
299
|
+
data = new bytes[](1);
|
|
300
|
+
data[0] = abi.encode(tokenList);
|
|
301
|
+
|
|
302
|
+
// Pass the hook ID.
|
|
303
|
+
ids = new bytes4[](1);
|
|
304
|
+
ids[0] = metadataHelper.getId("cashOut", address(hook));
|
|
305
|
+
|
|
306
|
+
// Generate the metadata.
|
|
307
|
+
hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
308
|
+
|
|
309
|
+
vm.prank(mockTerminalAddress);
|
|
310
|
+
hook.afterCashOutRecordedWith(
|
|
311
|
+
JBAfterCashOutRecordedContext({
|
|
312
|
+
holder: beneficiary,
|
|
313
|
+
projectId: projectId,
|
|
314
|
+
rulesetId: 1,
|
|
315
|
+
cashOutCount: 0,
|
|
316
|
+
reclaimedAmount: JBTokenAmount({
|
|
317
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
318
|
+
}),
|
|
319
|
+
forwardedAmount: JBTokenAmount({
|
|
320
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
321
|
+
}), // 0, forwarded to the hook.
|
|
322
|
+
cashOutTaxRate: 5000,
|
|
323
|
+
beneficiary: payable(beneficiary),
|
|
324
|
+
hookMetadata: bytes(""),
|
|
325
|
+
cashOutMetadata: hookMetadata
|
|
326
|
+
})
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// Check: is the beneficiary's balance zero again?
|
|
330
|
+
assertEq(hook.balanceOf(beneficiary), 0);
|
|
331
|
+
|
|
332
|
+
// Check: was the number of burned NFTs recorded correctly (to match `numberOfNfts` in the first tier)?
|
|
333
|
+
assertEq(hook.test_store().numberOfBurnedFor(address(hook), 1), numberOfNfts);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function test_afterCashOutRecordedWith_revertIfNotCorrectProjectId(uint8 wrongProjectId) public {
|
|
337
|
+
vm.assume(wrongProjectId != projectId);
|
|
338
|
+
|
|
339
|
+
uint256[] memory tokenList = new uint256[](1);
|
|
340
|
+
tokenList[0] = 1;
|
|
341
|
+
|
|
342
|
+
// Mock the directory call.
|
|
343
|
+
mockAndExpect(
|
|
344
|
+
address(mockJBDirectory),
|
|
345
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
346
|
+
abi.encode(true)
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
// Expect to revert on account of the project ID being incorrect.
|
|
350
|
+
vm.expectRevert(abi.encodeWithSelector(JB721Hook.JB721Hook_InvalidCashOut.selector));
|
|
351
|
+
|
|
352
|
+
vm.prank(mockTerminalAddress);
|
|
353
|
+
hook.afterCashOutRecordedWith(
|
|
354
|
+
JBAfterCashOutRecordedContext({
|
|
355
|
+
holder: beneficiary,
|
|
356
|
+
projectId: wrongProjectId,
|
|
357
|
+
rulesetId: 1,
|
|
358
|
+
cashOutCount: 0,
|
|
359
|
+
reclaimedAmount: JBTokenAmount({
|
|
360
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
361
|
+
}),
|
|
362
|
+
forwardedAmount: JBTokenAmount({
|
|
363
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
364
|
+
}), // 0, forwarded to the hook.
|
|
365
|
+
cashOutTaxRate: 5000,
|
|
366
|
+
beneficiary: payable(beneficiary),
|
|
367
|
+
hookMetadata: bytes(""),
|
|
368
|
+
cashOutMetadata: abi.encode(type(IJB721TiersHook).interfaceId, tokenList)
|
|
369
|
+
})
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function test_afterCashOutRecordedWith_revertIfCallerIsNotATerminalOfTheProject() public {
|
|
374
|
+
uint256[] memory tokenList = new uint256[](1);
|
|
375
|
+
tokenList[0] = 1;
|
|
376
|
+
|
|
377
|
+
// Mock the directory call.
|
|
378
|
+
mockAndExpect(
|
|
379
|
+
address(mockJBDirectory),
|
|
380
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
381
|
+
abi.encode(false)
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// Expect to revert on account of the caller not being a terminal of the project.
|
|
385
|
+
vm.expectRevert(abi.encodeWithSelector(JB721Hook.JB721Hook_InvalidCashOut.selector));
|
|
386
|
+
|
|
387
|
+
vm.prank(mockTerminalAddress);
|
|
388
|
+
hook.afterCashOutRecordedWith(
|
|
389
|
+
JBAfterCashOutRecordedContext({
|
|
390
|
+
holder: beneficiary,
|
|
391
|
+
projectId: projectId,
|
|
392
|
+
rulesetId: 1,
|
|
393
|
+
cashOutCount: 0,
|
|
394
|
+
reclaimedAmount: JBTokenAmount({
|
|
395
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
396
|
+
}),
|
|
397
|
+
forwardedAmount: JBTokenAmount({
|
|
398
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
399
|
+
}), // 0, forwarded to the hook.
|
|
400
|
+
cashOutTaxRate: 5000,
|
|
401
|
+
beneficiary: payable(beneficiary),
|
|
402
|
+
hookMetadata: bytes(""),
|
|
403
|
+
cashOutMetadata: abi.encode(type(IJB721TiersHook).interfaceId, tokenList)
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function test_afterCashOutRecordedWith_revertIfWrongHolder(address wrongHolder, uint8 tokenId) public {
|
|
409
|
+
vm.assume(beneficiary != wrongHolder);
|
|
410
|
+
vm.assume(tokenId != 0);
|
|
411
|
+
|
|
412
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(1);
|
|
413
|
+
|
|
414
|
+
hook.ForTest_setOwnerOf(tokenId, beneficiary);
|
|
415
|
+
|
|
416
|
+
uint256[] memory tokenList = new uint256[](1);
|
|
417
|
+
tokenList[0] = tokenId;
|
|
418
|
+
|
|
419
|
+
// Build the metadata with the tiers to cash out.
|
|
420
|
+
bytes[] memory data = new bytes[](1);
|
|
421
|
+
data[0] = abi.encode(tokenList);
|
|
422
|
+
|
|
423
|
+
// Pass the hook ID.
|
|
424
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
425
|
+
ids[0] = metadataHelper.getId("cashOut", address(hook));
|
|
426
|
+
|
|
427
|
+
// Generate the metadata.
|
|
428
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
429
|
+
|
|
430
|
+
// Mock the directory call.
|
|
431
|
+
mockAndExpect(
|
|
432
|
+
address(mockJBDirectory),
|
|
433
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
434
|
+
abi.encode(true)
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
vm.expectRevert(abi.encodeWithSelector(JB721Hook.JB721Hook_UnauthorizedToken.selector, tokenId, wrongHolder));
|
|
438
|
+
|
|
439
|
+
vm.prank(mockTerminalAddress);
|
|
440
|
+
hook.afterCashOutRecordedWith(
|
|
441
|
+
JBAfterCashOutRecordedContext({
|
|
442
|
+
holder: wrongHolder,
|
|
443
|
+
projectId: projectId,
|
|
444
|
+
rulesetId: 1,
|
|
445
|
+
cashOutCount: 0,
|
|
446
|
+
reclaimedAmount: JBTokenAmount({
|
|
447
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
448
|
+
}),
|
|
449
|
+
forwardedAmount: JBTokenAmount({
|
|
450
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
451
|
+
}), // 0, forwarded to the hook.
|
|
452
|
+
cashOutTaxRate: 5000,
|
|
453
|
+
beneficiary: payable(wrongHolder),
|
|
454
|
+
hookMetadata: bytes(""),
|
|
455
|
+
cashOutMetadata: hookMetadata
|
|
456
|
+
})
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
}
|