@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,1537 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.23;
|
|
3
|
+
|
|
4
|
+
import "../utils/UnitTestSetup.sol";
|
|
5
|
+
|
|
6
|
+
contract Test_afterPayRecorded_Unit is UnitTestSetup {
|
|
7
|
+
using stdStorage for StdStorage;
|
|
8
|
+
|
|
9
|
+
function test_afterPayRecorded_mintAndReserveCorrectAmounts(
|
|
10
|
+
uint256 initialSupply,
|
|
11
|
+
uint256 nftsToMint,
|
|
12
|
+
uint256 reserveFrequency
|
|
13
|
+
)
|
|
14
|
+
public
|
|
15
|
+
{
|
|
16
|
+
initialSupply = 400;
|
|
17
|
+
reserveFrequency = bound(reserveFrequency, 0, 200);
|
|
18
|
+
nftsToMint = bound(nftsToMint, 1, 200);
|
|
19
|
+
|
|
20
|
+
defaultTierConfig.initialSupply = uint32(initialSupply);
|
|
21
|
+
defaultTierConfig.reserveFrequency = uint16(reserveFrequency);
|
|
22
|
+
ForTest_JB721TiersHook hook = _initializeForTestHook(1); // Initialize with 1 default tier.
|
|
23
|
+
|
|
24
|
+
// Mock the directory call.
|
|
25
|
+
mockAndExpect(
|
|
26
|
+
address(mockJBDirectory),
|
|
27
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
28
|
+
abi.encode(true)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
uint16[] memory tierIdsToMint = new uint16[](nftsToMint);
|
|
32
|
+
|
|
33
|
+
for (uint256 i; i < nftsToMint; i++) {
|
|
34
|
+
tierIdsToMint[i] = uint16(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
38
|
+
bytes[] memory data = new bytes[](1);
|
|
39
|
+
data[0] = abi.encode(false, tierIdsToMint);
|
|
40
|
+
|
|
41
|
+
// Pass the hook ID.
|
|
42
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
43
|
+
ids[0] = metadataHelper.getId("pay", address(hook));
|
|
44
|
+
|
|
45
|
+
// Generate the metadata.
|
|
46
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
47
|
+
|
|
48
|
+
JBAfterPayRecordedContext memory payContext = JBAfterPayRecordedContext({
|
|
49
|
+
payer: beneficiary,
|
|
50
|
+
projectId: projectId,
|
|
51
|
+
rulesetId: 0,
|
|
52
|
+
amount: JBTokenAmount({
|
|
53
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
54
|
+
value: 10 * nftsToMint,
|
|
55
|
+
decimals: 18,
|
|
56
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
57
|
+
}),
|
|
58
|
+
forwardedAmount: JBTokenAmount({
|
|
59
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
60
|
+
value: 0,
|
|
61
|
+
decimals: 18,
|
|
62
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
63
|
+
}), // 0,
|
|
64
|
+
// forwarded to the hook.
|
|
65
|
+
weight: 10 ** 18,
|
|
66
|
+
newlyIssuedTokenCount: 0,
|
|
67
|
+
beneficiary: beneficiary,
|
|
68
|
+
hookMetadata: bytes(""),
|
|
69
|
+
payerMetadata: hookMetadata
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
vm.prank(mockTerminalAddress);
|
|
73
|
+
hook.afterPayRecordedWith(payContext);
|
|
74
|
+
|
|
75
|
+
// Check: has the correct number of NFTs been minted for the beneficiary?
|
|
76
|
+
assertEq(hook.balanceOf(beneficiary), nftsToMint);
|
|
77
|
+
|
|
78
|
+
// Check: were the correct number of NFTs reserved?
|
|
79
|
+
if (reserveFrequency > 0 && initialSupply - nftsToMint > 0) {
|
|
80
|
+
uint256 reservedToken = nftsToMint / reserveFrequency;
|
|
81
|
+
if (nftsToMint % reserveFrequency > 0) reservedToken += 1;
|
|
82
|
+
|
|
83
|
+
assertEq(hook.STORE().numberOfPendingReservesFor(address(hook), 1), reservedToken);
|
|
84
|
+
|
|
85
|
+
// Mint the pending reserves for the beneficiary.
|
|
86
|
+
vm.prank(owner);
|
|
87
|
+
hook.mintPendingReservesFor(1, reservedToken);
|
|
88
|
+
|
|
89
|
+
// Check: did the reserve beneficiary receive the correct number of NFTs?
|
|
90
|
+
assertEq(hook.balanceOf(reserveBeneficiary), reservedToken);
|
|
91
|
+
} else {
|
|
92
|
+
// Check: does the reserve beneficiary have no NFTs?
|
|
93
|
+
assertEq(hook.balanceOf(reserveBeneficiary), 0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If the amount paid is less than the NFT's price, the payment should revert if overspending is not allowed and no
|
|
98
|
+
// metadata was passed.
|
|
99
|
+
function test_afterPayRecorded_revertsOnAmountBelowPriceIfNoMetadataAndOverspendingIsPrevented() public {
|
|
100
|
+
JB721TiersHook hook = _initHookDefaultTiers(10, true);
|
|
101
|
+
|
|
102
|
+
// Mock the directory call.
|
|
103
|
+
mockAndExpect(
|
|
104
|
+
address(mockJBDirectory),
|
|
105
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
106
|
+
abi.encode(true)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Expect a revert for overspending.
|
|
110
|
+
vm.expectRevert(abi.encodeWithSelector(JB721TiersHook.JB721TiersHook_Overspending.selector, tiers[0].price - 1));
|
|
111
|
+
|
|
112
|
+
vm.prank(mockTerminalAddress);
|
|
113
|
+
hook.afterPayRecordedWith(
|
|
114
|
+
JBAfterPayRecordedContext({
|
|
115
|
+
payer: msg.sender,
|
|
116
|
+
projectId: projectId,
|
|
117
|
+
rulesetId: 0,
|
|
118
|
+
// 1 wei below the minimum amount
|
|
119
|
+
amount: JBTokenAmount({
|
|
120
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
121
|
+
value: tiers[0].price - 1,
|
|
122
|
+
decimals: 18,
|
|
123
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
124
|
+
}),
|
|
125
|
+
forwardedAmount: JBTokenAmount({
|
|
126
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
127
|
+
value: 0,
|
|
128
|
+
decimals: 18,
|
|
129
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
130
|
+
}), // 0,
|
|
131
|
+
// forwarded to the hook.
|
|
132
|
+
weight: 10 ** 18,
|
|
133
|
+
newlyIssuedTokenCount: 0,
|
|
134
|
+
beneficiary: msg.sender,
|
|
135
|
+
hookMetadata: new bytes(0),
|
|
136
|
+
payerMetadata: new bytes(0)
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If the amount paid is less than the NFT's price, the payment should not revert if overspending is allowed and no
|
|
142
|
+
// metadata was passed.
|
|
143
|
+
function test_afterPayRecorded_doesNotRevertOnAmountBelowPriceIfNoMetadata() public {
|
|
144
|
+
// Mock the directory call.
|
|
145
|
+
mockAndExpect(
|
|
146
|
+
address(mockJBDirectory),
|
|
147
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
148
|
+
abi.encode(true)
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
vm.prank(mockTerminalAddress);
|
|
152
|
+
hook.afterPayRecordedWith(
|
|
153
|
+
JBAfterPayRecordedContext({
|
|
154
|
+
payer: msg.sender,
|
|
155
|
+
projectId: projectId,
|
|
156
|
+
rulesetId: 0,
|
|
157
|
+
// 1 wei below the minimum amount
|
|
158
|
+
amount: JBTokenAmount({
|
|
159
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
160
|
+
value: tiers[0].price - 1,
|
|
161
|
+
decimals: 18,
|
|
162
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
163
|
+
}),
|
|
164
|
+
forwardedAmount: JBTokenAmount({
|
|
165
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
166
|
+
value: 0,
|
|
167
|
+
decimals: 18,
|
|
168
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
169
|
+
}), // 0,
|
|
170
|
+
// forwarded to the hook.
|
|
171
|
+
weight: 10 ** 18,
|
|
172
|
+
newlyIssuedTokenCount: 0,
|
|
173
|
+
beneficiary: msg.sender,
|
|
174
|
+
hookMetadata: new bytes(0),
|
|
175
|
+
payerMetadata: new bytes(0)
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Check: does the payer have the correct number of pay credits?
|
|
180
|
+
assertEq(hook.payCreditsOf(msg.sender), tiers[0].price - 1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// If a tier is passed and the amount paid exceeds that NFT's price, mint as many NFTs as possible.
|
|
184
|
+
function test_afterPayRecorded_mintCorrectTier() public {
|
|
185
|
+
// Mock the directory call.
|
|
186
|
+
mockAndExpect(
|
|
187
|
+
address(mockJBDirectory),
|
|
188
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
189
|
+
abi.encode(true)
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
uint256 totalSupplyBeforePay = hook.STORE().totalSupplyOf(address(hook));
|
|
193
|
+
|
|
194
|
+
bool allowOverspending;
|
|
195
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
196
|
+
tierIdsToMint[0] = 1;
|
|
197
|
+
tierIdsToMint[1] = 1;
|
|
198
|
+
tierIdsToMint[2] = 2;
|
|
199
|
+
|
|
200
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
201
|
+
bytes[] memory data = new bytes[](1);
|
|
202
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
203
|
+
|
|
204
|
+
// Pass the hook ID.
|
|
205
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
206
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
207
|
+
|
|
208
|
+
// Generate the metadata.
|
|
209
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
210
|
+
vm.prank(mockTerminalAddress);
|
|
211
|
+
hook.afterPayRecordedWith(
|
|
212
|
+
JBAfterPayRecordedContext({
|
|
213
|
+
payer: msg.sender,
|
|
214
|
+
projectId: projectId,
|
|
215
|
+
rulesetId: 0,
|
|
216
|
+
amount: JBTokenAmount({
|
|
217
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
218
|
+
value: tiers[0].price * 2 + tiers[1].price,
|
|
219
|
+
decimals: 18,
|
|
220
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
221
|
+
}),
|
|
222
|
+
forwardedAmount: JBTokenAmount({
|
|
223
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
224
|
+
value: 0,
|
|
225
|
+
decimals: 18,
|
|
226
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
227
|
+
}), // 0,
|
|
228
|
+
// forwarded to the hook.
|
|
229
|
+
weight: 10 ** 18,
|
|
230
|
+
newlyIssuedTokenCount: 0,
|
|
231
|
+
beneficiary: msg.sender,
|
|
232
|
+
hookMetadata: new bytes(0),
|
|
233
|
+
payerMetadata: hookMetadata
|
|
234
|
+
})
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Check: has the correct number of NFTs been minted?
|
|
238
|
+
assertEq(totalSupplyBeforePay + 3, hook.STORE().totalSupplyOf(address(hook)));
|
|
239
|
+
|
|
240
|
+
// Check: has the correct number of NFTs been minted in each tier?
|
|
241
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 1)), msg.sender);
|
|
242
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 2)), msg.sender);
|
|
243
|
+
assertEq(hook.ownerOf(_generateTokenId(2, 1)), msg.sender);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// If no tiers are passed, no NFTs should be minted.
|
|
247
|
+
function test_afterPayRecorded_mintNoneIfNonePassed(uint8 amount) public {
|
|
248
|
+
// Mock the directory call.
|
|
249
|
+
mockAndExpect(
|
|
250
|
+
address(mockJBDirectory),
|
|
251
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
252
|
+
abi.encode(true)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
uint256 totalSupplyBeforePay = hook.STORE().totalSupplyOf(address(hook));
|
|
256
|
+
|
|
257
|
+
bool allowOverspending = true;
|
|
258
|
+
uint16[] memory tierIdsToMint = new uint16[](0);
|
|
259
|
+
bytes memory metadata =
|
|
260
|
+
abi.encode(bytes32(0), bytes32(0), type(IJB721TiersHook).interfaceId, allowOverspending, tierIdsToMint);
|
|
261
|
+
|
|
262
|
+
vm.prank(mockTerminalAddress);
|
|
263
|
+
hook.afterPayRecordedWith(
|
|
264
|
+
JBAfterPayRecordedContext({
|
|
265
|
+
payer: msg.sender,
|
|
266
|
+
projectId: projectId,
|
|
267
|
+
rulesetId: 0,
|
|
268
|
+
amount: JBTokenAmount({
|
|
269
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
270
|
+
value: amount,
|
|
271
|
+
decimals: 18,
|
|
272
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
273
|
+
}),
|
|
274
|
+
forwardedAmount: JBTokenAmount({
|
|
275
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
276
|
+
value: 0,
|
|
277
|
+
decimals: 18,
|
|
278
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
279
|
+
}), // 0,
|
|
280
|
+
// forwarded to the hook.
|
|
281
|
+
weight: 10 ** 18,
|
|
282
|
+
newlyIssuedTokenCount: 0,
|
|
283
|
+
beneficiary: msg.sender,
|
|
284
|
+
hookMetadata: new bytes(0),
|
|
285
|
+
payerMetadata: metadata
|
|
286
|
+
})
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Check: has the total supply stayed the same?
|
|
290
|
+
assertEq(totalSupplyBeforePay, hook.STORE().totalSupplyOf(address(hook)));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function test_afterPayRecorded_mintTierAndTrackLeftover() public {
|
|
294
|
+
uint256 leftover = tiers[0].price - 1;
|
|
295
|
+
uint256 amount = tiers[0].price + leftover;
|
|
296
|
+
|
|
297
|
+
// Mock the directory call.
|
|
298
|
+
mockAndExpect(
|
|
299
|
+
address(mockJBDirectory),
|
|
300
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
301
|
+
abi.encode(true)
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
bool allowOverspending = true;
|
|
305
|
+
uint16[] memory tierIdsToMint = new uint16[](1);
|
|
306
|
+
tierIdsToMint[0] = uint16(1);
|
|
307
|
+
|
|
308
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
309
|
+
bytes[] memory data = new bytes[](1);
|
|
310
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
311
|
+
|
|
312
|
+
// Pass the hook ID.
|
|
313
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
314
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
315
|
+
|
|
316
|
+
// Generate the metadata.
|
|
317
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
318
|
+
|
|
319
|
+
// Calculate the new pay credits.
|
|
320
|
+
uint256 newPayCredits = leftover + hook.payCreditsOf(beneficiary);
|
|
321
|
+
|
|
322
|
+
vm.expectEmit(true, true, true, true, address(hook));
|
|
323
|
+
emit AddPayCredits(newPayCredits, newPayCredits, beneficiary, mockTerminalAddress);
|
|
324
|
+
|
|
325
|
+
vm.prank(mockTerminalAddress);
|
|
326
|
+
hook.afterPayRecordedWith(
|
|
327
|
+
JBAfterPayRecordedContext({
|
|
328
|
+
payer: msg.sender,
|
|
329
|
+
projectId: projectId,
|
|
330
|
+
rulesetId: 0,
|
|
331
|
+
amount: JBTokenAmount({
|
|
332
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
333
|
+
value: amount,
|
|
334
|
+
decimals: 18,
|
|
335
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
336
|
+
}),
|
|
337
|
+
forwardedAmount: JBTokenAmount({
|
|
338
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
339
|
+
value: 0,
|
|
340
|
+
decimals: 18,
|
|
341
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
342
|
+
}), // 0,
|
|
343
|
+
// forwarded to the hook.
|
|
344
|
+
weight: 10 ** 18,
|
|
345
|
+
newlyIssuedTokenCount: 0,
|
|
346
|
+
beneficiary: beneficiary,
|
|
347
|
+
hookMetadata: new bytes(0),
|
|
348
|
+
payerMetadata: hookMetadata
|
|
349
|
+
})
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
// Check: has the pay credit balance been updated appropriately?
|
|
353
|
+
assertEq(hook.payCreditsOf(beneficiary), leftover);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Mint various tiers, leaving leftovers, and use the resulting pay credits to mint more NFTs.
|
|
357
|
+
function test_afterPayRecorded_mintCorrectTiersWhenPartiallyUsingPayCredits() public {
|
|
358
|
+
uint256 leftover = tiers[0].price + 1; // + 1 to avoid rounding error
|
|
359
|
+
uint256 amount = tiers[0].price * 2 + tiers[1].price + leftover / 2;
|
|
360
|
+
|
|
361
|
+
// Mock the directory call.
|
|
362
|
+
mockAndExpect(
|
|
363
|
+
address(mockJBDirectory),
|
|
364
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
365
|
+
abi.encode(true)
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
bool allowOverspending = true;
|
|
369
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
370
|
+
tierIdsToMint[0] = 1;
|
|
371
|
+
tierIdsToMint[1] = 1;
|
|
372
|
+
tierIdsToMint[2] = 2;
|
|
373
|
+
|
|
374
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
375
|
+
bytes[] memory data = new bytes[](1);
|
|
376
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
377
|
+
|
|
378
|
+
// Pass the hook ID.
|
|
379
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
380
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
381
|
+
|
|
382
|
+
// Generate the metadata.
|
|
383
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
384
|
+
|
|
385
|
+
uint256 payCredits = hook.payCreditsOf(beneficiary);
|
|
386
|
+
|
|
387
|
+
leftover = leftover / 2 + payCredits; // Amount left over.
|
|
388
|
+
|
|
389
|
+
vm.expectEmit(true, true, true, true, address(hook));
|
|
390
|
+
emit AddPayCredits(leftover - payCredits, leftover, beneficiary, mockTerminalAddress);
|
|
391
|
+
|
|
392
|
+
// First call will mint the 3 tiers requested and accumulate half of the first price in pay credits.
|
|
393
|
+
vm.prank(mockTerminalAddress);
|
|
394
|
+
hook.afterPayRecordedWith(
|
|
395
|
+
JBAfterPayRecordedContext({
|
|
396
|
+
payer: beneficiary,
|
|
397
|
+
projectId: projectId,
|
|
398
|
+
rulesetId: 0,
|
|
399
|
+
amount: JBTokenAmount({
|
|
400
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
401
|
+
value: amount,
|
|
402
|
+
decimals: 18,
|
|
403
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
404
|
+
}),
|
|
405
|
+
forwardedAmount: JBTokenAmount({
|
|
406
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
407
|
+
value: 0,
|
|
408
|
+
decimals: 18,
|
|
409
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
410
|
+
}), // 0,
|
|
411
|
+
// forwarded to the hook.
|
|
412
|
+
weight: 10 ** 18,
|
|
413
|
+
newlyIssuedTokenCount: 0,
|
|
414
|
+
beneficiary: beneficiary,
|
|
415
|
+
hookMetadata: new bytes(0),
|
|
416
|
+
payerMetadata: hookMetadata
|
|
417
|
+
})
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
uint256 totalSupplyBefore = hook.STORE().totalSupplyOf(address(hook));
|
|
421
|
+
{
|
|
422
|
+
// We now attempt to mint an additional NFT from tier 1 using the pay credits we collected.
|
|
423
|
+
uint16[] memory moreTierIdsToMint = new uint16[](4);
|
|
424
|
+
moreTierIdsToMint[0] = 1;
|
|
425
|
+
moreTierIdsToMint[1] = 1;
|
|
426
|
+
moreTierIdsToMint[2] = 2;
|
|
427
|
+
moreTierIdsToMint[3] = 1;
|
|
428
|
+
|
|
429
|
+
data[0] = abi.encode(allowOverspending, moreTierIdsToMint);
|
|
430
|
+
|
|
431
|
+
// Generate the metadata.
|
|
432
|
+
hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Fetch existing credits.
|
|
436
|
+
payCredits = hook.payCreditsOf(beneficiary);
|
|
437
|
+
vm.expectEmit(true, true, true, true, address(hook));
|
|
438
|
+
emit UsePayCredits(
|
|
439
|
+
payCredits,
|
|
440
|
+
0, // No stashed credits.
|
|
441
|
+
beneficiary,
|
|
442
|
+
mockTerminalAddress
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
// Second call will mint another 3 tiers requested and mint from the first tier using pay credits.
|
|
446
|
+
vm.prank(mockTerminalAddress);
|
|
447
|
+
hook.afterPayRecordedWith(
|
|
448
|
+
JBAfterPayRecordedContext({
|
|
449
|
+
payer: beneficiary,
|
|
450
|
+
projectId: projectId,
|
|
451
|
+
rulesetId: 0,
|
|
452
|
+
amount: JBTokenAmount({
|
|
453
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
454
|
+
value: amount,
|
|
455
|
+
decimals: 18,
|
|
456
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
457
|
+
}),
|
|
458
|
+
forwardedAmount: JBTokenAmount({
|
|
459
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
460
|
+
value: 0,
|
|
461
|
+
decimals: 18,
|
|
462
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
463
|
+
}), // 0,
|
|
464
|
+
// forwarded to the hook.
|
|
465
|
+
weight: 10 ** 18,
|
|
466
|
+
newlyIssuedTokenCount: 0,
|
|
467
|
+
beneficiary: beneficiary,
|
|
468
|
+
hookMetadata: new bytes(0),
|
|
469
|
+
payerMetadata: hookMetadata
|
|
470
|
+
})
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
// Check: has the total supply increased?
|
|
474
|
+
assertEq(totalSupplyBefore + 4, hook.STORE().totalSupplyOf(address(hook)));
|
|
475
|
+
|
|
476
|
+
// Check: have the correct tiers been minted...
|
|
477
|
+
// ... from the first payment?
|
|
478
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 1)), beneficiary);
|
|
479
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 2)), beneficiary);
|
|
480
|
+
assertEq(hook.ownerOf(_generateTokenId(2, 1)), beneficiary);
|
|
481
|
+
|
|
482
|
+
// ... from the second payment?
|
|
483
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 3)), beneficiary);
|
|
484
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 4)), beneficiary);
|
|
485
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 5)), beneficiary);
|
|
486
|
+
assertEq(hook.ownerOf(_generateTokenId(2, 2)), beneficiary);
|
|
487
|
+
|
|
488
|
+
// Check: have all pay credits been used?
|
|
489
|
+
assertEq(hook.payCreditsOf(beneficiary), 0);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function test_afterPayRecorded_doNotMintWithSomeoneElsesCredits() public {
|
|
493
|
+
uint256 leftover = tiers[0].price + 1; // + 1 to avoid rounding error.
|
|
494
|
+
uint256 amount = tiers[0].price * 2 + tiers[1].price + leftover / 2;
|
|
495
|
+
|
|
496
|
+
// Mock the directory call.
|
|
497
|
+
mockAndExpect(
|
|
498
|
+
address(mockJBDirectory),
|
|
499
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
500
|
+
abi.encode(true)
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
bool allowOverspending = true;
|
|
504
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
505
|
+
tierIdsToMint[0] = 1;
|
|
506
|
+
tierIdsToMint[1] = 1;
|
|
507
|
+
tierIdsToMint[2] = 2;
|
|
508
|
+
|
|
509
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
510
|
+
bytes[] memory data = new bytes[](1);
|
|
511
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
512
|
+
|
|
513
|
+
// Pass the hook ID.
|
|
514
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
515
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
516
|
+
|
|
517
|
+
// Generate the metadata.
|
|
518
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
519
|
+
|
|
520
|
+
// The first call will mint the 3 tiers requested and accumulate half of the first price as pay credits.
|
|
521
|
+
vm.prank(mockTerminalAddress);
|
|
522
|
+
hook.afterPayRecordedWith(
|
|
523
|
+
JBAfterPayRecordedContext({
|
|
524
|
+
payer: beneficiary,
|
|
525
|
+
projectId: projectId,
|
|
526
|
+
rulesetId: 0,
|
|
527
|
+
amount: JBTokenAmount({
|
|
528
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
529
|
+
value: amount,
|
|
530
|
+
decimals: 18,
|
|
531
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
532
|
+
}),
|
|
533
|
+
forwardedAmount: JBTokenAmount({
|
|
534
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
535
|
+
value: 0,
|
|
536
|
+
decimals: 18,
|
|
537
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
538
|
+
}), // 0,
|
|
539
|
+
// forwarded to the hook.
|
|
540
|
+
weight: 10 ** 18,
|
|
541
|
+
newlyIssuedTokenCount: 0,
|
|
542
|
+
beneficiary: beneficiary,
|
|
543
|
+
hookMetadata: new bytes(0),
|
|
544
|
+
payerMetadata: hookMetadata
|
|
545
|
+
})
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
uint256 totalSupplyBefore = hook.STORE().totalSupplyOf(address(hook));
|
|
549
|
+
uint256 payCreditsBefore = hook.payCreditsOf(beneficiary);
|
|
550
|
+
|
|
551
|
+
// The second call will mint another 3 tiers requested but NOT with the pay credits.
|
|
552
|
+
vm.prank(mockTerminalAddress);
|
|
553
|
+
hook.afterPayRecordedWith(
|
|
554
|
+
JBAfterPayRecordedContext({
|
|
555
|
+
payer: msg.sender,
|
|
556
|
+
projectId: projectId,
|
|
557
|
+
rulesetId: 0,
|
|
558
|
+
amount: JBTokenAmount({
|
|
559
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
560
|
+
value: amount,
|
|
561
|
+
decimals: 18,
|
|
562
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
563
|
+
}),
|
|
564
|
+
forwardedAmount: JBTokenAmount({
|
|
565
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
566
|
+
value: 0,
|
|
567
|
+
decimals: 18,
|
|
568
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
569
|
+
}), // 0,
|
|
570
|
+
// forwarded to the hook.
|
|
571
|
+
weight: 10 ** 18,
|
|
572
|
+
newlyIssuedTokenCount: 0,
|
|
573
|
+
beneficiary: beneficiary,
|
|
574
|
+
hookMetadata: new bytes(0),
|
|
575
|
+
payerMetadata: hookMetadata
|
|
576
|
+
})
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
// Check: has the total supply has increased by 3 NFTs?
|
|
580
|
+
assertEq(totalSupplyBefore + 3, hook.STORE().totalSupplyOf(address(hook)));
|
|
581
|
+
|
|
582
|
+
// Check: were the correct tiers minted...
|
|
583
|
+
// ... from the first payment?
|
|
584
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 1)), beneficiary);
|
|
585
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 2)), beneficiary);
|
|
586
|
+
assertEq(hook.ownerOf(_generateTokenId(2, 1)), beneficiary);
|
|
587
|
+
|
|
588
|
+
// ... from the second payment (without extras from the pay credits)?
|
|
589
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 3)), beneficiary);
|
|
590
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 4)), beneficiary);
|
|
591
|
+
assertEq(hook.ownerOf(_generateTokenId(2, 2)), beneficiary);
|
|
592
|
+
|
|
593
|
+
// Check: are pay credits from both payments left over?
|
|
594
|
+
assertEq(hook.payCreditsOf(beneficiary), payCreditsBefore * 2);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// The terminal uses currency 1 with 18 decimals, and the hook uses currency 2 with 9 decimals.
|
|
598
|
+
// The conversion rate is set at 1:2.
|
|
599
|
+
function test_afterPayRecorded_mintCorrectTierWithAnotherCurrency() public {
|
|
600
|
+
address jbPrice = address(bytes20(keccak256("MockJBPrice")));
|
|
601
|
+
vm.etch(jbPrice, new bytes(1));
|
|
602
|
+
|
|
603
|
+
// Currency 2, with 9 decimals.
|
|
604
|
+
JB721TiersHook hook = _initHookDefaultTiers(10, false, 2, 9, jbPrice);
|
|
605
|
+
|
|
606
|
+
// Mock the directory call.
|
|
607
|
+
mockAndExpect(
|
|
608
|
+
address(mockJBDirectory),
|
|
609
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
610
|
+
abi.encode(true)
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
// Mock the price oracle call.
|
|
614
|
+
uint256 amountInEth = (tiers[0].price * 2 + tiers[1].price) * 2;
|
|
615
|
+
mockAndExpect(
|
|
616
|
+
jbPrice,
|
|
617
|
+
abi.encodeCall(IJBPrices.pricePerUnitOf, (projectId, uint32(uint160(JBConstants.NATIVE_TOKEN)), 2, 18)),
|
|
618
|
+
abi.encode(2 * 10 ** 9)
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
uint256 totalSupplyBeforePay = hook.STORE().totalSupplyOf(address(hook));
|
|
622
|
+
|
|
623
|
+
bool allowOverspending = true;
|
|
624
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
625
|
+
tierIdsToMint[0] = 1;
|
|
626
|
+
tierIdsToMint[1] = 1;
|
|
627
|
+
tierIdsToMint[2] = 2;
|
|
628
|
+
|
|
629
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
630
|
+
bytes[] memory data = new bytes[](1);
|
|
631
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
632
|
+
|
|
633
|
+
// Pass the hook ID.
|
|
634
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
635
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
636
|
+
|
|
637
|
+
// Generate the metadata.
|
|
638
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
639
|
+
|
|
640
|
+
vm.prank(mockTerminalAddress);
|
|
641
|
+
hook.afterPayRecordedWith(
|
|
642
|
+
JBAfterPayRecordedContext({
|
|
643
|
+
payer: msg.sender,
|
|
644
|
+
projectId: projectId,
|
|
645
|
+
rulesetId: 0,
|
|
646
|
+
amount: JBTokenAmount({
|
|
647
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
648
|
+
value: amountInEth,
|
|
649
|
+
decimals: 18,
|
|
650
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
651
|
+
}),
|
|
652
|
+
forwardedAmount: JBTokenAmount({
|
|
653
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
654
|
+
value: 0,
|
|
655
|
+
decimals: 18,
|
|
656
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
657
|
+
}), // 0,
|
|
658
|
+
// forwarded to the hook.
|
|
659
|
+
weight: 10 ** 18,
|
|
660
|
+
newlyIssuedTokenCount: 0,
|
|
661
|
+
beneficiary: msg.sender,
|
|
662
|
+
hookMetadata: new bytes(0),
|
|
663
|
+
payerMetadata: hookMetadata
|
|
664
|
+
})
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
// Make sure 3 new NFTs were minted.
|
|
668
|
+
assertEq(totalSupplyBeforePay + 3, hook.STORE().totalSupplyOf(address(hook)));
|
|
669
|
+
|
|
670
|
+
// Check: have the correct NFT tiers been minted?
|
|
671
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 1)), msg.sender);
|
|
672
|
+
assertEq(hook.ownerOf(_generateTokenId(1, 2)), msg.sender);
|
|
673
|
+
assertEq(hook.ownerOf(_generateTokenId(2, 1)), msg.sender);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// If the tier has been removed, revert.
|
|
677
|
+
function test_afterPayRecorded_revertIfTierRemoved() public {
|
|
678
|
+
// Mock the directory call.
|
|
679
|
+
mockAndExpect(
|
|
680
|
+
address(mockJBDirectory),
|
|
681
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
682
|
+
abi.encode(true)
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
uint256 totalSupplyBeforePay = hook.STORE().totalSupplyOf(address(hook));
|
|
686
|
+
|
|
687
|
+
bool allowOverspending;
|
|
688
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
689
|
+
tierIdsToMint[0] = 1;
|
|
690
|
+
tierIdsToMint[1] = 1;
|
|
691
|
+
tierIdsToMint[2] = 2;
|
|
692
|
+
|
|
693
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
694
|
+
bytes[] memory data = new bytes[](1);
|
|
695
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
696
|
+
|
|
697
|
+
// Pass the hook ID.
|
|
698
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
699
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
700
|
+
|
|
701
|
+
// Generate the metadata.
|
|
702
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
703
|
+
|
|
704
|
+
uint256[] memory toRemove = new uint256[](1);
|
|
705
|
+
toRemove[0] = 1;
|
|
706
|
+
|
|
707
|
+
vm.prank(owner);
|
|
708
|
+
hook.adjustTiers(new JB721TierConfig[](0), toRemove);
|
|
709
|
+
|
|
710
|
+
vm.expectRevert(abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_TierRemoved.selector, 1));
|
|
711
|
+
|
|
712
|
+
vm.prank(mockTerminalAddress);
|
|
713
|
+
hook.afterPayRecordedWith(
|
|
714
|
+
JBAfterPayRecordedContext({
|
|
715
|
+
payer: msg.sender,
|
|
716
|
+
projectId: projectId,
|
|
717
|
+
rulesetId: 0,
|
|
718
|
+
amount: JBTokenAmount({
|
|
719
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
720
|
+
value: tiers[0].price * 2 + tiers[1].price,
|
|
721
|
+
decimals: 18,
|
|
722
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
723
|
+
}),
|
|
724
|
+
forwardedAmount: JBTokenAmount({
|
|
725
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
726
|
+
value: 0,
|
|
727
|
+
decimals: 18,
|
|
728
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
729
|
+
}), // 0,
|
|
730
|
+
// forwarded to the hook.
|
|
731
|
+
weight: 10 ** 18,
|
|
732
|
+
newlyIssuedTokenCount: 0,
|
|
733
|
+
beneficiary: msg.sender,
|
|
734
|
+
hookMetadata: new bytes(0),
|
|
735
|
+
payerMetadata: hookMetadata
|
|
736
|
+
})
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
// Check: has the total supply stayed the same?
|
|
740
|
+
assertEq(totalSupplyBeforePay, hook.STORE().totalSupplyOf(address(hook)));
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function test_afterPayRecorded_revertIfTierDoesNotExist(uint256 invalidTier) public {
|
|
744
|
+
invalidTier = bound(invalidTier, tiers.length + 1, type(uint16).max);
|
|
745
|
+
|
|
746
|
+
// Mock the directory call.
|
|
747
|
+
mockAndExpect(
|
|
748
|
+
address(mockJBDirectory),
|
|
749
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
750
|
+
abi.encode(true)
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
uint256 totalSupplyBeforePay = hook.STORE().totalSupplyOf(address(hook));
|
|
754
|
+
|
|
755
|
+
bool allowOverspending;
|
|
756
|
+
uint16[] memory tierIdsToMint = new uint16[](1);
|
|
757
|
+
tierIdsToMint[0] = uint16(invalidTier);
|
|
758
|
+
|
|
759
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
760
|
+
bytes[] memory data = new bytes[](1);
|
|
761
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
762
|
+
|
|
763
|
+
// Pass the hook ID.
|
|
764
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
765
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
766
|
+
|
|
767
|
+
// Generate the metadata.
|
|
768
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
769
|
+
|
|
770
|
+
uint256[] memory toRemove = new uint256[](1);
|
|
771
|
+
toRemove[0] = 1;
|
|
772
|
+
|
|
773
|
+
vm.prank(owner);
|
|
774
|
+
hook.adjustTiers(new JB721TierConfig[](0), toRemove);
|
|
775
|
+
|
|
776
|
+
vm.expectRevert(
|
|
777
|
+
abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_UnrecognizedTier.selector, invalidTier)
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
vm.prank(mockTerminalAddress);
|
|
781
|
+
hook.afterPayRecordedWith(
|
|
782
|
+
JBAfterPayRecordedContext({
|
|
783
|
+
payer: msg.sender,
|
|
784
|
+
projectId: projectId,
|
|
785
|
+
rulesetId: 0,
|
|
786
|
+
amount: JBTokenAmount({
|
|
787
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
788
|
+
value: tiers[0].price * 2 + tiers[1].price,
|
|
789
|
+
decimals: 18,
|
|
790
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
791
|
+
}),
|
|
792
|
+
forwardedAmount: JBTokenAmount({
|
|
793
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
794
|
+
value: 0,
|
|
795
|
+
decimals: 18,
|
|
796
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
797
|
+
}), // 0,
|
|
798
|
+
// forwarded to the hook.
|
|
799
|
+
weight: 10 ** 18,
|
|
800
|
+
newlyIssuedTokenCount: 0,
|
|
801
|
+
beneficiary: msg.sender,
|
|
802
|
+
hookMetadata: new bytes(0),
|
|
803
|
+
payerMetadata: hookMetadata
|
|
804
|
+
})
|
|
805
|
+
);
|
|
806
|
+
|
|
807
|
+
// Check: has the total supply stayed the same?
|
|
808
|
+
assertEq(totalSupplyBeforePay, hook.STORE().totalSupplyOf(address(hook)));
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// If the amount is not enought to pay for all of the requested tiers, revert.
|
|
812
|
+
function test_afterPayRecorded_revertIfAmountTooLow() public {
|
|
813
|
+
// Mock the directory call.
|
|
814
|
+
mockAndExpect(
|
|
815
|
+
address(mockJBDirectory),
|
|
816
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
817
|
+
abi.encode(true)
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
uint256 totalSupplyBeforePay = hook.STORE().totalSupplyOf(address(hook));
|
|
821
|
+
|
|
822
|
+
bool allowOverspending;
|
|
823
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
824
|
+
tierIdsToMint[0] = 1;
|
|
825
|
+
tierIdsToMint[1] = 1;
|
|
826
|
+
tierIdsToMint[2] = 2;
|
|
827
|
+
|
|
828
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
829
|
+
bytes[] memory data = new bytes[](1);
|
|
830
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
831
|
+
|
|
832
|
+
// Pass the hook ID.
|
|
833
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
834
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
835
|
+
|
|
836
|
+
// Generate the metadata.
|
|
837
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
838
|
+
|
|
839
|
+
// Expect a revert for the amount being too low.
|
|
840
|
+
vm.expectRevert(
|
|
841
|
+
abi.encodeWithSelector(
|
|
842
|
+
JB721TiersHookStore.JB721TiersHookStore_PriceExceedsAmount.selector, tiers[1].price, tiers[1].price - 1
|
|
843
|
+
)
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
vm.prank(mockTerminalAddress);
|
|
847
|
+
hook.afterPayRecordedWith(
|
|
848
|
+
JBAfterPayRecordedContext({
|
|
849
|
+
payer: msg.sender,
|
|
850
|
+
projectId: projectId,
|
|
851
|
+
rulesetId: 0,
|
|
852
|
+
amount: JBTokenAmount({
|
|
853
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
854
|
+
value: tiers[0].price * 2 + tiers[1].price - 1,
|
|
855
|
+
decimals: 18,
|
|
856
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
857
|
+
}),
|
|
858
|
+
forwardedAmount: JBTokenAmount({
|
|
859
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
860
|
+
value: 0,
|
|
861
|
+
decimals: 18,
|
|
862
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
863
|
+
}), // 0,
|
|
864
|
+
// forwarded to the hook.
|
|
865
|
+
weight: 10 ** 18,
|
|
866
|
+
newlyIssuedTokenCount: 0,
|
|
867
|
+
beneficiary: msg.sender,
|
|
868
|
+
hookMetadata: new bytes(0),
|
|
869
|
+
payerMetadata: hookMetadata
|
|
870
|
+
})
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
// Check: has the total supply stayed the same?
|
|
874
|
+
assertEq(totalSupplyBeforePay, hook.STORE().totalSupplyOf(address(hook)));
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function test_afterPayRecorded_revertIfAllowanceRunsOutInSpecifiedTier() public {
|
|
878
|
+
// Mock the directory call.
|
|
879
|
+
mockAndExpect(
|
|
880
|
+
address(mockJBDirectory),
|
|
881
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
882
|
+
abi.encode(true)
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
uint256 supplyLeft = tiers[0].initialSupply;
|
|
886
|
+
|
|
887
|
+
while (true) {
|
|
888
|
+
uint256 totalSupplyBeforePay = hook.STORE().totalSupplyOf(address(hook));
|
|
889
|
+
|
|
890
|
+
bool allowOverspending = true;
|
|
891
|
+
|
|
892
|
+
uint16[] memory tierSelected = new uint16[](1);
|
|
893
|
+
tierSelected[0] = 1;
|
|
894
|
+
|
|
895
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
896
|
+
bytes[] memory data = new bytes[](1);
|
|
897
|
+
data[0] = abi.encode(allowOverspending, tierSelected);
|
|
898
|
+
|
|
899
|
+
// Pass the hook ID.
|
|
900
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
901
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
902
|
+
|
|
903
|
+
// Generate the metadata.
|
|
904
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
905
|
+
|
|
906
|
+
// If there is no remaining supply, this should revert.
|
|
907
|
+
if (supplyLeft == 0) {
|
|
908
|
+
vm.expectRevert(
|
|
909
|
+
abi.encodeWithSelector(
|
|
910
|
+
JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1
|
|
911
|
+
)
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Execute the payment.
|
|
916
|
+
vm.prank(mockTerminalAddress);
|
|
917
|
+
hook.afterPayRecordedWith(
|
|
918
|
+
JBAfterPayRecordedContext({
|
|
919
|
+
payer: msg.sender,
|
|
920
|
+
projectId: projectId,
|
|
921
|
+
rulesetId: 0,
|
|
922
|
+
amount: JBTokenAmount({
|
|
923
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
924
|
+
value: tiers[0].price,
|
|
925
|
+
decimals: 18,
|
|
926
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
927
|
+
}),
|
|
928
|
+
forwardedAmount: JBTokenAmount({
|
|
929
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
930
|
+
value: 0,
|
|
931
|
+
decimals: 18,
|
|
932
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
933
|
+
}), // 0, forwarded to the hook.
|
|
934
|
+
weight: 10 ** 18,
|
|
935
|
+
newlyIssuedTokenCount: 0,
|
|
936
|
+
beneficiary: msg.sender,
|
|
937
|
+
hookMetadata: new bytes(0),
|
|
938
|
+
payerMetadata: hookMetadata
|
|
939
|
+
})
|
|
940
|
+
);
|
|
941
|
+
// If there's no supply left...
|
|
942
|
+
if (supplyLeft == 0) {
|
|
943
|
+
// Check: has the total supply stayed the same?
|
|
944
|
+
assertEq(hook.STORE().totalSupplyOf(address(hook)), totalSupplyBeforePay);
|
|
945
|
+
break;
|
|
946
|
+
} else {
|
|
947
|
+
// Otherwise, check that the total supply has increased by 1.
|
|
948
|
+
assertEq(hook.STORE().totalSupplyOf(address(hook)), totalSupplyBeforePay + 1);
|
|
949
|
+
}
|
|
950
|
+
--supplyLeft;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function test_afterPayRecorded_revertIfCallerIsNotATerminalOfProjectId(address terminal) public {
|
|
955
|
+
vm.assume(terminal != mockTerminalAddress);
|
|
956
|
+
|
|
957
|
+
// Mock the directory call.
|
|
958
|
+
mockAndExpect(
|
|
959
|
+
address(mockJBDirectory),
|
|
960
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, terminal),
|
|
961
|
+
abi.encode(false)
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
// The caller is the `_expectedCaller`. However, the terminal in the calldata is not correct.
|
|
965
|
+
vm.prank(terminal);
|
|
966
|
+
|
|
967
|
+
// Expect a revert for the caller not being a terminal of the project.
|
|
968
|
+
vm.expectRevert(abi.encodeWithSelector(JB721Hook.JB721Hook_InvalidPay.selector));
|
|
969
|
+
|
|
970
|
+
hook.afterPayRecordedWith(
|
|
971
|
+
JBAfterPayRecordedContext({
|
|
972
|
+
payer: msg.sender,
|
|
973
|
+
projectId: projectId,
|
|
974
|
+
rulesetId: 0,
|
|
975
|
+
amount: JBTokenAmount({
|
|
976
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
977
|
+
}),
|
|
978
|
+
forwardedAmount: JBTokenAmount({
|
|
979
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
980
|
+
value: 0,
|
|
981
|
+
decimals: 18,
|
|
982
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
983
|
+
}), // 0,
|
|
984
|
+
// forwarded to the hook.
|
|
985
|
+
weight: 10 ** 18,
|
|
986
|
+
newlyIssuedTokenCount: 0,
|
|
987
|
+
beneficiary: msg.sender,
|
|
988
|
+
hookMetadata: new bytes(0),
|
|
989
|
+
payerMetadata: new bytes(0)
|
|
990
|
+
})
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function test_afterPayRecorded_revertsOnCurrencyMismatchWithoutPriceFeed(address token) public {
|
|
995
|
+
vm.assume(token != JBConstants.NATIVE_TOKEN);
|
|
996
|
+
|
|
997
|
+
// Mock the directory call.
|
|
998
|
+
mockAndExpect(
|
|
999
|
+
address(mockJBDirectory),
|
|
1000
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
1001
|
+
abi.encode(true)
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
// The payment's currency (18, from positional arg order) doesn't match the hook's pricing currency.
|
|
1005
|
+
// With no price feed configured, this should revert instead of silently doing nothing.
|
|
1006
|
+
vm.expectRevert(
|
|
1007
|
+
abi.encodeWithSelector(
|
|
1008
|
+
JB721TiersHook.JB721TiersHook_CurrencyMismatch.selector,
|
|
1009
|
+
uint256(18),
|
|
1010
|
+
uint256(uint32(uint160(JBConstants.NATIVE_TOKEN)))
|
|
1011
|
+
)
|
|
1012
|
+
);
|
|
1013
|
+
vm.prank(mockTerminalAddress);
|
|
1014
|
+
hook.afterPayRecordedWith(
|
|
1015
|
+
JBAfterPayRecordedContext({
|
|
1016
|
+
payer: msg.sender,
|
|
1017
|
+
projectId: projectId,
|
|
1018
|
+
rulesetId: 0,
|
|
1019
|
+
amount: JBTokenAmount(token, 0, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))),
|
|
1020
|
+
forwardedAmount: JBTokenAmount(
|
|
1021
|
+
JBConstants.NATIVE_TOKEN, 0, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1022
|
+
), // 0,
|
|
1023
|
+
// forwarded to the hook.
|
|
1024
|
+
weight: 10 ** 18,
|
|
1025
|
+
newlyIssuedTokenCount: 0,
|
|
1026
|
+
beneficiary: msg.sender,
|
|
1027
|
+
hookMetadata: new bytes(0),
|
|
1028
|
+
payerMetadata: new bytes(0)
|
|
1029
|
+
})
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function test_afterPayRecorded_mintWithExistingCreditsWhenMoreExistingCreditsThanNewCredits() public {
|
|
1034
|
+
uint256 leftover = tiers[0].price + 1; // + 1 to avoid rounding error.
|
|
1035
|
+
uint256 amount = tiers[0].price * 2 + tiers[1].price + leftover / 2;
|
|
1036
|
+
|
|
1037
|
+
// Mock the directory call.
|
|
1038
|
+
mockAndExpect(
|
|
1039
|
+
address(mockJBDirectory),
|
|
1040
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
1041
|
+
abi.encode(true)
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
bool allowOverspending = true;
|
|
1045
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
1046
|
+
tierIdsToMint[0] = 1;
|
|
1047
|
+
tierIdsToMint[1] = 1;
|
|
1048
|
+
tierIdsToMint[2] = 2;
|
|
1049
|
+
|
|
1050
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
1051
|
+
bytes[] memory data = new bytes[](1);
|
|
1052
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
1053
|
+
|
|
1054
|
+
// Pass the hook ID.
|
|
1055
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
1056
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
1057
|
+
|
|
1058
|
+
// Generate the metadata.
|
|
1059
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
1060
|
+
|
|
1061
|
+
uint256 credits = hook.payCreditsOf(beneficiary);
|
|
1062
|
+
leftover = leftover / 2 + credits; // Leftover amount.
|
|
1063
|
+
|
|
1064
|
+
vm.expectEmit(true, true, true, true, address(hook));
|
|
1065
|
+
emit AddPayCredits(leftover - credits, leftover, beneficiary, mockTerminalAddress);
|
|
1066
|
+
|
|
1067
|
+
// The first call will mint the 3 tiers requested and accumulate half of the first price as pay credits.
|
|
1068
|
+
vm.prank(mockTerminalAddress);
|
|
1069
|
+
hook.afterPayRecordedWith(
|
|
1070
|
+
JBAfterPayRecordedContext({
|
|
1071
|
+
payer: beneficiary,
|
|
1072
|
+
projectId: projectId,
|
|
1073
|
+
rulesetId: 0,
|
|
1074
|
+
amount: JBTokenAmount({
|
|
1075
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1076
|
+
value: amount,
|
|
1077
|
+
decimals: 18,
|
|
1078
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1079
|
+
}),
|
|
1080
|
+
forwardedAmount: JBTokenAmount({
|
|
1081
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1082
|
+
value: 0,
|
|
1083
|
+
decimals: 18,
|
|
1084
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1085
|
+
}), // 0,
|
|
1086
|
+
// forwarded to the hook.
|
|
1087
|
+
weight: 10 ** 18,
|
|
1088
|
+
newlyIssuedTokenCount: 0,
|
|
1089
|
+
beneficiary: beneficiary,
|
|
1090
|
+
hookMetadata: new bytes(0),
|
|
1091
|
+
payerMetadata: hookMetadata
|
|
1092
|
+
})
|
|
1093
|
+
);
|
|
1094
|
+
|
|
1095
|
+
uint256 totalSupplyBefore = hook.STORE().totalSupplyOf(address(hook));
|
|
1096
|
+
{
|
|
1097
|
+
// We now attempt to mint an additional NFT from tier 1 by using the pay credits we collected from the last
|
|
1098
|
+
// payment.
|
|
1099
|
+
uint16[] memory moreTierIdsToMint = new uint16[](1);
|
|
1100
|
+
moreTierIdsToMint[0] = 1;
|
|
1101
|
+
|
|
1102
|
+
data[0] = abi.encode(allowOverspending, moreTierIdsToMint);
|
|
1103
|
+
|
|
1104
|
+
// Generate the metadata.
|
|
1105
|
+
hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Fetch the existing pay credits.
|
|
1109
|
+
credits = hook.payCreditsOf(beneficiary);
|
|
1110
|
+
|
|
1111
|
+
// Use existing credits to mint.
|
|
1112
|
+
leftover = tiers[0].price - 1 - credits;
|
|
1113
|
+
vm.expectEmit(true, true, true, true, address(hook));
|
|
1114
|
+
emit UsePayCredits(credits - leftover, leftover, beneficiary, mockTerminalAddress);
|
|
1115
|
+
|
|
1116
|
+
// Mint with leftover pay credits.
|
|
1117
|
+
vm.prank(mockTerminalAddress);
|
|
1118
|
+
hook.afterPayRecordedWith(
|
|
1119
|
+
JBAfterPayRecordedContext({
|
|
1120
|
+
payer: beneficiary,
|
|
1121
|
+
projectId: projectId,
|
|
1122
|
+
rulesetId: 0,
|
|
1123
|
+
amount: JBTokenAmount({
|
|
1124
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1125
|
+
value: tiers[0].price - 1,
|
|
1126
|
+
decimals: 18,
|
|
1127
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1128
|
+
}),
|
|
1129
|
+
forwardedAmount: JBTokenAmount({
|
|
1130
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1131
|
+
value: 0,
|
|
1132
|
+
decimals: 18,
|
|
1133
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1134
|
+
}), // 0,
|
|
1135
|
+
// forwarded to the hook.
|
|
1136
|
+
weight: 10 ** 18,
|
|
1137
|
+
newlyIssuedTokenCount: 0,
|
|
1138
|
+
beneficiary: beneficiary,
|
|
1139
|
+
hookMetadata: new bytes(0),
|
|
1140
|
+
payerMetadata: hookMetadata
|
|
1141
|
+
})
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
// Check: has the total supply increased by 1?
|
|
1145
|
+
assertEq(totalSupplyBefore + 1, hook.STORE().totalSupplyOf(address(hook)));
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function test_afterPayRecorded_revertIfUnexpectedLeftover() public {
|
|
1149
|
+
uint256 leftover = tiers[1].price - 1;
|
|
1150
|
+
uint256 amount = tiers[0].price + leftover;
|
|
1151
|
+
|
|
1152
|
+
// Mock the directory call.
|
|
1153
|
+
mockAndExpect(
|
|
1154
|
+
address(mockJBDirectory),
|
|
1155
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
1156
|
+
abi.encode(true)
|
|
1157
|
+
);
|
|
1158
|
+
bool allowOverspending;
|
|
1159
|
+
uint16[] memory tierIdsToMint = new uint16[](0);
|
|
1160
|
+
|
|
1161
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
1162
|
+
bytes[] memory data = new bytes[](1);
|
|
1163
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
1164
|
+
|
|
1165
|
+
// Pass the hook ID.
|
|
1166
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
1167
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
1168
|
+
|
|
1169
|
+
// Generate the metadata.
|
|
1170
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
1171
|
+
vm.prank(mockTerminalAddress);
|
|
1172
|
+
vm.expectRevert(abi.encodeWithSelector(JB721TiersHook.JB721TiersHook_Overspending.selector, amount));
|
|
1173
|
+
hook.afterPayRecordedWith(
|
|
1174
|
+
JBAfterPayRecordedContext({
|
|
1175
|
+
payer: msg.sender,
|
|
1176
|
+
projectId: projectId,
|
|
1177
|
+
rulesetId: 0,
|
|
1178
|
+
amount: JBTokenAmount({
|
|
1179
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1180
|
+
value: amount,
|
|
1181
|
+
decimals: 18,
|
|
1182
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1183
|
+
}),
|
|
1184
|
+
forwardedAmount: JBTokenAmount({
|
|
1185
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1186
|
+
value: 0,
|
|
1187
|
+
decimals: 18,
|
|
1188
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1189
|
+
}), // 0,
|
|
1190
|
+
// forwarded to the hook.
|
|
1191
|
+
weight: 10 ** 18,
|
|
1192
|
+
newlyIssuedTokenCount: 0,
|
|
1193
|
+
beneficiary: beneficiary,
|
|
1194
|
+
hookMetadata: new bytes(0),
|
|
1195
|
+
payerMetadata: hookMetadata
|
|
1196
|
+
})
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function test_afterPayRecorded_revertIfUnexpectedLeftoverAndOverspendingPrevented(bool prevent) public {
|
|
1201
|
+
uint256 leftover = tiers[1].price - 1;
|
|
1202
|
+
uint256 amount = tiers[0].price + leftover;
|
|
1203
|
+
|
|
1204
|
+
// Mock the directory call.
|
|
1205
|
+
mockAndExpect(
|
|
1206
|
+
address(mockJBDirectory),
|
|
1207
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
1208
|
+
abi.encode(true)
|
|
1209
|
+
);
|
|
1210
|
+
|
|
1211
|
+
// Get the current flags.
|
|
1212
|
+
JB721TiersHookFlags memory flags = hook.STORE().flagsOf(address(hook));
|
|
1213
|
+
|
|
1214
|
+
// Set the prevent flag to the given value.
|
|
1215
|
+
flags.preventOverspending = prevent;
|
|
1216
|
+
|
|
1217
|
+
// Mock the call to return the new flags.
|
|
1218
|
+
mockAndExpect(
|
|
1219
|
+
address(hook.STORE()),
|
|
1220
|
+
abi.encodeWithSelector(IJB721TiersHookStore.flagsOf.selector, address(hook)),
|
|
1221
|
+
abi.encode(flags)
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
bool allowOverspending = true;
|
|
1225
|
+
uint16[] memory tierIdsToMint = new uint16[](0);
|
|
1226
|
+
|
|
1227
|
+
bytes memory metadata =
|
|
1228
|
+
abi.encode(bytes32(0), bytes32(0), type(IJB721TiersHook).interfaceId, allowOverspending, tierIdsToMint);
|
|
1229
|
+
|
|
1230
|
+
// If prevent is enabled the call should revert. Otherwise, we should receive pay credits.
|
|
1231
|
+
if (prevent) {
|
|
1232
|
+
vm.expectRevert(abi.encodeWithSelector(JB721TiersHook.JB721TiersHook_Overspending.selector, amount));
|
|
1233
|
+
} else {
|
|
1234
|
+
uint256 payCredits = hook.payCreditsOf(beneficiary);
|
|
1235
|
+
uint256 stashedPayCredits = payCredits;
|
|
1236
|
+
// Calculating new pay credit balance (since leftover is non-zero).
|
|
1237
|
+
uint256 newPayCredits = tiers[0].price + leftover + stashedPayCredits;
|
|
1238
|
+
vm.expectEmit(true, true, true, true, address(hook));
|
|
1239
|
+
emit AddPayCredits(newPayCredits - payCredits, newPayCredits, beneficiary, mockTerminalAddress);
|
|
1240
|
+
}
|
|
1241
|
+
vm.prank(mockTerminalAddress);
|
|
1242
|
+
hook.afterPayRecordedWith(
|
|
1243
|
+
JBAfterPayRecordedContext({
|
|
1244
|
+
payer: msg.sender,
|
|
1245
|
+
projectId: projectId,
|
|
1246
|
+
rulesetId: 0,
|
|
1247
|
+
amount: JBTokenAmount({
|
|
1248
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1249
|
+
value: amount,
|
|
1250
|
+
decimals: 18,
|
|
1251
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1252
|
+
}),
|
|
1253
|
+
forwardedAmount: JBTokenAmount({
|
|
1254
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1255
|
+
value: 0,
|
|
1256
|
+
decimals: 18,
|
|
1257
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1258
|
+
}), // 0,
|
|
1259
|
+
// forwarded to the hook.
|
|
1260
|
+
weight: 10 ** 18,
|
|
1261
|
+
newlyIssuedTokenCount: 0,
|
|
1262
|
+
beneficiary: beneficiary,
|
|
1263
|
+
hookMetadata: new bytes(0),
|
|
1264
|
+
payerMetadata: metadata
|
|
1265
|
+
})
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// If transfers are paused, transfers which do not involve the zero address are reverted,
|
|
1270
|
+
// as long as the `transfersPausable` flag must be true.
|
|
1271
|
+
// Transfers involving the zero address (minting and burning) are not affected.
|
|
1272
|
+
function test_transferFrom_revertTransferIfPausedInRuleset() public {
|
|
1273
|
+
defaultTierConfig.transfersPausable = true;
|
|
1274
|
+
JB721TiersHook hook = _initHookDefaultTiers(10);
|
|
1275
|
+
|
|
1276
|
+
// Mock the directory call.
|
|
1277
|
+
mockAndExpect(
|
|
1278
|
+
address(mockJBDirectory),
|
|
1279
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
1280
|
+
abi.encode(true)
|
|
1281
|
+
);
|
|
1282
|
+
|
|
1283
|
+
mockAndExpect(
|
|
1284
|
+
mockJBRulesets,
|
|
1285
|
+
abi.encodeCall(IJBRulesets.currentOf, projectId),
|
|
1286
|
+
abi.encode(
|
|
1287
|
+
JBRuleset({
|
|
1288
|
+
cycleNumber: 1,
|
|
1289
|
+
id: uint48(block.timestamp),
|
|
1290
|
+
basedOnId: 0,
|
|
1291
|
+
start: uint48(block.timestamp),
|
|
1292
|
+
duration: 600,
|
|
1293
|
+
weight: 10e18,
|
|
1294
|
+
weightCutPercent: 0,
|
|
1295
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
1296
|
+
metadata: JBRulesetMetadataResolver.packRulesetMetadata(
|
|
1297
|
+
JBRulesetMetadata({
|
|
1298
|
+
reservedPercent: 5000, //50%
|
|
1299
|
+
cashOutTaxRate: 5000, //50%
|
|
1300
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1301
|
+
pausePay: false,
|
|
1302
|
+
pauseCreditTransfers: false,
|
|
1303
|
+
allowOwnerMinting: true,
|
|
1304
|
+
allowSetCustomToken: false,
|
|
1305
|
+
allowTerminalMigration: false,
|
|
1306
|
+
allowSetTerminals: false,
|
|
1307
|
+
allowSetController: false,
|
|
1308
|
+
allowAddAccountingContext: false,
|
|
1309
|
+
allowAddPriceFeed: false,
|
|
1310
|
+
ownerMustSendPayouts: false,
|
|
1311
|
+
holdFees: false,
|
|
1312
|
+
useTotalSurplusForCashOuts: false,
|
|
1313
|
+
useDataHookForPay: true,
|
|
1314
|
+
useDataHookForCashOut: true,
|
|
1315
|
+
dataHook: address(hook),
|
|
1316
|
+
metadata: 1
|
|
1317
|
+
})
|
|
1318
|
+
)
|
|
1319
|
+
})
|
|
1320
|
+
)
|
|
1321
|
+
);
|
|
1322
|
+
|
|
1323
|
+
bool allowOverspending;
|
|
1324
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
1325
|
+
tierIdsToMint[0] = 1;
|
|
1326
|
+
tierIdsToMint[1] = 1;
|
|
1327
|
+
tierIdsToMint[2] = 2;
|
|
1328
|
+
|
|
1329
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
1330
|
+
bytes[] memory data = new bytes[](1);
|
|
1331
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
1332
|
+
|
|
1333
|
+
// Pass the hook ID.
|
|
1334
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
1335
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
1336
|
+
|
|
1337
|
+
// Generate the metadata.
|
|
1338
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
1339
|
+
|
|
1340
|
+
vm.prank(mockTerminalAddress);
|
|
1341
|
+
hook.afterPayRecordedWith(
|
|
1342
|
+
JBAfterPayRecordedContext({
|
|
1343
|
+
payer: msg.sender,
|
|
1344
|
+
projectId: projectId,
|
|
1345
|
+
rulesetId: 0,
|
|
1346
|
+
amount: JBTokenAmount({
|
|
1347
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1348
|
+
value: tiers[0].price * 2 + tiers[1].price,
|
|
1349
|
+
decimals: 18,
|
|
1350
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1351
|
+
}),
|
|
1352
|
+
forwardedAmount: JBTokenAmount({
|
|
1353
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1354
|
+
value: 0,
|
|
1355
|
+
decimals: 18,
|
|
1356
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1357
|
+
}), // 0,
|
|
1358
|
+
// forwarded to the hook.
|
|
1359
|
+
weight: 10 ** 18,
|
|
1360
|
+
newlyIssuedTokenCount: 0,
|
|
1361
|
+
beneficiary: msg.sender,
|
|
1362
|
+
hookMetadata: new bytes(0),
|
|
1363
|
+
payerMetadata: hookMetadata
|
|
1364
|
+
})
|
|
1365
|
+
);
|
|
1366
|
+
|
|
1367
|
+
uint256 tokenId = _generateTokenId(1, 1);
|
|
1368
|
+
|
|
1369
|
+
// Expect a revert on account of transfers being paused.
|
|
1370
|
+
vm.expectRevert(JB721TiersHook.JB721TiersHook_TierTransfersPaused.selector);
|
|
1371
|
+
|
|
1372
|
+
vm.prank(msg.sender);
|
|
1373
|
+
IERC721(hook).transferFrom(msg.sender, beneficiary, tokenId);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// If the ruleset metadata has `pauseTransfers` enabled,
|
|
1377
|
+
// BUT the tier being transferred has `transfersPausable` disabled,
|
|
1378
|
+
// transfer are not paused (this bypasses the call to `JBRulesets`).
|
|
1379
|
+
function test_transferFrom_pauseFlagOverridesRuleset() public {
|
|
1380
|
+
// Mock the directory call.
|
|
1381
|
+
mockAndExpect(
|
|
1382
|
+
address(mockJBDirectory),
|
|
1383
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
1384
|
+
abi.encode(true)
|
|
1385
|
+
);
|
|
1386
|
+
|
|
1387
|
+
JB721TiersHook hook = _initHookDefaultTiers(10);
|
|
1388
|
+
|
|
1389
|
+
bool allowOverspending;
|
|
1390
|
+
uint16[] memory tierIdsToMint = new uint16[](3);
|
|
1391
|
+
tierIdsToMint[0] = 1;
|
|
1392
|
+
tierIdsToMint[1] = 1;
|
|
1393
|
+
tierIdsToMint[2] = 2;
|
|
1394
|
+
|
|
1395
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
1396
|
+
bytes[] memory data = new bytes[](1);
|
|
1397
|
+
data[0] = abi.encode(allowOverspending, tierIdsToMint);
|
|
1398
|
+
|
|
1399
|
+
// Pass the hook ID.
|
|
1400
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
1401
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
1402
|
+
|
|
1403
|
+
// Generate the metadata.
|
|
1404
|
+
bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
1405
|
+
|
|
1406
|
+
vm.prank(mockTerminalAddress);
|
|
1407
|
+
hook.afterPayRecordedWith(
|
|
1408
|
+
JBAfterPayRecordedContext({
|
|
1409
|
+
payer: msg.sender,
|
|
1410
|
+
projectId: projectId,
|
|
1411
|
+
rulesetId: 0,
|
|
1412
|
+
amount: JBTokenAmount({
|
|
1413
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1414
|
+
value: tiers[0].price * 2 + tiers[1].price,
|
|
1415
|
+
decimals: 18,
|
|
1416
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1417
|
+
}),
|
|
1418
|
+
forwardedAmount: JBTokenAmount({
|
|
1419
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1420
|
+
value: 0,
|
|
1421
|
+
decimals: 18,
|
|
1422
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1423
|
+
}), // 0,
|
|
1424
|
+
// forwarded to the hook.
|
|
1425
|
+
weight: 10 ** 18,
|
|
1426
|
+
newlyIssuedTokenCount: 0,
|
|
1427
|
+
beneficiary: msg.sender,
|
|
1428
|
+
hookMetadata: new bytes(0),
|
|
1429
|
+
payerMetadata: hookMetadata
|
|
1430
|
+
})
|
|
1431
|
+
);
|
|
1432
|
+
|
|
1433
|
+
uint256 tokenId = _generateTokenId(1, 1);
|
|
1434
|
+
vm.prank(msg.sender);
|
|
1435
|
+
IERC721(hook).transferFrom(msg.sender, beneficiary, tokenId);
|
|
1436
|
+
// Check: was the NFT transferred to the beneficiary?
|
|
1437
|
+
assertEq(IERC721(hook).ownerOf(tokenId), beneficiary);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Cash out an NFT, even if transfers are paused in the ruleset metadata. This should bypass the call to
|
|
1441
|
+
// `JBRulesets`.
|
|
1442
|
+
function test_afterCashOutRecordedWith_cashOutEvenIfTransfersPausedInRuleset() public {
|
|
1443
|
+
address holder = address(bytes20(keccak256("holder")));
|
|
1444
|
+
|
|
1445
|
+
JB721TiersHook hook = _initHookDefaultTiers(10);
|
|
1446
|
+
|
|
1447
|
+
// Mock the directory call.
|
|
1448
|
+
mockAndExpect(
|
|
1449
|
+
address(mockJBDirectory),
|
|
1450
|
+
abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
|
|
1451
|
+
abi.encode(true)
|
|
1452
|
+
);
|
|
1453
|
+
|
|
1454
|
+
// Build the metadata which will be used to mint.
|
|
1455
|
+
bytes memory hookMetadata;
|
|
1456
|
+
bytes[] memory data = new bytes[](1);
|
|
1457
|
+
bytes4[] memory ids = new bytes4[](1);
|
|
1458
|
+
|
|
1459
|
+
{
|
|
1460
|
+
// Craft the metadata: mint the specified tier.
|
|
1461
|
+
uint16[] memory rawMetadata = new uint16[](1);
|
|
1462
|
+
rawMetadata[0] = uint16(1); // 1 indexed
|
|
1463
|
+
|
|
1464
|
+
// Build the metadata using the tiers to mint and the overspending flag.
|
|
1465
|
+
data[0] = abi.encode(true, rawMetadata);
|
|
1466
|
+
|
|
1467
|
+
// Pass the hook ID.
|
|
1468
|
+
ids[0] = metadataHelper.getId("pay", address(hook));
|
|
1469
|
+
|
|
1470
|
+
// Generate the metadata.
|
|
1471
|
+
hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// Mint the NFTs. Otherwise, the voting balance is not incremented which leads to an underflow upon cash outs.
|
|
1475
|
+
vm.prank(mockTerminalAddress);
|
|
1476
|
+
hook.afterPayRecordedWith(
|
|
1477
|
+
JBAfterPayRecordedContext({
|
|
1478
|
+
payer: holder,
|
|
1479
|
+
projectId: projectId,
|
|
1480
|
+
rulesetId: 0,
|
|
1481
|
+
amount: JBTokenAmount({
|
|
1482
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1483
|
+
value: tiers[0].price,
|
|
1484
|
+
decimals: 18,
|
|
1485
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1486
|
+
}),
|
|
1487
|
+
forwardedAmount: JBTokenAmount({
|
|
1488
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1489
|
+
value: 0,
|
|
1490
|
+
decimals: 18,
|
|
1491
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1492
|
+
}), // 0,
|
|
1493
|
+
// forwarded to the hook.
|
|
1494
|
+
weight: 10 ** 18,
|
|
1495
|
+
newlyIssuedTokenCount: 0,
|
|
1496
|
+
beneficiary: holder,
|
|
1497
|
+
hookMetadata: new bytes(0),
|
|
1498
|
+
payerMetadata: hookMetadata
|
|
1499
|
+
})
|
|
1500
|
+
);
|
|
1501
|
+
|
|
1502
|
+
uint256[] memory tokenToCashOut = new uint256[](1);
|
|
1503
|
+
tokenToCashOut[0] = _generateTokenId(1, 1);
|
|
1504
|
+
|
|
1505
|
+
// Build the metadata with the tiers to cash out.
|
|
1506
|
+
data[0] = abi.encode(tokenToCashOut);
|
|
1507
|
+
|
|
1508
|
+
// Pass the hook ID.
|
|
1509
|
+
ids[0] = metadataHelper.getId("pay", address(hookOrigin));
|
|
1510
|
+
|
|
1511
|
+
// Generate the metadata.
|
|
1512
|
+
hookMetadata = metadataHelper.createMetadata(ids, data);
|
|
1513
|
+
|
|
1514
|
+
vm.prank(mockTerminalAddress);
|
|
1515
|
+
hook.afterCashOutRecordedWith(
|
|
1516
|
+
JBAfterCashOutRecordedContext({
|
|
1517
|
+
holder: holder,
|
|
1518
|
+
projectId: projectId,
|
|
1519
|
+
rulesetId: 1,
|
|
1520
|
+
cashOutCount: 0,
|
|
1521
|
+
reclaimedAmount: JBTokenAmount({
|
|
1522
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1523
|
+
}),
|
|
1524
|
+
forwardedAmount: JBTokenAmount({
|
|
1525
|
+
token: address(0), value: 0, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1526
|
+
}), // 0, forwarded to the hook.
|
|
1527
|
+
cashOutTaxRate: 5000,
|
|
1528
|
+
beneficiary: payable(holder),
|
|
1529
|
+
hookMetadata: bytes(""),
|
|
1530
|
+
cashOutMetadata: hookMetadata
|
|
1531
|
+
})
|
|
1532
|
+
);
|
|
1533
|
+
|
|
1534
|
+
// Check: has the holder's balance returned to 0?
|
|
1535
|
+
assertEq(hook.balanceOf(holder), 0);
|
|
1536
|
+
}
|
|
1537
|
+
}
|