@bananapus/721-hook-v6 0.0.45 → 0.0.46
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/CHANGELOG.md +2 -3
- package/README.md +2 -2
- package/package.json +2 -2
- package/references/operations.md +2 -2
- package/references/runtime.md +1 -1
- package/script/helpers/Hook721DeploymentLib.sol +21 -5
- package/src/JB721Checkpoints.sol +4 -6
- package/src/JB721CheckpointsDeployer.sol +9 -1
- package/src/JB721TiersHook.sol +10 -24
- package/src/JB721TiersHookDeployer.sol +0 -1
- package/src/JB721TiersHookProjectDeployer.sol +3 -4
- package/src/JB721TiersHookStore.sol +33 -27
- package/src/abstract/ERC721.sol +24 -22
- package/src/abstract/JB721Hook.sol +12 -8
- package/src/interfaces/IJB721CheckpointsDeployer.sol +0 -3
- package/src/interfaces/IJB721TiersHook.sol +0 -8
- package/src/libraries/JB721Constants.sol +1 -0
- package/src/libraries/JB721TiersHookLib.sol +19 -32
- package/src/libraries/JBBitmap.sol +1 -1
- package/src/structs/JBPayDataHookRulesetMetadata.sol +3 -3
- package/test/utils/UnitTestSetup.sol +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -20,7 +20,7 @@ This file describes the verified change from `nana-721-hook-v5` to the current `
|
|
|
20
20
|
- The repo now carries a dedicated helper library to keep the hook surface manageable and to support the larger v6 feature set.
|
|
21
21
|
- The repo was upgraded from the v5 Solidity baseline to `0.8.28`.
|
|
22
22
|
|
|
23
|
-
## Local
|
|
23
|
+
## Local review remediations
|
|
24
24
|
|
|
25
25
|
- `JB721TiersHookProjectDeployer.launchRulesetsFor` now checks `LAUNCH_RULESETS` instead of `QUEUE_RULESETS`. The previous check was semantically wrong — launching active rulesets should require the launch permission, not the queue permission.
|
|
26
26
|
|
|
@@ -43,7 +43,7 @@ This file describes the verified change from `nana-721-hook-v5` to the current `
|
|
|
43
43
|
|
|
44
44
|
## Indexer impact
|
|
45
45
|
|
|
46
|
-
- New events: `
|
|
46
|
+
- New events: `SetName`, `SetSymbol`, `SplitPayoutReverted`.
|
|
47
47
|
- Tier config decoding changed because `JB721TierConfig` is no longer v5-compatible.
|
|
48
48
|
- Collection metadata can now change after deployment, so one-time indexing of `name` and `symbol` is no longer sufficient.
|
|
49
49
|
|
|
@@ -62,7 +62,6 @@ This file describes the verified change from `nana-721-hook-v5` to the current `
|
|
|
62
62
|
- `pricingContext()`
|
|
63
63
|
- `setMetadata(...)`
|
|
64
64
|
- Added events
|
|
65
|
-
- `AddToBalanceReverted` (declared in interface but no longer emitted; the library now reverts with `JB721TiersHookLib_SplitFallbackFailed` instead)
|
|
66
65
|
- `SetName`
|
|
67
66
|
- `SetSymbol`
|
|
68
67
|
- `SplitPayoutReverted`
|
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ That split is why UI bugs, economic bugs, and deployment bugs often land in diff
|
|
|
73
73
|
1. `test/E2E/Pay_Mint_Redeem_E2E.t.sol`
|
|
74
74
|
2. `test/invariants/TierLifecycleInvariant.t.sol`
|
|
75
75
|
3. `test/invariants/TieredHookStoreInvariant.t.sol`
|
|
76
|
-
4. `test/
|
|
76
|
+
4. `test/regression/RegressionSplitCreditsMismatch.t.sol`
|
|
77
77
|
5. `test/regression/ProjectDeployerRulesets.t.sol`
|
|
78
78
|
|
|
79
79
|
## Install
|
|
@@ -112,7 +112,7 @@ src/
|
|
|
112
112
|
libraries/
|
|
113
113
|
structs/
|
|
114
114
|
test/
|
|
115
|
-
unit, E2E, fork, invariant,
|
|
115
|
+
unit, E2E, fork, invariant, review, and regression coverage
|
|
116
116
|
script/
|
|
117
117
|
Deploy.s.sol
|
|
118
118
|
helpers/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/721-hook-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@bananapus/address-registry-v6": "0.0.25",
|
|
32
|
-
"@bananapus/core-v6": "
|
|
32
|
+
"@bananapus/core-v6": "0.0.44",
|
|
33
33
|
"@bananapus/ownable-v6": "^0.0.24",
|
|
34
34
|
"@bananapus/permission-ids-v6": "0.0.22",
|
|
35
35
|
"@openzeppelin/contracts": "5.6.1",
|
package/references/operations.md
CHANGED
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
## Useful Proof Points
|
|
26
26
|
|
|
27
27
|
- [`test/Fork.t.sol`](../test/Fork.t.sol) for live-integration assumptions.
|
|
28
|
-
- [`test/
|
|
28
|
+
- [`test/TestRegressionGaps.sol`](../test/TestRegressionGaps.sol) for known edge cases the repo authors considered worth pinning down.
|
|
29
29
|
- [`test/TestCheckpoints.t.sol`](../test/TestCheckpoints.t.sol) when you need a narrow function-level proof before editing a broad runtime path.
|
|
30
30
|
- [`test/invariants/TierLifecycleInvariant.t.sol`](../test/invariants/TierLifecycleInvariant.t.sol) and [`test/invariants/TieredHookStoreInvariant.t.sol`](../test/invariants/TieredHookStoreInvariant.t.sol) when a local patch may have broken store-level relationships.
|
|
31
|
-
- [`test/
|
|
31
|
+
- [`test/regression/RetroactiveReserveBeneficiaryDilution.t.sol`](../test/regression/RetroactiveReserveBeneficiaryDilution.t.sol) when reserve-beneficiary or pending-reserve behavior changes.
|
|
32
32
|
- [`script/Deploy.s.sol`](../script/Deploy.s.sol) when a deployment or launch question is really about config assembly rather than contract behavior.
|
package/references/runtime.md
CHANGED
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
- [`test/TestVotingUnitsLifecycle.t.sol`](../test/TestVotingUnitsLifecycle.t.sol) for voting-unit lifecycle behavior.
|
|
31
31
|
- [`test/TestCheckpoints.t.sol`](../test/TestCheckpoints.t.sol) for checkpoint/module behavior.
|
|
32
32
|
- [`test/invariants/TierLifecycleInvariant.t.sol`](../test/invariants/TierLifecycleInvariant.t.sol) and [`test/invariants/TieredHookStoreInvariant.t.sol`](../test/invariants/TieredHookStoreInvariant.t.sol) for store-level lifecycle invariants.
|
|
33
|
-
- [`test/TestSafeTransferReentrancy.t.sol`](../test/TestSafeTransferReentrancy.t.sol), [`test/721HookAttacks.t.sol`](../test/721HookAttacks.t.sol), [`test/
|
|
33
|
+
- [`test/TestSafeTransferReentrancy.t.sol`](../test/TestSafeTransferReentrancy.t.sol), [`test/721HookAttacks.t.sol`](../test/721HookAttacks.t.sol), [`test/regression/RetroactiveReserveBeneficiaryDilution.t.sol`](../test/regression/RetroactiveReserveBeneficiaryDilution.t.sol), and [`test/TestRegressionGaps.sol`](../test/TestRegressionGaps.sol) for reentrancy and attack-surface checks.
|
|
@@ -35,7 +35,7 @@ library Hook721DeploymentLib {
|
|
|
35
35
|
|
|
36
36
|
for (uint256 _i; _i < networks.length; _i++) {
|
|
37
37
|
if (networks[_i].chainId == chainId) {
|
|
38
|
-
return getDeployment(path, networks[_i].name);
|
|
38
|
+
return getDeployment({path: path, network_name: networks[_i].name});
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -52,15 +52,31 @@ library Hook721DeploymentLib {
|
|
|
52
52
|
returns (Hook721Deployment memory deployment)
|
|
53
53
|
{
|
|
54
54
|
deployment.hook_deployer = IJB721TiersHookDeployer(
|
|
55
|
-
_getDeploymentAddress(
|
|
55
|
+
_getDeploymentAddress({
|
|
56
|
+
path: path,
|
|
57
|
+
project_name: "nana-721-hook-v6",
|
|
58
|
+
network_name: network_name,
|
|
59
|
+
contractName: "JB721TiersHookDeployer"
|
|
60
|
+
})
|
|
56
61
|
);
|
|
57
62
|
|
|
58
63
|
deployment.project_deployer = IJB721TiersHookProjectDeployer(
|
|
59
|
-
_getDeploymentAddress(
|
|
64
|
+
_getDeploymentAddress({
|
|
65
|
+
path: path,
|
|
66
|
+
project_name: "nana-721-hook-v6",
|
|
67
|
+
network_name: network_name,
|
|
68
|
+
contractName: "JB721TiersHookProjectDeployer"
|
|
69
|
+
})
|
|
60
70
|
);
|
|
61
71
|
|
|
62
|
-
deployment.store =
|
|
63
|
-
|
|
72
|
+
deployment.store = IJB721TiersHookStore(
|
|
73
|
+
_getDeploymentAddress({
|
|
74
|
+
path: path,
|
|
75
|
+
project_name: "nana-721-hook-v6",
|
|
76
|
+
network_name: network_name,
|
|
77
|
+
contractName: "JB721TiersHookStore"
|
|
78
|
+
})
|
|
79
|
+
);
|
|
64
80
|
}
|
|
65
81
|
|
|
66
82
|
/// @notice Get the address of a contract that was deployed by the Deploy script.
|
package/src/JB721Checkpoints.sol
CHANGED
|
@@ -23,8 +23,8 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
23
23
|
// --------------------------- custom errors ------------------------- //
|
|
24
24
|
//*********************************************************************//
|
|
25
25
|
|
|
26
|
-
error JB721Checkpoints_AlreadyInitialized();
|
|
27
|
-
error JB721Checkpoints_Unauthorized();
|
|
26
|
+
error JB721Checkpoints_AlreadyInitialized(address hook);
|
|
27
|
+
error JB721Checkpoints_Unauthorized(address caller, address hook);
|
|
28
28
|
|
|
29
29
|
//*********************************************************************//
|
|
30
30
|
// --------------- public immutable stored properties ---------------- //
|
|
@@ -68,9 +68,8 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
68
68
|
/// @dev Can only be called once. Called by the deployer after cloning.
|
|
69
69
|
/// @param hook The hook this module serves.
|
|
70
70
|
function initialize(address hook) external override {
|
|
71
|
-
if (HOOK != address(0)) revert JB721Checkpoints_AlreadyInitialized();
|
|
71
|
+
if (HOOK != address(0)) revert JB721Checkpoints_AlreadyInitialized({hook: HOOK});
|
|
72
72
|
// `hook` cannot be zero when called through the deployer because `msg.sender` must equal `hook`.
|
|
73
|
-
// slither-disable-next-line missing-zero-check
|
|
74
73
|
HOOK = hook;
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -80,11 +79,10 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
80
79
|
/// @param to The new owner (address(0) on burn).
|
|
81
80
|
/// @param tokenId The token ID to transfer.
|
|
82
81
|
function onTransfer(address from, address to, uint256 tokenId) external override {
|
|
83
|
-
if (msg.sender != HOOK) revert JB721Checkpoints_Unauthorized();
|
|
82
|
+
if (msg.sender != HOOK) revert JB721Checkpoints_Unauthorized({caller: msg.sender, hook: HOOK});
|
|
84
83
|
|
|
85
84
|
if (from != address(0)) {
|
|
86
85
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
87
|
-
// slither-disable-next-line unused-return
|
|
88
86
|
_ownerCheckpointsOf[tokenId].push({key: uint96(block.number), value: uint160(to)});
|
|
89
87
|
}
|
|
90
88
|
|
|
@@ -13,6 +13,12 @@ import {IJB721TiersHookStore} from "./interfaces/IJB721TiersHookStore.sol";
|
|
|
13
13
|
/// @dev The implementation is deployed once in the constructor. Each `deploy()` call clones it (~45k gas) and
|
|
14
14
|
/// initializes the clone with the hook and store references.
|
|
15
15
|
contract JB721CheckpointsDeployer is IJB721CheckpointsDeployer {
|
|
16
|
+
//*********************************************************************//
|
|
17
|
+
// --------------------------- custom errors ------------------------- //
|
|
18
|
+
//*********************************************************************//
|
|
19
|
+
|
|
20
|
+
error JB721CheckpointsDeployer_Unauthorized(address caller, address hook);
|
|
21
|
+
|
|
16
22
|
//*********************************************************************//
|
|
17
23
|
// --------------- public immutable stored properties ---------------- //
|
|
18
24
|
//*********************************************************************//
|
|
@@ -42,7 +48,9 @@ contract JB721CheckpointsDeployer is IJB721CheckpointsDeployer {
|
|
|
42
48
|
/// @param hook The hook address the module will serve.
|
|
43
49
|
/// @return module The newly deployed and initialized checkpoint module.
|
|
44
50
|
function deploy(address hook) external override returns (IJB721Checkpoints module) {
|
|
45
|
-
if (msg.sender != hook)
|
|
51
|
+
if (msg.sender != hook) {
|
|
52
|
+
revert JB721CheckpointsDeployer_Unauthorized({caller: msg.sender, hook: hook});
|
|
53
|
+
}
|
|
46
54
|
|
|
47
55
|
module = IJB721Checkpoints(
|
|
48
56
|
LibClone.cloneDeterministic({implementation: IMPLEMENTATION, salt: bytes32(uint256(uint160(hook)))})
|
package/src/JB721TiersHook.sol
CHANGED
|
@@ -42,12 +42,10 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
42
42
|
//*********************************************************************//
|
|
43
43
|
|
|
44
44
|
error JB721TiersHook_AlreadyInitialized(uint256 projectId);
|
|
45
|
-
error JB721TiersHook_CantBuyWithCredits();
|
|
46
45
|
error JB721TiersHook_InvalidPricingDecimals(uint256 decimals);
|
|
47
|
-
error JB721TiersHook_MintReserveNftsPaused();
|
|
48
|
-
error JB721TiersHook_NoProjectId();
|
|
49
|
-
error
|
|
50
|
-
error JB721TiersHook_TierTransfersPaused();
|
|
46
|
+
error JB721TiersHook_MintReserveNftsPaused(uint256 projectId, uint256 tierId);
|
|
47
|
+
error JB721TiersHook_NoProjectId(uint256 projectId);
|
|
48
|
+
error JB721TiersHook_TierTransfersPaused(uint256 projectId, uint256 tokenId, address from, address to);
|
|
51
49
|
|
|
52
50
|
//*********************************************************************//
|
|
53
51
|
// --------------- public immutable stored properties ---------------- //
|
|
@@ -264,7 +262,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
264
262
|
_initialized = true;
|
|
265
263
|
|
|
266
264
|
// Make sure a projectId is provided.
|
|
267
|
-
if (projectId == 0) revert JB721TiersHook_NoProjectId();
|
|
265
|
+
if (projectId == 0) revert JB721TiersHook_NoProjectId({projectId: projectId});
|
|
268
266
|
|
|
269
267
|
// Initialize the superclass.
|
|
270
268
|
JB721Hook._initialize({projectId: projectId, name: name, symbol: symbol});
|
|
@@ -279,7 +277,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
279
277
|
// pack the pricing decimals in bits 32-39 (8 bits).
|
|
280
278
|
packed |= uint256(tiersConfig.decimals) << 32;
|
|
281
279
|
// Store the packed value.
|
|
282
|
-
// slither-disable-next-line events-maths
|
|
283
280
|
_packedPricingContext = packed;
|
|
284
281
|
|
|
285
282
|
// Store the base URI if provided.
|
|
@@ -385,7 +382,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
385
382
|
_requirePermissionFrom({account: owner(), projectId: PROJECT_ID, permissionId: JBPermissionIds.MINT_721});
|
|
386
383
|
|
|
387
384
|
// Record the mint. The token IDs returned correspond to the tiers passed in.
|
|
388
|
-
// slither-disable-next-line reentrancy-events,unused-return
|
|
389
385
|
(tokenIds,,) = STORE.recordMint({
|
|
390
386
|
amount: type(uint256).max, // force the mint.
|
|
391
387
|
tierIds: tierIds,
|
|
@@ -503,7 +499,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
503
499
|
// `address(this)` is the sentinel value meaning "leave unchanged" (since `address(0)` clears the resolver).
|
|
504
500
|
if (tokenUriResolver != IJB721TokenUriResolver(address(this))) {
|
|
505
501
|
// Store the new URI resolver.
|
|
506
|
-
// slither-disable-next-line reentrancy-events
|
|
507
502
|
_recordSetTokenUriResolver(tokenUriResolver);
|
|
508
503
|
}
|
|
509
504
|
if (encodedIPFSUriTierId != 0 && encodedIPFSUri != bytes32(0)) {
|
|
@@ -529,15 +524,13 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
529
524
|
// Pending reserve mints must not be paused.
|
|
530
525
|
if (JB721TiersRulesetMetadataResolver.mintPendingReservesPaused((JBRulesetMetadataResolver.metadata(ruleset))))
|
|
531
526
|
{
|
|
532
|
-
revert JB721TiersHook_MintReserveNftsPaused();
|
|
527
|
+
revert JB721TiersHook_MintReserveNftsPaused({projectId: PROJECT_ID, tierId: tierId});
|
|
533
528
|
}
|
|
534
529
|
|
|
535
530
|
// Record the reserved mint for the tier.
|
|
536
|
-
// slither-disable-next-line reentrancy-events,calls-loop
|
|
537
531
|
uint256[] memory tokenIds = STORE.recordMintReservesFor({tierId: tierId, count: count});
|
|
538
532
|
|
|
539
533
|
// Keep a reference to the beneficiary.
|
|
540
|
-
// slither-disable-next-line calls-loop
|
|
541
534
|
address reserveBeneficiary = STORE.reserveBeneficiaryOf({hook: address(this), tierId: tierId});
|
|
542
535
|
|
|
543
536
|
// Cache _msgSender() before the loop to avoid repeated calls.
|
|
@@ -550,7 +543,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
550
543
|
emit MintReservedNft({tokenId: tokenId, tierId: tierId, beneficiary: reserveBeneficiary, caller: caller});
|
|
551
544
|
|
|
552
545
|
// Mint the NFT.
|
|
553
|
-
// slither-disable-next-line reentrency-events
|
|
554
546
|
_mint({to: reserveBeneficiary, tokenId: tokenId});
|
|
555
547
|
|
|
556
548
|
unchecked {
|
|
@@ -572,7 +564,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
572
564
|
/// @param projectId The ID of the project to check.
|
|
573
565
|
/// @return The project's current ruleset.
|
|
574
566
|
function _currentRulesetOf(uint256 projectId) internal view returns (JBRuleset memory) {
|
|
575
|
-
// slither-disable-next-line calls-loop
|
|
576
567
|
return RULESETS.currentOf(projectId);
|
|
577
568
|
}
|
|
578
569
|
|
|
@@ -625,7 +616,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
625
616
|
caller: caller
|
|
626
617
|
});
|
|
627
618
|
|
|
628
|
-
// slither-disable-next-line reentrancy-events
|
|
629
619
|
_mint({to: beneficiary, tokenId: tokenIds[i]});
|
|
630
620
|
|
|
631
621
|
unchecked {
|
|
@@ -665,7 +655,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
665
655
|
if (tokenIds.length != 0) {
|
|
666
656
|
// totalAmountPaid is the full amount available before recordMint deducted tier prices.
|
|
667
657
|
uint256 totalAmountPaid = (payer == beneficiary) ? value + payCredits : value;
|
|
668
|
-
// slither-disable-next-line reentrancy-events
|
|
669
658
|
_mintTokens({
|
|
670
659
|
tokenIds: tokenIds, tierIds: tierIdsToMint, beneficiary: beneficiary, totalAmountPaid: totalAmountPaid
|
|
671
660
|
});
|
|
@@ -689,7 +678,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
689
678
|
});
|
|
690
679
|
}
|
|
691
680
|
|
|
692
|
-
// slither-disable-next-line reentrancy-no-eth
|
|
693
681
|
payCreditsOf[beneficiary] = newPayCredits;
|
|
694
682
|
}
|
|
695
683
|
}
|
|
@@ -757,7 +745,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
757
745
|
/// @param tierId The ID of the tier to set the discount percent for.
|
|
758
746
|
/// @param discountPercent The discount percent to set (0 = no discount, up to DISCOUNT_DENOMINATOR = free).
|
|
759
747
|
function _setDiscountPercentOf(uint256 tierId, uint256 discountPercent) internal {
|
|
760
|
-
// slither-disable-next-line calls-loop
|
|
761
748
|
JB721TiersHookLib.setDiscountPercentOf({
|
|
762
749
|
store: STORE, tierId: tierId, discountPercent: discountPercent, caller: _msgSender()
|
|
763
750
|
});
|
|
@@ -768,7 +755,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
768
755
|
/// @param tokenId The token ID of the NFT to transfer.
|
|
769
756
|
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address from) {
|
|
770
757
|
// Get only the tier ID and transfersPausable flag (lightweight — avoids full struct construction).
|
|
771
|
-
// slither-disable-next-line calls-loop
|
|
772
758
|
(uint256 tierId, bool transfersPausable) =
|
|
773
759
|
STORE.tierTransferInfoOfTokenId({hook: address(this), tokenId: tokenId});
|
|
774
760
|
|
|
@@ -788,26 +774,26 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
788
774
|
&& JB721TiersRulesetMetadataResolver.transfersPaused(
|
|
789
775
|
(JBRulesetMetadataResolver.metadata(ruleset))
|
|
790
776
|
)
|
|
791
|
-
)
|
|
777
|
+
) {
|
|
778
|
+
revert JB721TiersHook_TierTransfersPaused({
|
|
779
|
+
projectId: PROJECT_ID, tokenId: tokenId, from: from, to: to
|
|
780
|
+
});
|
|
781
|
+
}
|
|
792
782
|
}
|
|
793
783
|
|
|
794
784
|
// If the token isn't already associated with a first owner, store the sender as the first owner.
|
|
795
|
-
// slither-disable-next-line calls-loop
|
|
796
785
|
if (_firstOwnerOf[tokenId] == address(0)) _firstOwnerOf[tokenId] = from;
|
|
797
786
|
}
|
|
798
787
|
|
|
799
788
|
// Record the transfer.
|
|
800
|
-
// slither-disable-next-line reentrency-events,calls-loop
|
|
801
789
|
STORE.recordTransferForTier({tierId: tierId, from: from, to: to});
|
|
802
790
|
|
|
803
791
|
// Deploy the checkpoint module lazily on the first transfer.
|
|
804
792
|
if (address(CHECKPOINTS) == address(0)) {
|
|
805
|
-
// slither-disable-next-line calls-loop,reentrancy-events
|
|
806
793
|
CHECKPOINTS = CHECKPOINTS_DEPLOYER.deploy(address(this));
|
|
807
794
|
}
|
|
808
795
|
|
|
809
796
|
// Notify the checkpoint module to update checkpointed voting power.
|
|
810
|
-
// slither-disable-next-line calls-loop,reentrancy-events
|
|
811
797
|
CHECKPOINTS.onTransfer({from: from, to: to, tokenId: tokenId});
|
|
812
798
|
}
|
|
813
799
|
}
|
|
@@ -103,7 +103,6 @@ contract JB721TiersHookDeployer is ERC2771Context, IJB721TiersHookDeployer {
|
|
|
103
103
|
JBOwnable(address(newHook)).transferOwnership(_msgSender());
|
|
104
104
|
|
|
105
105
|
// Increment the nonce.
|
|
106
|
-
// slither-disable-next-line reentrancy-benign
|
|
107
106
|
++_nonce;
|
|
108
107
|
|
|
109
108
|
// Add the hook to the address registry. This contract's nonce starts at 1.
|
|
@@ -276,7 +276,7 @@ contract JB721TiersHookProjectDeployer is
|
|
|
276
276
|
allowAddPriceFeed: payDataRulesetConfig.metadata.allowAddPriceFeed,
|
|
277
277
|
ownerMustSendPayouts: payDataRulesetConfig.metadata.ownerMustSendPayouts,
|
|
278
278
|
holdFees: payDataRulesetConfig.metadata.holdFees,
|
|
279
|
-
|
|
279
|
+
scopeCashOutsToLocalBalances: payDataRulesetConfig.metadata.scopeCashOutsToLocalBalances,
|
|
280
280
|
useDataHookForPay: true,
|
|
281
281
|
useDataHookForCashOut: payDataRulesetConfig.metadata.useDataHookForCashOut,
|
|
282
282
|
dataHook: address(dataHook),
|
|
@@ -292,7 +292,6 @@ contract JB721TiersHookProjectDeployer is
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// Launch the rulesets for the reserved project.
|
|
295
|
-
// slither-disable-next-line unused-return
|
|
296
295
|
controller.launchRulesetsFor({
|
|
297
296
|
projectId: projectId,
|
|
298
297
|
projectUri: launchProjectConfig.projectUri,
|
|
@@ -351,7 +350,7 @@ contract JB721TiersHookProjectDeployer is
|
|
|
351
350
|
allowAddPriceFeed: payDataRulesetConfig.metadata.allowAddPriceFeed,
|
|
352
351
|
ownerMustSendPayouts: payDataRulesetConfig.metadata.ownerMustSendPayouts,
|
|
353
352
|
holdFees: payDataRulesetConfig.metadata.holdFees,
|
|
354
|
-
|
|
353
|
+
scopeCashOutsToLocalBalances: payDataRulesetConfig.metadata.scopeCashOutsToLocalBalances,
|
|
355
354
|
useDataHookForPay: true,
|
|
356
355
|
useDataHookForCashOut: payDataRulesetConfig.metadata.useDataHookForCashOut,
|
|
357
356
|
dataHook: address(dataHook),
|
|
@@ -426,7 +425,7 @@ contract JB721TiersHookProjectDeployer is
|
|
|
426
425
|
allowAddPriceFeed: payDataRulesetConfig.metadata.allowAddPriceFeed,
|
|
427
426
|
ownerMustSendPayouts: payDataRulesetConfig.metadata.ownerMustSendPayouts,
|
|
428
427
|
holdFees: payDataRulesetConfig.metadata.holdFees,
|
|
429
|
-
|
|
428
|
+
scopeCashOutsToLocalBalances: payDataRulesetConfig.metadata.scopeCashOutsToLocalBalances,
|
|
430
429
|
useDataHookForPay: true,
|
|
431
430
|
useDataHookForCashOut: payDataRulesetConfig.metadata.useDataHookForCashOut,
|
|
432
431
|
dataHook: address(dataHook),
|
|
@@ -31,7 +31,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
31
31
|
|
|
32
32
|
error JB721TiersHookStore_CantMintManually(uint256 tierId);
|
|
33
33
|
error JB721TiersHookStore_CantRemoveTier(uint256 tierId);
|
|
34
|
-
error JB721TiersHookStore_DeadlockedReserve();
|
|
34
|
+
error JB721TiersHookStore_DeadlockedReserve(uint256 tierId, uint256 initialSupply, uint256 reserveFrequency);
|
|
35
35
|
error JB721TiersHookStore_DiscountPercentExceedsBounds(uint256 percent, uint256 limit);
|
|
36
36
|
error JB721TiersHookStore_DiscountPercentIncreaseNotAllowed(uint256 percent, uint256 storedPercent);
|
|
37
37
|
error JB721TiersHookStore_InsufficientPendingReserves(uint256 count, uint256 numberOfPendingReserves);
|
|
@@ -618,7 +618,6 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
618
618
|
// Cache packed bools to avoid stack-too-deep from destructuring all 6 bools.
|
|
619
619
|
uint8 packed = storedTier.packedBools;
|
|
620
620
|
|
|
621
|
-
// slither-disable-next-line calls-loop
|
|
622
621
|
return JB721Tier({
|
|
623
622
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
624
623
|
id: uint32(tierId),
|
|
@@ -838,11 +837,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
838
837
|
uint256 currentSortedTierId = _firstSortedTierIdOf({hook: hook, category: 0});
|
|
839
838
|
|
|
840
839
|
// Keep track of the previous non-removed tier ID.
|
|
841
|
-
// slither-disable-next-line uninitialized-local
|
|
842
840
|
uint256 previousSortedTierId;
|
|
843
841
|
|
|
844
842
|
// Initialize a `JBBitmapWord` for tracking removed tiers.
|
|
845
|
-
// slither-disable-next-line uninitialized-local
|
|
846
843
|
JBBitmapWord memory bitmapWord;
|
|
847
844
|
|
|
848
845
|
// Make the sorted array.
|
|
@@ -905,7 +902,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
905
902
|
|
|
906
903
|
// Make sure the max number of tiers won't be exceeded.
|
|
907
904
|
if (currentMaxTierIdOf + tiersToAdd.length > type(uint16).max) {
|
|
908
|
-
revert JB721TiersHookStore_MaxTiersExceeded(
|
|
905
|
+
revert JB721TiersHookStore_MaxTiersExceeded({
|
|
906
|
+
numberOfTiers: currentMaxTierIdOf + tiersToAdd.length, limit: type(uint16).max
|
|
907
|
+
});
|
|
909
908
|
}
|
|
910
909
|
|
|
911
910
|
// Keep a reference to the current last sorted tier ID (sorted by category).
|
|
@@ -919,7 +918,6 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
919
918
|
uint256 startSortedTierId = currentMaxTierIdOf == 0 ? 0 : _firstSortedTierIdOf({hook: msg.sender, category: 0});
|
|
920
919
|
|
|
921
920
|
// Keep track of the previous tier's ID while iterating.
|
|
922
|
-
// slither-disable-next-line uninitialized-local
|
|
923
921
|
uint256 previousTierId;
|
|
924
922
|
|
|
925
923
|
// Keep a reference to the 721 contract's flags.
|
|
@@ -932,7 +930,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
932
930
|
// Make sure the supply maximum is enforced. If it's greater than one billion, it would overflow into the
|
|
933
931
|
// next tier.
|
|
934
932
|
if (tierToAdd.initialSupply > _ONE_BILLION - 1) {
|
|
935
|
-
revert JB721TiersHookStore_InvalidQuantity(tierToAdd.initialSupply, _ONE_BILLION - 1);
|
|
933
|
+
revert JB721TiersHookStore_InvalidQuantity({quantity: tierToAdd.initialSupply, limit: _ONE_BILLION - 1});
|
|
936
934
|
}
|
|
937
935
|
|
|
938
936
|
// Make sure the tier's category is greater than or equal to the previously added tier's category.
|
|
@@ -942,7 +940,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
942
940
|
|
|
943
941
|
// Revert if the category is not equal or greater than the previously added tier's category.
|
|
944
942
|
if (tierToAdd.category < previousCategory) {
|
|
945
|
-
revert JB721TiersHookStore_InvalidCategorySortOrder(
|
|
943
|
+
revert JB721TiersHookStore_InvalidCategorySortOrder({
|
|
944
|
+
tierCategory: tierToAdd.category, previousTierCategory: previousCategory
|
|
945
|
+
});
|
|
946
946
|
}
|
|
947
947
|
}
|
|
948
948
|
|
|
@@ -955,32 +955,32 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
955
955
|
&& ((tierToAdd.flags.useVotingUnits && tierToAdd.votingUnits != 0)
|
|
956
956
|
|| (!tierToAdd.flags.useVotingUnits && tierToAdd.price != 0))
|
|
957
957
|
) {
|
|
958
|
-
revert JB721TiersHookStore_VotingUnitsNotAllowed(tierId);
|
|
958
|
+
revert JB721TiersHookStore_VotingUnitsNotAllowed({tierId: tierId});
|
|
959
959
|
}
|
|
960
960
|
|
|
961
961
|
// Make sure the new tier doesn't have a reserve frequency if the 721 contract's flags don't allow it to,
|
|
962
962
|
// OR if manual minting is allowed.
|
|
963
963
|
if ((flags.noNewTiersWithReserves || tierToAdd.flags.allowOwnerMint) && tierToAdd.reserveFrequency != 0) {
|
|
964
|
-
revert JB721TiersHookStore_ReserveFrequencyNotAllowed(tierId);
|
|
964
|
+
revert JB721TiersHookStore_ReserveFrequencyNotAllowed({tierId: tierId});
|
|
965
965
|
}
|
|
966
966
|
|
|
967
967
|
// Make sure the new tier doesn't have owner minting enabled if the 721 contract's flags don't allow it to.
|
|
968
968
|
if (flags.noNewTiersWithOwnerMinting && tierToAdd.flags.allowOwnerMint) {
|
|
969
|
-
revert JB721TiersHookStore_ManualMintingNotAllowed(tierId);
|
|
969
|
+
revert JB721TiersHookStore_ManualMintingNotAllowed({tierId: tierId});
|
|
970
970
|
}
|
|
971
971
|
|
|
972
972
|
// Make sure the discount percent is within the bound.
|
|
973
973
|
if (tierToAdd.discountPercent > JB721Constants.DISCOUNT_DENOMINATOR) {
|
|
974
|
-
revert JB721TiersHookStore_DiscountPercentExceedsBounds(
|
|
975
|
-
tierToAdd.discountPercent, JB721Constants.DISCOUNT_DENOMINATOR
|
|
976
|
-
);
|
|
974
|
+
revert JB721TiersHookStore_DiscountPercentExceedsBounds({
|
|
975
|
+
percent: tierToAdd.discountPercent, limit: JB721Constants.DISCOUNT_DENOMINATOR
|
|
976
|
+
});
|
|
977
977
|
}
|
|
978
978
|
|
|
979
979
|
// Make sure the split percent is within the bound.
|
|
980
980
|
if (tierToAdd.splitPercent > JBConstants.SPLITS_TOTAL_PERCENT) {
|
|
981
|
-
revert JB721TiersHookStore_SplitPercentExceedsBounds(
|
|
982
|
-
tierToAdd.splitPercent, JBConstants.SPLITS_TOTAL_PERCENT
|
|
983
|
-
);
|
|
981
|
+
revert JB721TiersHookStore_SplitPercentExceedsBounds({
|
|
982
|
+
percent: tierToAdd.splitPercent, limit: JBConstants.SPLITS_TOTAL_PERCENT
|
|
983
|
+
});
|
|
984
984
|
}
|
|
985
985
|
|
|
986
986
|
// Make sure the tier has a non-zero supply.
|
|
@@ -989,7 +989,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
989
989
|
// A tier with initialSupply == 1 and reserveFrequency > 0 deadlocks: the single mint is reserved,
|
|
990
990
|
// leaving zero available for paid mints, but reserves only mint after a paid mint triggers them.
|
|
991
991
|
if (tierToAdd.initialSupply == 1 && tierToAdd.reserveFrequency > 0) {
|
|
992
|
-
revert JB721TiersHookStore_DeadlockedReserve(
|
|
992
|
+
revert JB721TiersHookStore_DeadlockedReserve({
|
|
993
|
+
tierId: tierId, initialSupply: tierToAdd.initialSupply, reserveFrequency: tierToAdd.reserveFrequency
|
|
994
|
+
});
|
|
993
995
|
}
|
|
994
996
|
|
|
995
997
|
// A tier with reserves must have a beneficiary — either tier-specific or a previously set default.
|
|
@@ -998,7 +1000,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
998
1000
|
tierToAdd.reserveFrequency > 0 && tierToAdd.reserveBeneficiary == address(0)
|
|
999
1001
|
&& defaultReserveBeneficiaryOf[msg.sender] == address(0)
|
|
1000
1002
|
) {
|
|
1001
|
-
revert JB721TiersHookStore_MissingReserveBeneficiary(tierId);
|
|
1003
|
+
revert JB721TiersHookStore_MissingReserveBeneficiary({tierId: tierId});
|
|
1002
1004
|
}
|
|
1003
1005
|
|
|
1004
1006
|
// Store the tier with that ID.
|
|
@@ -1213,7 +1215,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1213
1215
|
|
|
1214
1216
|
// Make sure the tier hasn't been removed.
|
|
1215
1217
|
if (_isTierRemovedWithRefresh({hook: msg.sender, tierId: tierId, bitmapWord: bitmapWord})) {
|
|
1216
|
-
revert JB721TiersHookStore_TierRemoved(tierId);
|
|
1218
|
+
revert JB721TiersHookStore_TierRemoved({tierId: tierId});
|
|
1217
1219
|
}
|
|
1218
1220
|
|
|
1219
1221
|
// Keep a reference to the stored tier being iterated on.
|
|
@@ -1265,7 +1267,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1265
1267
|
storedTier.remainingSupply
|
|
1266
1268
|
< _numberOfPendingReservesFor({hook: msg.sender, tierId: tierId, storedTier: storedTier})
|
|
1267
1269
|
) {
|
|
1268
|
-
revert JB721TiersHookStore_InsufficientSupplyRemaining(tierId);
|
|
1270
|
+
revert JB721TiersHookStore_InsufficientSupplyRemaining({tierId: tierId});
|
|
1269
1271
|
}
|
|
1270
1272
|
|
|
1271
1273
|
unchecked {
|
|
@@ -1295,7 +1297,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1295
1297
|
|
|
1296
1298
|
// Can't mint more than the number of pending reserves.
|
|
1297
1299
|
if (count > numberOfPendingReserves) {
|
|
1298
|
-
revert JB721TiersHookStore_InsufficientPendingReserves(
|
|
1300
|
+
revert JB721TiersHookStore_InsufficientPendingReserves({
|
|
1301
|
+
count: count, numberOfPendingReserves: numberOfPendingReserves
|
|
1302
|
+
});
|
|
1299
1303
|
}
|
|
1300
1304
|
|
|
1301
1305
|
// Increment the number of reserve NFTs minted.
|
|
@@ -1330,7 +1334,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1330
1334
|
// Reject tier IDs that don't exist yet — removing a future tier would cause it
|
|
1331
1335
|
// to be born already removed when later added.
|
|
1332
1336
|
if (tierId == 0 || tierId > maxTierIdOf[msg.sender]) {
|
|
1333
|
-
revert JB721TiersHookStore_UnrecognizedTier(tierId);
|
|
1337
|
+
revert JB721TiersHookStore_UnrecognizedTier({tierId: tierId});
|
|
1334
1338
|
}
|
|
1335
1339
|
|
|
1336
1340
|
// Get a reference to the stored tier.
|
|
@@ -1363,9 +1367,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1363
1367
|
|
|
1364
1368
|
// Make sure the discount percent is within the bound.
|
|
1365
1369
|
if (discountPercent > JB721Constants.DISCOUNT_DENOMINATOR) {
|
|
1366
|
-
revert JB721TiersHookStore_DiscountPercentExceedsBounds(
|
|
1367
|
-
discountPercent, JB721Constants.DISCOUNT_DENOMINATOR
|
|
1368
|
-
);
|
|
1370
|
+
revert JB721TiersHookStore_DiscountPercentExceedsBounds({
|
|
1371
|
+
percent: discountPercent, limit: JB721Constants.DISCOUNT_DENOMINATOR
|
|
1372
|
+
});
|
|
1369
1373
|
}
|
|
1370
1374
|
|
|
1371
1375
|
// Get a reference to the stored tier.
|
|
@@ -1376,7 +1380,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1376
1380
|
|
|
1377
1381
|
// Make sure that increasing the discount is allowed for the tier.
|
|
1378
1382
|
if (discountPercent > storedTier.discountPercent && cantIncreaseDiscountPercent) {
|
|
1379
|
-
revert JB721TiersHookStore_DiscountPercentIncreaseNotAllowed(
|
|
1383
|
+
revert JB721TiersHookStore_DiscountPercentIncreaseNotAllowed({
|
|
1384
|
+
percent: discountPercent, storedPercent: storedTier.discountPercent
|
|
1385
|
+
});
|
|
1380
1386
|
}
|
|
1381
1387
|
|
|
1382
1388
|
// Set the discount.
|
package/src/abstract/ERC721.sol
CHANGED
|
@@ -72,7 +72,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
72
72
|
*/
|
|
73
73
|
function balanceOf(address owner) public view virtual returns (uint256) {
|
|
74
74
|
if (owner == address(0)) {
|
|
75
|
-
revert ERC721InvalidOwner(address(0));
|
|
75
|
+
revert ERC721InvalidOwner({owner: address(0)});
|
|
76
76
|
}
|
|
77
77
|
return _balances[owner];
|
|
78
78
|
}
|
|
@@ -153,13 +153,13 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
153
153
|
*/
|
|
154
154
|
function transferFrom(address from, address to, uint256 tokenId) public virtual {
|
|
155
155
|
if (to == address(0)) {
|
|
156
|
-
revert ERC721InvalidReceiver(address(0));
|
|
156
|
+
revert ERC721InvalidReceiver({receiver: address(0)});
|
|
157
157
|
}
|
|
158
158
|
// Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists
|
|
159
159
|
// (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
|
|
160
160
|
address previousOwner = _update({to: to, tokenId: tokenId, auth: _msgSender()});
|
|
161
161
|
if (previousOwner != from) {
|
|
162
|
-
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
|
|
162
|
+
revert ERC721IncorrectOwner({sender: from, tokenId: tokenId, owner: previousOwner});
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -206,7 +206,9 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
206
206
|
*/
|
|
207
207
|
function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
|
|
208
208
|
return spender != address(0)
|
|
209
|
-
&& (owner == spender
|
|
209
|
+
&& (owner == spender
|
|
210
|
+
|| isApprovedForAll({owner: owner, operator: spender})
|
|
211
|
+
|| _getApproved(tokenId) == spender);
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
/**
|
|
@@ -218,11 +220,11 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
218
220
|
* assumption.
|
|
219
221
|
*/
|
|
220
222
|
function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
|
|
221
|
-
if (!_isAuthorized(owner, spender, tokenId)) {
|
|
223
|
+
if (!_isAuthorized({owner: owner, spender: spender, tokenId: tokenId})) {
|
|
222
224
|
if (owner == address(0)) {
|
|
223
|
-
revert ERC721NonexistentToken(tokenId);
|
|
225
|
+
revert ERC721NonexistentToken({tokenId: tokenId});
|
|
224
226
|
} else {
|
|
225
|
-
revert ERC721InsufficientApproval(spender, tokenId);
|
|
227
|
+
revert ERC721InsufficientApproval({operator: spender, tokenId: tokenId});
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
}
|
|
@@ -280,7 +282,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
280
282
|
|
|
281
283
|
_owners[tokenId] = to;
|
|
282
284
|
|
|
283
|
-
emit Transfer(from, to, tokenId);
|
|
285
|
+
emit Transfer({from: from, to: to, tokenId: tokenId});
|
|
284
286
|
|
|
285
287
|
return from;
|
|
286
288
|
}
|
|
@@ -299,11 +301,11 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
299
301
|
*/
|
|
300
302
|
function _mint(address to, uint256 tokenId) internal {
|
|
301
303
|
if (to == address(0)) {
|
|
302
|
-
revert ERC721InvalidReceiver(address(0));
|
|
304
|
+
revert ERC721InvalidReceiver({receiver: address(0)});
|
|
303
305
|
}
|
|
304
306
|
address previousOwner = _update({to: to, tokenId: tokenId, auth: address(0)});
|
|
305
307
|
if (previousOwner != address(0)) {
|
|
306
|
-
revert ERC721InvalidSender(address(0));
|
|
308
|
+
revert ERC721InvalidSender({sender: address(0)});
|
|
307
309
|
}
|
|
308
310
|
}
|
|
309
311
|
|
|
@@ -345,7 +347,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
345
347
|
function _burn(uint256 tokenId) internal {
|
|
346
348
|
address previousOwner = _update({to: address(0), tokenId: tokenId, auth: address(0)});
|
|
347
349
|
if (previousOwner == address(0)) {
|
|
348
|
-
revert ERC721NonexistentToken(tokenId);
|
|
350
|
+
revert ERC721NonexistentToken({tokenId: tokenId});
|
|
349
351
|
}
|
|
350
352
|
}
|
|
351
353
|
|
|
@@ -362,13 +364,13 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
362
364
|
*/
|
|
363
365
|
function _transfer(address from, address to, uint256 tokenId) internal {
|
|
364
366
|
if (to == address(0)) {
|
|
365
|
-
revert ERC721InvalidReceiver(address(0));
|
|
367
|
+
revert ERC721InvalidReceiver({receiver: address(0)});
|
|
366
368
|
}
|
|
367
369
|
address previousOwner = _update({to: to, tokenId: tokenId, auth: address(0)});
|
|
368
370
|
if (previousOwner == address(0)) {
|
|
369
|
-
revert ERC721NonexistentToken(tokenId);
|
|
371
|
+
revert ERC721NonexistentToken({tokenId: tokenId});
|
|
370
372
|
} else if (previousOwner != from) {
|
|
371
|
-
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
|
|
373
|
+
revert ERC721IncorrectOwner({sender: from, tokenId: tokenId, owner: previousOwner});
|
|
372
374
|
}
|
|
373
375
|
}
|
|
374
376
|
|
|
@@ -430,12 +432,12 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
430
432
|
address owner = _requireOwned(tokenId);
|
|
431
433
|
|
|
432
434
|
// We do not use _isAuthorized because single-token approvals should not be able to call approve
|
|
433
|
-
if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
|
|
434
|
-
revert ERC721InvalidApprover(auth);
|
|
435
|
+
if (auth != address(0) && owner != auth && !isApprovedForAll({owner: owner, operator: auth})) {
|
|
436
|
+
revert ERC721InvalidApprover({approver: auth});
|
|
435
437
|
}
|
|
436
438
|
|
|
437
439
|
if (emitEvent) {
|
|
438
|
-
emit Approval(owner, to, tokenId);
|
|
440
|
+
emit Approval({owner: owner, approved: to, tokenId: tokenId});
|
|
439
441
|
}
|
|
440
442
|
}
|
|
441
443
|
|
|
@@ -452,10 +454,10 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
452
454
|
*/
|
|
453
455
|
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
|
|
454
456
|
if (operator == address(0)) {
|
|
455
|
-
revert ERC721InvalidOperator(operator);
|
|
457
|
+
revert ERC721InvalidOperator({operator: operator});
|
|
456
458
|
}
|
|
457
459
|
_operatorApprovals[owner][operator] = approved;
|
|
458
|
-
emit ApprovalForAll(owner, operator, approved);
|
|
460
|
+
emit ApprovalForAll({owner: owner, operator: operator, approved: approved});
|
|
459
461
|
}
|
|
460
462
|
|
|
461
463
|
/**
|
|
@@ -467,7 +469,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
467
469
|
function _requireOwned(uint256 tokenId) internal view returns (address) {
|
|
468
470
|
address owner = _ownerOf(tokenId);
|
|
469
471
|
if (owner == address(0)) {
|
|
470
|
-
revert ERC721NonexistentToken(tokenId);
|
|
472
|
+
revert ERC721NonexistentToken({tokenId: tokenId});
|
|
471
473
|
}
|
|
472
474
|
return owner;
|
|
473
475
|
}
|
|
@@ -489,11 +491,11 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
489
491
|
bytes4 retval
|
|
490
492
|
) {
|
|
491
493
|
if (retval != IERC721Receiver.onERC721Received.selector) {
|
|
492
|
-
revert ERC721InvalidReceiver(to);
|
|
494
|
+
revert ERC721InvalidReceiver({receiver: to});
|
|
493
495
|
}
|
|
494
496
|
} catch (bytes memory reason) {
|
|
495
497
|
if (reason.length == 0) {
|
|
496
|
-
revert ERC721InvalidReceiver(to);
|
|
498
|
+
revert ERC721InvalidReceiver({receiver: to});
|
|
497
499
|
} else {
|
|
498
500
|
/// @solidity memory-safe-assembly
|
|
499
501
|
assembly {
|
|
@@ -30,10 +30,10 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
30
30
|
// --------------------------- custom errors ------------------------- //
|
|
31
31
|
//*********************************************************************//
|
|
32
32
|
|
|
33
|
-
error JB721Hook_InvalidCashOut();
|
|
34
|
-
error JB721Hook_InvalidPay();
|
|
33
|
+
error JB721Hook_InvalidCashOut(address caller, uint256 contextProjectId, uint256 projectId, uint256 msgValue);
|
|
34
|
+
error JB721Hook_InvalidPay(address caller, uint256 contextProjectId, uint256 projectId);
|
|
35
35
|
error JB721Hook_UnauthorizedToken(uint256 tokenId, address holder);
|
|
36
|
-
error JB721Hook_UnexpectedTokenCashedOut();
|
|
36
|
+
error JB721Hook_UnexpectedTokenCashedOut(uint256 cashOutCount);
|
|
37
37
|
|
|
38
38
|
//*********************************************************************//
|
|
39
39
|
// --------------- public immutable stored properties ---------------- //
|
|
@@ -94,7 +94,9 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
94
94
|
)
|
|
95
95
|
{
|
|
96
96
|
// Make sure (fungible) project tokens aren't also being cashed out.
|
|
97
|
-
if (context.cashOutCount > 0)
|
|
97
|
+
if (context.cashOutCount > 0) {
|
|
98
|
+
revert JB721Hook_UnexpectedTokenCashedOut({cashOutCount: context.cashOutCount});
|
|
99
|
+
}
|
|
98
100
|
|
|
99
101
|
// Fetch the cash out hook metadata using the corresponding metadata ID.
|
|
100
102
|
(bool metadataExists, bytes memory metadata) = JBMetadataResolver.getDataFor({
|
|
@@ -185,7 +187,6 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
185
187
|
/// `context.beneficiary`. Part of `IJBCashOutHook`.
|
|
186
188
|
/// @dev Reverts if the calling contract is not one of the project's terminals.
|
|
187
189
|
/// @param context The cash out context passed in by the terminal.
|
|
188
|
-
// slither-disable-next-line locked-ether
|
|
189
190
|
function afterCashOutRecordedWith(JBAfterCashOutRecordedContext calldata context)
|
|
190
191
|
external
|
|
191
192
|
payable
|
|
@@ -200,7 +201,11 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
200
201
|
if (
|
|
201
202
|
msg.value != 0 || !DIRECTORY.isTerminalOf({projectId: projectId, terminal: IJBTerminal(msg.sender)})
|
|
202
203
|
|| context.projectId != projectId
|
|
203
|
-
)
|
|
204
|
+
) {
|
|
205
|
+
revert JB721Hook_InvalidCashOut({
|
|
206
|
+
caller: msg.sender, contextProjectId: context.projectId, projectId: projectId, msgValue: msg.value
|
|
207
|
+
});
|
|
208
|
+
}
|
|
204
209
|
|
|
205
210
|
// Fetch the cash out hook metadata using the corresponding metadata ID.
|
|
206
211
|
(bool metadataExists, bytes memory metadata) = JBMetadataResolver.getDataFor({
|
|
@@ -237,7 +242,6 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
237
242
|
/// `IJBPayHook`.
|
|
238
243
|
/// @dev Reverts if the calling contract is not one of the project's terminals.
|
|
239
244
|
/// @param context The payment context passed in by the terminal.
|
|
240
|
-
// slither-disable-next-line locked-ether
|
|
241
245
|
function afterPayRecordedWith(JBAfterPayRecordedContext calldata context) external payable virtual override {
|
|
242
246
|
uint256 projectId = PROJECT_ID;
|
|
243
247
|
|
|
@@ -247,7 +251,7 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
247
251
|
!DIRECTORY.isTerminalOf({projectId: projectId, terminal: IJBTerminal(msg.sender)})
|
|
248
252
|
|| context.projectId != projectId
|
|
249
253
|
) {
|
|
250
|
-
revert JB721Hook_InvalidPay();
|
|
254
|
+
revert JB721Hook_InvalidPay({caller: msg.sender, contextProjectId: context.projectId, projectId: projectId});
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
// Process the payment.
|
|
@@ -6,9 +6,6 @@ import {IJB721TiersHookStore} from "./IJB721TiersHookStore.sol";
|
|
|
6
6
|
|
|
7
7
|
/// @notice Deploys JB721Checkpoints clones for JB721TiersHook instances.
|
|
8
8
|
interface IJB721CheckpointsDeployer {
|
|
9
|
-
/// @notice Thrown when the caller is not the hook that the checkpoint module is deployed for.
|
|
10
|
-
error JB721CheckpointsDeployer_Unauthorized();
|
|
11
|
-
|
|
12
9
|
/// @notice The implementation contract that clones are based on.
|
|
13
10
|
/// @return The implementation address.
|
|
14
11
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
@@ -18,14 +18,6 @@ import {JB721TiersSetDiscountPercentConfig} from "../structs/JB721TiersSetDiscou
|
|
|
18
18
|
|
|
19
19
|
/// @notice A 721 tiers hook that mints tiered NFTs for payments and tracks their cash out weight.
|
|
20
20
|
interface IJB721TiersHook is IJB721Hook {
|
|
21
|
-
/// @notice Emitted when an `addToBalanceOf` call reverts during leftover distribution. The funds remain
|
|
22
|
-
/// stranded in the hook contract.
|
|
23
|
-
/// @param projectId The project ID whose terminal reverted.
|
|
24
|
-
/// @param token The token to send.
|
|
25
|
-
/// @param amount The amount that failed to send.
|
|
26
|
-
/// @param reason The revert reason bytes.
|
|
27
|
-
event AddToBalanceReverted(uint256 indexed projectId, address token, uint256 amount, bytes reason);
|
|
28
|
-
|
|
29
21
|
/// @notice Emitted when pay credits are added for an account.
|
|
30
22
|
/// @param amount The amount of credits added.
|
|
31
23
|
/// @param newTotalCredits The new total credits balance for the account.
|
|
@@ -3,6 +3,7 @@ pragma solidity 0.8.28;
|
|
|
3
3
|
|
|
4
4
|
/// @notice Global constants used across 721 hook contracts.
|
|
5
5
|
library JB721Constants {
|
|
6
|
+
/// @notice The denominator used when applying tier discount percentages.
|
|
6
7
|
uint16 public constant DISCOUNT_DENOMINATOR = 200;
|
|
7
8
|
|
|
8
9
|
/// @notice The metadata ID used to identify the 721 beneficiary entry in payment metadata.
|
|
@@ -31,7 +31,7 @@ library JB721TiersHookLib {
|
|
|
31
31
|
// --------------------------- custom errors ------------------------- //
|
|
32
32
|
//*********************************************************************//
|
|
33
33
|
|
|
34
|
-
error JB721TiersHook_CantBuyWithCredits();
|
|
34
|
+
error JB721TiersHook_CantBuyWithCredits(uint256 restrictedCost, uint256 freshValue);
|
|
35
35
|
error JB721TiersHook_Overspending(uint256 leftoverAmount);
|
|
36
36
|
error JB721TiersHookLib_NoTerminalForLeftover(uint256 projectId, address token, uint256 leftoverAmount);
|
|
37
37
|
error JB721TiersHookLib_SplitFallbackFailed(uint256 projectId, address token, uint256 amount, bytes reason);
|
|
@@ -79,7 +79,6 @@ library JB721TiersHookLib {
|
|
|
79
79
|
++i;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
// slither-disable-next-line reentrancy-events
|
|
83
82
|
store.recordRemoveTierIds(tierIdsToRemove);
|
|
84
83
|
}
|
|
85
84
|
|
|
@@ -87,7 +86,6 @@ library JB721TiersHookLib {
|
|
|
87
86
|
if (tiersToAdd.length != 0) {
|
|
88
87
|
uint256[] memory tierIdsAdded = store.recordAddTiers(tiersToAdd);
|
|
89
88
|
|
|
90
|
-
// slither-disable-next-line reentrancy-events
|
|
91
89
|
for (uint256 i; i < tiersToAdd.length;) {
|
|
92
90
|
emit AddTier({tierId: tierIdsAdded[i], tier: tiersToAdd[i], caller: caller});
|
|
93
91
|
|
|
@@ -135,7 +133,9 @@ library JB721TiersHookLib {
|
|
|
135
133
|
SafeERC20.safeTransferFrom({token: IERC20(token), from: msg.sender, to: address(this), value: amount});
|
|
136
134
|
uint256 receivedAmount = IERC20(token).balanceOf(address(this)) - balanceBefore;
|
|
137
135
|
if (receivedAmount != amount) {
|
|
138
|
-
revert JB721TiersHookLib_TokenTransferAmountMismatch(
|
|
136
|
+
revert JB721TiersHookLib_TokenTransferAmountMismatch({
|
|
137
|
+
expectedAmount: amount, receivedAmount: receivedAmount
|
|
138
|
+
});
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -223,16 +223,19 @@ library JB721TiersHookLib {
|
|
|
223
223
|
if (tierIdsToMint.length != 0) {
|
|
224
224
|
uint256 restrictedCost;
|
|
225
225
|
|
|
226
|
-
// slither-disable-next-line reentrancy-events,reentrancy-no-eth
|
|
227
226
|
(tokenIds, leftoverAmount, restrictedCost) =
|
|
228
227
|
store.recordMint({amount: leftoverAmount, tierIds: tierIdsToMint, isOwnerMint: false});
|
|
229
228
|
|
|
230
229
|
// Credit-restricted tiers must be fully covered by fresh payment (not stored credits).
|
|
231
|
-
if (restrictedCost > value)
|
|
230
|
+
if (restrictedCost > value) {
|
|
231
|
+
revert JB721TiersHook_CantBuyWithCredits({restrictedCost: restrictedCost, freshValue: value});
|
|
232
|
+
}
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
// If overspending isn't allowed, revert.
|
|
235
|
-
if (leftoverAmount != 0 && !allowOverspending)
|
|
236
|
+
if (leftoverAmount != 0 && !allowOverspending) {
|
|
237
|
+
revert JB721TiersHook_Overspending({leftoverAmount: leftoverAmount});
|
|
238
|
+
}
|
|
236
239
|
|
|
237
240
|
// Compute the new pay credits balance: leftover + unused credits (held in newPayCredits).
|
|
238
241
|
newPayCredits = leftoverAmount + newPayCredits;
|
|
@@ -259,7 +262,6 @@ library JB721TiersHookLib {
|
|
|
259
262
|
uint256[] memory tierIdsAdded = store.recordAddTiers(tiersToAdd);
|
|
260
263
|
|
|
261
264
|
for (uint256 i; i < tiersToAdd.length;) {
|
|
262
|
-
// slither-disable-next-line reentrancy-events
|
|
263
265
|
emit AddTier({tierId: tierIdsAdded[i], tier: tiersToAdd[i], caller: caller});
|
|
264
266
|
|
|
265
267
|
unchecked {
|
|
@@ -489,7 +491,6 @@ library JB721TiersHookLib {
|
|
|
489
491
|
|
|
490
492
|
for (uint256 i; i < tierIdsToMint.length;) {
|
|
491
493
|
// Get only the pricing fields (lightweight — avoids full struct construction).
|
|
492
|
-
// slither-disable-next-line calls-loop
|
|
493
494
|
(uint104 tierPrice, uint32 tierSplitPercent, uint8 tierDiscountPercent) =
|
|
494
495
|
store.tierPricingOf({hook: hook, id: tierIdsToMint[i]});
|
|
495
496
|
if (tierSplitPercent != 0) {
|
|
@@ -707,7 +708,6 @@ library JB721TiersHookLib {
|
|
|
707
708
|
)
|
|
708
709
|
private
|
|
709
710
|
{
|
|
710
|
-
// slither-disable-next-line calls-loop
|
|
711
711
|
JBSplit[] memory tierSplits = splitsContract.splitsOf({projectId: projectId, rulesetId: 0, groupId: groupId});
|
|
712
712
|
|
|
713
713
|
bool isNativeToken = token == JBConstants.NATIVE_TOKEN;
|
|
@@ -725,7 +725,6 @@ library JB721TiersHookLib {
|
|
|
725
725
|
// On failure, don't re-add to leftoverAmount — this prevents inflating later recipients.
|
|
726
726
|
// Failed amounts accumulate as the gap between `amount` and `leftoverAmount + total sent`.
|
|
727
727
|
// After the loop, we re-add leftoverPercentage-based residual naturally.
|
|
728
|
-
// slither-disable-next-line calls-loop,reentrancy-no-eth,reentrancy-benign,reentrancy-events
|
|
729
728
|
if (!_sendPayoutToSplit({
|
|
730
729
|
directory: directory,
|
|
731
730
|
split: tierSplits[j],
|
|
@@ -752,14 +751,14 @@ library JB721TiersHookLib {
|
|
|
752
751
|
leftoverAmount += amount;
|
|
753
752
|
|
|
754
753
|
if (leftoverAmount != 0) {
|
|
755
|
-
// slither-disable-next-line calls-loop
|
|
756
754
|
IJBTerminal terminal = directory.primaryTerminalOf({projectId: projectId, token: token});
|
|
757
755
|
// Revert if there are leftover funds but no terminal to route them to.
|
|
758
756
|
if (address(terminal) == address(0)) {
|
|
759
|
-
revert JB721TiersHookLib_NoTerminalForLeftover(
|
|
757
|
+
revert JB721TiersHookLib_NoTerminalForLeftover({
|
|
758
|
+
projectId: projectId, token: token, leftoverAmount: leftoverAmount
|
|
759
|
+
});
|
|
760
760
|
}
|
|
761
761
|
if (isNativeToken) {
|
|
762
|
-
// slither-disable-next-line arbitrary-send-eth,calls-loop
|
|
763
762
|
try terminal.addToBalanceOf{value: leftoverAmount}({
|
|
764
763
|
projectId: projectId,
|
|
765
764
|
token: token,
|
|
@@ -769,11 +768,12 @@ library JB721TiersHookLib {
|
|
|
769
768
|
metadata: bytes("")
|
|
770
769
|
}) {}
|
|
771
770
|
catch (bytes memory reason) {
|
|
772
|
-
revert JB721TiersHookLib_SplitFallbackFailed(
|
|
771
|
+
revert JB721TiersHookLib_SplitFallbackFailed({
|
|
772
|
+
projectId: projectId, token: token, amount: leftoverAmount, reason: reason
|
|
773
|
+
});
|
|
773
774
|
}
|
|
774
775
|
} else {
|
|
775
776
|
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: leftoverAmount});
|
|
776
|
-
// slither-disable-next-line calls-loop
|
|
777
777
|
try terminal.addToBalanceOf({
|
|
778
778
|
projectId: projectId,
|
|
779
779
|
token: token,
|
|
@@ -785,7 +785,9 @@ library JB721TiersHookLib {
|
|
|
785
785
|
catch (bytes memory reason) {
|
|
786
786
|
// Reset approval on failure.
|
|
787
787
|
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: 0});
|
|
788
|
-
revert JB721TiersHookLib_SplitFallbackFailed(
|
|
788
|
+
revert JB721TiersHookLib_SplitFallbackFailed({
|
|
789
|
+
projectId: projectId, token: token, amount: leftoverAmount, reason: reason
|
|
790
|
+
});
|
|
789
791
|
}
|
|
790
792
|
}
|
|
791
793
|
}
|
|
@@ -817,11 +819,9 @@ library JB721TiersHookLib {
|
|
|
817
819
|
if (isNativeToken) {
|
|
818
820
|
// Wrap in try-catch so a reverting hook doesn't brick all project payments.
|
|
819
821
|
// On revert, ETH stays with the caller and we return false.
|
|
820
|
-
// slither-disable-next-line calls-loop,reentrancy-no-eth,reentrancy-events
|
|
821
822
|
try split.hook.processSplitWith{value: amount}(context) {
|
|
822
823
|
return true;
|
|
823
824
|
} catch (bytes memory reason) {
|
|
824
|
-
// slither-disable-next-line reentrancy-events
|
|
825
825
|
emit SplitPayoutReverted({
|
|
826
826
|
projectId: projectId, split: split, amount: amount, reason: reason, caller: msg.sender
|
|
827
827
|
});
|
|
@@ -834,10 +834,8 @@ library JB721TiersHookLib {
|
|
|
834
834
|
// cause the caller to skip subtracting this amount from leftoverAmount, leading
|
|
835
835
|
// to a double-spend when the leftover is later sent to the project's balance.
|
|
836
836
|
SafeERC20.safeTransfer({token: IERC20(token), to: address(split.hook), value: amount});
|
|
837
|
-
// slither-disable-next-line calls-loop,reentrancy-no-eth,reentrancy-events
|
|
838
837
|
try split.hook.processSplitWith(context) {}
|
|
839
838
|
catch (bytes memory reason) {
|
|
840
|
-
// slither-disable-next-line reentrancy-events
|
|
841
839
|
emit SplitPayoutReverted({
|
|
842
840
|
projectId: projectId, split: split, amount: amount, reason: reason, caller: msg.sender
|
|
843
841
|
});
|
|
@@ -845,14 +843,12 @@ library JB721TiersHookLib {
|
|
|
845
843
|
return true;
|
|
846
844
|
}
|
|
847
845
|
} else if (split.projectId != 0) {
|
|
848
|
-
// slither-disable-next-line calls-loop
|
|
849
846
|
IJBTerminal terminal = directory.primaryTerminalOf({projectId: split.projectId, token: token});
|
|
850
847
|
if (address(terminal) == address(0)) return false;
|
|
851
848
|
|
|
852
849
|
// Wrap terminal calls in try-catch to prevent a failing terminal from bricking payments.
|
|
853
850
|
if (split.preferAddToBalance) {
|
|
854
851
|
if (isNativeToken) {
|
|
855
|
-
// slither-disable-next-line arbitrary-send-eth,calls-loop,reentrancy-no-eth,reentrancy-events
|
|
856
852
|
try terminal.addToBalanceOf{value: amount}({
|
|
857
853
|
projectId: split.projectId,
|
|
858
854
|
token: token,
|
|
@@ -863,7 +859,6 @@ library JB721TiersHookLib {
|
|
|
863
859
|
}) {
|
|
864
860
|
return true;
|
|
865
861
|
} catch (bytes memory reason) {
|
|
866
|
-
// slither-disable-next-line reentrancy-events
|
|
867
862
|
emit SplitPayoutReverted({
|
|
868
863
|
projectId: projectId, split: split, amount: amount, reason: reason, caller: msg.sender
|
|
869
864
|
});
|
|
@@ -871,7 +866,6 @@ library JB721TiersHookLib {
|
|
|
871
866
|
}
|
|
872
867
|
} else {
|
|
873
868
|
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: amount});
|
|
874
|
-
// slither-disable-next-line calls-loop,reentrancy-no-eth,reentrancy-events
|
|
875
869
|
try terminal.addToBalanceOf({
|
|
876
870
|
projectId: split.projectId,
|
|
877
871
|
token: token,
|
|
@@ -884,7 +878,6 @@ library JB721TiersHookLib {
|
|
|
884
878
|
} catch (bytes memory reason) {
|
|
885
879
|
// Reset approval on failure so tokens aren't left approved to the terminal.
|
|
886
880
|
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: 0});
|
|
887
|
-
// slither-disable-next-line reentrancy-events
|
|
888
881
|
emit SplitPayoutReverted({
|
|
889
882
|
projectId: projectId, split: split, amount: amount, reason: reason, caller: msg.sender
|
|
890
883
|
});
|
|
@@ -893,7 +886,6 @@ library JB721TiersHookLib {
|
|
|
893
886
|
}
|
|
894
887
|
} else {
|
|
895
888
|
if (isNativeToken) {
|
|
896
|
-
// slither-disable-next-line arbitrary-send-eth,unused-return,calls-loop,reentrancy-events
|
|
897
889
|
try terminal.pay{value: amount}({
|
|
898
890
|
projectId: split.projectId,
|
|
899
891
|
token: token,
|
|
@@ -905,7 +897,6 @@ library JB721TiersHookLib {
|
|
|
905
897
|
}) {
|
|
906
898
|
return true;
|
|
907
899
|
} catch (bytes memory reason) {
|
|
908
|
-
// slither-disable-next-line reentrancy-events
|
|
909
900
|
emit SplitPayoutReverted({
|
|
910
901
|
projectId: projectId, split: split, amount: amount, reason: reason, caller: msg.sender
|
|
911
902
|
});
|
|
@@ -913,7 +904,6 @@ library JB721TiersHookLib {
|
|
|
913
904
|
}
|
|
914
905
|
} else {
|
|
915
906
|
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: amount});
|
|
916
|
-
// slither-disable-next-line unused-return,calls-loop,reentrancy-no-eth,reentrancy-events
|
|
917
907
|
try terminal.pay({
|
|
918
908
|
projectId: split.projectId,
|
|
919
909
|
token: token,
|
|
@@ -927,7 +917,6 @@ library JB721TiersHookLib {
|
|
|
927
917
|
} catch (bytes memory reason) {
|
|
928
918
|
// Reset approval on failure so tokens aren't left approved to the terminal.
|
|
929
919
|
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: 0});
|
|
930
|
-
// slither-disable-next-line reentrancy-events
|
|
931
920
|
emit SplitPayoutReverted({
|
|
932
921
|
projectId: projectId, split: split, amount: amount, reason: reason, caller: msg.sender
|
|
933
922
|
});
|
|
@@ -937,7 +926,6 @@ library JB721TiersHookLib {
|
|
|
937
926
|
}
|
|
938
927
|
} else if (split.beneficiary != address(0)) {
|
|
939
928
|
if (isNativeToken) {
|
|
940
|
-
// slither-disable-next-line arbitrary-send-eth,calls-loop
|
|
941
929
|
(bool success,) = split.beneficiary.call{value: amount}("");
|
|
942
930
|
if (!success) return false;
|
|
943
931
|
} else {
|
|
@@ -945,7 +933,6 @@ library JB721TiersHookLib {
|
|
|
945
933
|
// false on failure instead of reverting. This handles non-standard tokens (e.g. USDT)
|
|
946
934
|
// that return void, while routing failed transfers to the project's balance instead
|
|
947
935
|
// of bricking all payments.
|
|
948
|
-
// slither-disable-next-line calls-loop
|
|
949
936
|
(bool callSuccess, bytes memory returndata) =
|
|
950
937
|
address(token).call(abi.encodeCall(IERC20.transfer, (split.beneficiary, amount)));
|
|
951
938
|
if (!callSuccess || (returndata.length != 0 && !abi.decode(returndata, (bool)))) return false;
|
|
@@ -32,7 +32,7 @@ library JBBitmap {
|
|
|
32
32
|
/// @dev The `index` is the index that the bit would have if the bitmap were reshaped to a 1*n matrix.
|
|
33
33
|
function isTierIdRemoved(mapping(uint256 => uint256) storage self, uint256 index) internal view returns (bool) {
|
|
34
34
|
uint256 depth = _retrieveDepth(index);
|
|
35
|
-
return isTierIdRemoved(JBBitmapWord({currentWord: self[depth], currentDepth: depth}), index);
|
|
35
|
+
return isTierIdRemoved({self: JBBitmapWord({currentWord: self[depth], currentDepth: depth}), index: index});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/// @notice Set the bit at the given index to true, indicating that the corresponding tier has been removed.
|
|
@@ -22,8 +22,8 @@ pragma solidity ^0.8.0;
|
|
|
22
22
|
/// @custom:member allowAddPriceFeed A flag indicating if a project can add new price feeds to calculate exchange rates
|
|
23
23
|
/// between its tokens.
|
|
24
24
|
/// @custom:member holdFees A flag indicating if fees should be held during this ruleset.
|
|
25
|
-
/// @custom:member
|
|
26
|
-
///
|
|
25
|
+
/// @custom:member scopeCashOutsToLocalBalances A flag indicating if omnichain cash-out calculations should use only
|
|
26
|
+
/// the local chain's terminal balance instead of the project's balance held in all terminals.
|
|
27
27
|
/// @custom:member useDataHookForCashOuts A flag indicating if the data hook should be used for cash out transactions
|
|
28
28
|
/// during
|
|
29
29
|
/// this ruleset.
|
|
@@ -43,7 +43,7 @@ struct JBPayDataHookRulesetMetadata {
|
|
|
43
43
|
bool allowAddPriceFeed;
|
|
44
44
|
bool ownerMustSendPayouts;
|
|
45
45
|
bool holdFees;
|
|
46
|
-
bool
|
|
46
|
+
bool scopeCashOutsToLocalBalances;
|
|
47
47
|
bool useDataHookForCashOut;
|
|
48
48
|
uint16 metadata;
|
|
49
49
|
}
|
|
@@ -223,7 +223,7 @@ contract UnitTestSetup is Test {
|
|
|
223
223
|
allowAddPriceFeed: false,
|
|
224
224
|
ownerMustSendPayouts: false,
|
|
225
225
|
holdFees: false,
|
|
226
|
-
|
|
226
|
+
scopeCashOutsToLocalBalances: true,
|
|
227
227
|
useDataHookForPay: true,
|
|
228
228
|
useDataHookForCashOut: true,
|
|
229
229
|
dataHook: address(0),
|
|
@@ -772,7 +772,7 @@ contract UnitTestSetup is Test {
|
|
|
772
772
|
allowAddAccountingContext: false,
|
|
773
773
|
allowAddPriceFeed: false,
|
|
774
774
|
holdFees: false,
|
|
775
|
-
|
|
775
|
+
scopeCashOutsToLocalBalances: true,
|
|
776
776
|
useDataHookForCashOut: false,
|
|
777
777
|
metadata: 0x00
|
|
778
778
|
});
|