@bananapus/721-hook-v6 0.0.51 → 0.0.52
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/package.json +1 -1
- package/script/Deploy.s.sol +36 -39
- package/script/helpers/Hook721DeploymentLib.sol +18 -24
- package/src/JB721Checkpoints.sol +11 -11
- package/src/JB721TiersHook.sol +33 -36
- package/src/JB721TiersHookDeployer.sol +1 -1
- package/src/JB721TiersHookProjectDeployer.sol +5 -5
- package/src/JB721TiersHookStore.sol +9 -9
- package/src/abstract/ERC721.sol +3 -3
- package/src/abstract/JB721Hook.sol +15 -13
- package/src/interfaces/IJB721Checkpoints.sol +3 -3
- package/src/interfaces/IJB721Hook.sol +1 -1
- package/src/interfaces/IJB721TiersHook.sol +8 -9
- package/src/interfaces/IJB721TiersHookStore.sol +3 -3
- package/src/libraries/JB721TiersHookLib.sol +182 -43
- package/src/structs/JB721Tier.sol +2 -2
- package/src/structs/JB721TierConfig.sol +2 -2
- package/test/utils/ForTest_JB721TiersHook.sol +1 -1
- package/test/utils/UnitTestSetup.sol +8 -8
package/package.json
CHANGED
package/script/Deploy.s.sol
CHANGED
|
@@ -24,20 +24,14 @@ contract DeployScript is Script, Sphinx {
|
|
|
24
24
|
AddressRegistryDeployment registry;
|
|
25
25
|
|
|
26
26
|
/// @notice The address that is allowed to forward calls to the terminal and controller on a users behalf.
|
|
27
|
-
|
|
28
|
-
address private TRUSTED_FORWARDER;
|
|
27
|
+
address private trustedForwarder;
|
|
29
28
|
|
|
30
29
|
/// @notice the salts that are used to deploy the contracts.
|
|
31
|
-
|
|
32
|
-
bytes32
|
|
33
|
-
|
|
34
|
-
bytes32
|
|
35
|
-
|
|
36
|
-
bytes32 HOOK_STORE_SALT = "JB721TiersHookStoreV6_";
|
|
37
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
38
|
-
bytes32 PROJECT_DEPLOYER_SALT = "JB721TiersHookProjectDeployerV6";
|
|
39
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
40
|
-
bytes32 CHECKPOINTS_DEPLOYER_SALT = "JB721CheckpointsDeployerV6";
|
|
30
|
+
bytes32 private constant _HOOK_SALT = "JB721TiersHookV6_";
|
|
31
|
+
bytes32 private constant _HOOK_DEPLOYER_SALT = "JB721TiersHookDeployerV6_";
|
|
32
|
+
bytes32 private constant _HOOK_STORE_SALT = "JB721TiersHookStoreV6_";
|
|
33
|
+
bytes32 private constant _PROJECT_DEPLOYER_SALT = "JB721TiersHookProjectDeployerV6";
|
|
34
|
+
bytes32 private constant _CHECKPOINTS_DEPLOYER_SALT = "JB721CheckpointsDeployerV6";
|
|
41
35
|
|
|
42
36
|
function configureSphinx() public override {
|
|
43
37
|
sphinxConfig.projectName = "nana-721-hook-v6";
|
|
@@ -53,7 +47,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
53
47
|
);
|
|
54
48
|
|
|
55
49
|
// We use the same trusted forwarder as the core deployment.
|
|
56
|
-
|
|
50
|
+
trustedForwarder = core.permissions.trustedForwarder();
|
|
57
51
|
|
|
58
52
|
registry = AddressRegistryDeploymentLib.getDeployment(
|
|
59
53
|
vm.envOr(
|
|
@@ -74,25 +68,25 @@ contract DeployScript is Script, Sphinx {
|
|
|
74
68
|
{
|
|
75
69
|
// Perform the check for the store.
|
|
76
70
|
(address _store, bool _storeIsDeployed) = _isDeployed({
|
|
77
|
-
salt:
|
|
71
|
+
salt: _HOOK_STORE_SALT, creationCode: type(JB721TiersHookStore).creationCode, arguments: ""
|
|
78
72
|
});
|
|
79
73
|
|
|
80
74
|
// Deploy it if it has not been deployed yet.
|
|
81
|
-
store = !_storeIsDeployed ? new JB721TiersHookStore{salt:
|
|
75
|
+
store = !_storeIsDeployed ? new JB721TiersHookStore{salt: _HOOK_STORE_SALT}() : JB721TiersHookStore(_store);
|
|
82
76
|
}
|
|
83
77
|
|
|
84
78
|
JB721CheckpointsDeployer checkpointsDeployer;
|
|
85
79
|
{
|
|
86
80
|
// Perform the check for the deployer.
|
|
87
81
|
(address _deployer, bool _deployerIsDeployed) = _isDeployed({
|
|
88
|
-
salt:
|
|
82
|
+
salt: _CHECKPOINTS_DEPLOYER_SALT,
|
|
89
83
|
creationCode: type(JB721CheckpointsDeployer).creationCode,
|
|
90
84
|
arguments: abi.encode(store)
|
|
91
85
|
});
|
|
92
86
|
|
|
93
87
|
// Deploy it if it has not been deployed yet.
|
|
94
88
|
checkpointsDeployer = !_deployerIsDeployed
|
|
95
|
-
? new JB721CheckpointsDeployer{salt:
|
|
89
|
+
? new JB721CheckpointsDeployer{salt: _CHECKPOINTS_DEPLOYER_SALT}(store)
|
|
96
90
|
: JB721CheckpointsDeployer(_deployer);
|
|
97
91
|
}
|
|
98
92
|
|
|
@@ -100,7 +94,7 @@ contract DeployScript is Script, Sphinx {
|
|
|
100
94
|
{
|
|
101
95
|
// Perform the check for the registry.
|
|
102
96
|
(address _hook, bool _hookIsDeployed) = _isDeployed({
|
|
103
|
-
salt:
|
|
97
|
+
salt: _HOOK_SALT,
|
|
104
98
|
creationCode: type(JB721TiersHook).creationCode,
|
|
105
99
|
arguments: abi.encode(
|
|
106
100
|
core.directory,
|
|
@@ -110,22 +104,22 @@ contract DeployScript is Script, Sphinx {
|
|
|
110
104
|
store,
|
|
111
105
|
core.splits,
|
|
112
106
|
checkpointsDeployer,
|
|
113
|
-
|
|
107
|
+
trustedForwarder
|
|
114
108
|
)
|
|
115
109
|
});
|
|
116
110
|
|
|
117
111
|
// Deploy it if it has not been deployed yet.
|
|
118
112
|
hook = !_hookIsDeployed
|
|
119
|
-
? new JB721TiersHook{salt:
|
|
120
|
-
core.directory,
|
|
121
|
-
core.permissions,
|
|
122
|
-
core.prices,
|
|
123
|
-
core.rulesets,
|
|
124
|
-
store,
|
|
125
|
-
core.splits,
|
|
126
|
-
IJB721CheckpointsDeployer(address(checkpointsDeployer)),
|
|
127
|
-
|
|
128
|
-
)
|
|
113
|
+
? new JB721TiersHook{salt: _HOOK_SALT}({
|
|
114
|
+
directory: core.directory,
|
|
115
|
+
permissions: core.permissions,
|
|
116
|
+
prices: core.prices,
|
|
117
|
+
rulesets: core.rulesets,
|
|
118
|
+
store: store,
|
|
119
|
+
splits: core.splits,
|
|
120
|
+
checkpointsDeployer: IJB721CheckpointsDeployer(address(checkpointsDeployer)),
|
|
121
|
+
trustedForwarder: trustedForwarder
|
|
122
|
+
})
|
|
129
123
|
: JB721TiersHook(_hook);
|
|
130
124
|
}
|
|
131
125
|
|
|
@@ -133,15 +127,15 @@ contract DeployScript is Script, Sphinx {
|
|
|
133
127
|
{
|
|
134
128
|
// Perform the check for the registry.
|
|
135
129
|
(address _hookDeployer, bool _hookDeployerIsDeployed) = _isDeployed({
|
|
136
|
-
salt:
|
|
130
|
+
salt: _HOOK_DEPLOYER_SALT,
|
|
137
131
|
creationCode: type(JB721TiersHookDeployer).creationCode,
|
|
138
|
-
arguments: abi.encode(hook, store, registry.registry,
|
|
132
|
+
arguments: abi.encode(hook, store, registry.registry, trustedForwarder)
|
|
139
133
|
});
|
|
140
134
|
|
|
141
135
|
hookDeployer = !_hookDeployerIsDeployed
|
|
142
|
-
? new JB721TiersHookDeployer{salt:
|
|
143
|
-
hook, store, registry.registry,
|
|
144
|
-
)
|
|
136
|
+
? new JB721TiersHookDeployer{salt: _HOOK_DEPLOYER_SALT}({
|
|
137
|
+
hook: hook, store: store, addressRegistry: registry.registry, trustedForwarder: trustedForwarder
|
|
138
|
+
})
|
|
145
139
|
: JB721TiersHookDeployer(_hookDeployer);
|
|
146
140
|
}
|
|
147
141
|
|
|
@@ -149,15 +143,18 @@ contract DeployScript is Script, Sphinx {
|
|
|
149
143
|
{
|
|
150
144
|
// Perform the check for the registry.
|
|
151
145
|
(address _projectDeployer, bool _projectDeployerIsdeployed) = _isDeployed({
|
|
152
|
-
salt:
|
|
146
|
+
salt: _PROJECT_DEPLOYER_SALT,
|
|
153
147
|
creationCode: type(JB721TiersHookProjectDeployer).creationCode,
|
|
154
|
-
arguments: abi.encode(core.directory, core.permissions, hookDeployer,
|
|
148
|
+
arguments: abi.encode(core.directory, core.permissions, hookDeployer, trustedForwarder)
|
|
155
149
|
});
|
|
156
150
|
|
|
157
151
|
projectDeployer = !_projectDeployerIsdeployed
|
|
158
|
-
? new JB721TiersHookProjectDeployer{salt:
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
? new JB721TiersHookProjectDeployer{salt: _PROJECT_DEPLOYER_SALT}({
|
|
153
|
+
directory: core.directory,
|
|
154
|
+
permissions: core.permissions,
|
|
155
|
+
hookDeployer: hookDeployer,
|
|
156
|
+
trustedForwarder: trustedForwarder
|
|
157
|
+
})
|
|
161
158
|
: JB721TiersHookProjectDeployer(_projectDeployer);
|
|
162
159
|
}
|
|
163
160
|
}
|
|
@@ -11,10 +11,8 @@ import {IJB721TiersHookProjectDeployer} from "../../src/interfaces/IJB721TiersHo
|
|
|
11
11
|
import {IJB721TiersHookStore} from "../../src/interfaces/IJB721TiersHookStore.sol";
|
|
12
12
|
|
|
13
13
|
struct Hook721Deployment {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
17
|
-
IJB721TiersHookProjectDeployer project_deployer;
|
|
14
|
+
IJB721TiersHookDeployer hookDeployer;
|
|
15
|
+
IJB721TiersHookProjectDeployer projectDeployer;
|
|
18
16
|
IJB721TiersHookStore store;
|
|
19
17
|
}
|
|
20
18
|
|
|
@@ -25,17 +23,16 @@ library Hook721DeploymentLib {
|
|
|
25
23
|
Vm internal constant vm = Vm(VM_ADDRESS);
|
|
26
24
|
|
|
27
25
|
function getDeployment(string memory path) internal returns (Hook721Deployment memory deployment) {
|
|
28
|
-
//
|
|
26
|
+
// Match the current chain ID to the Sphinx network name used in deployment artifacts.
|
|
29
27
|
uint256 chainId = block.chainid;
|
|
30
28
|
|
|
31
|
-
//
|
|
32
|
-
// TODO: get constants without deploy.
|
|
29
|
+
// `SphinxConstants` exposes Sphinx's supported chain ID to network name mapping.
|
|
33
30
|
SphinxConstants sphinxConstants = new SphinxConstants();
|
|
34
31
|
NetworkInfo[] memory networks = sphinxConstants.getNetworkInfoArray();
|
|
35
32
|
|
|
36
33
|
for (uint256 _i; _i < networks.length; _i++) {
|
|
37
34
|
if (networks[_i].chainId == chainId) {
|
|
38
|
-
return getDeployment({path: path,
|
|
35
|
+
return getDeployment({path: path, networkName: networks[_i].name});
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
38
|
|
|
@@ -44,27 +41,26 @@ library Hook721DeploymentLib {
|
|
|
44
41
|
|
|
45
42
|
function getDeployment(
|
|
46
43
|
string memory path,
|
|
47
|
-
|
|
48
|
-
string memory network_name
|
|
44
|
+
string memory networkName
|
|
49
45
|
)
|
|
50
46
|
internal
|
|
51
47
|
view
|
|
52
48
|
returns (Hook721Deployment memory deployment)
|
|
53
49
|
{
|
|
54
|
-
deployment.
|
|
50
|
+
deployment.hookDeployer = IJB721TiersHookDeployer(
|
|
55
51
|
_getDeploymentAddress({
|
|
56
52
|
path: path,
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
projectName: "nana-721-hook-v6",
|
|
54
|
+
networkName: networkName,
|
|
59
55
|
contractName: "JB721TiersHookDeployer"
|
|
60
56
|
})
|
|
61
57
|
);
|
|
62
58
|
|
|
63
|
-
deployment.
|
|
59
|
+
deployment.projectDeployer = IJB721TiersHookProjectDeployer(
|
|
64
60
|
_getDeploymentAddress({
|
|
65
61
|
path: path,
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
projectName: "nana-721-hook-v6",
|
|
63
|
+
networkName: networkName,
|
|
68
64
|
contractName: "JB721TiersHookProjectDeployer"
|
|
69
65
|
})
|
|
70
66
|
);
|
|
@@ -72,8 +68,8 @@ library Hook721DeploymentLib {
|
|
|
72
68
|
deployment.store = IJB721TiersHookStore(
|
|
73
69
|
_getDeploymentAddress({
|
|
74
70
|
path: path,
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
projectName: "nana-721-hook-v6",
|
|
72
|
+
networkName: networkName,
|
|
77
73
|
contractName: "JB721TiersHookStore"
|
|
78
74
|
})
|
|
79
75
|
);
|
|
@@ -86,10 +82,8 @@ library Hook721DeploymentLib {
|
|
|
86
82
|
/// @return The address of the contract.
|
|
87
83
|
function _getDeploymentAddress(
|
|
88
84
|
string memory path,
|
|
89
|
-
|
|
90
|
-
string memory
|
|
91
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
92
|
-
string memory network_name,
|
|
85
|
+
string memory projectName,
|
|
86
|
+
string memory networkName,
|
|
93
87
|
string memory contractName
|
|
94
88
|
)
|
|
95
89
|
internal
|
|
@@ -98,7 +92,7 @@ library Hook721DeploymentLib {
|
|
|
98
92
|
{
|
|
99
93
|
string memory deploymentJson =
|
|
100
94
|
// forge-lint: disable-next-line(unsafe-cheatcode)
|
|
101
|
-
vm.readFile(string.concat(path,
|
|
102
|
-
return stdJson.readAddress(deploymentJson, ".address");
|
|
95
|
+
vm.readFile(string.concat(path, projectName, "/", networkName, "/", contractName, ".json"));
|
|
96
|
+
return stdJson.readAddress({json: deploymentJson, key: ".address"});
|
|
103
97
|
}
|
|
104
98
|
}
|
package/src/JB721Checkpoints.sol
CHANGED
|
@@ -40,7 +40,7 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
40
40
|
//*********************************************************************//
|
|
41
41
|
|
|
42
42
|
/// @notice The hook that this module tracks voting power for.
|
|
43
|
-
address public override
|
|
43
|
+
address public override hook;
|
|
44
44
|
|
|
45
45
|
//*********************************************************************//
|
|
46
46
|
// -------------------- internal stored properties ------------------- //
|
|
@@ -59,7 +59,7 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
59
59
|
/// @param store The store that holds tier data for each hook's NFTs.
|
|
60
60
|
constructor(IJB721TiersHookStore store) EIP712("JB721Checkpoints", "1") {
|
|
61
61
|
STORE = store;
|
|
62
|
-
|
|
62
|
+
hook = address(1);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
//*********************************************************************//
|
|
@@ -81,7 +81,7 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
81
81
|
uint256 tokenId = tokenIds[i];
|
|
82
82
|
|
|
83
83
|
// Only the current owner can enroll their tokens.
|
|
84
|
-
if (IERC721(
|
|
84
|
+
if (IERC721(hook).ownerOf(tokenId) != msg.sender) {
|
|
85
85
|
revert JB721Checkpoints_NotOwner({tokenId: tokenId, caller: msg.sender});
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -99,20 +99,20 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
99
99
|
|
|
100
100
|
/// @notice Initializes a cloned module with its hook reference.
|
|
101
101
|
/// @dev Can only be called once. Called by the deployer after cloning.
|
|
102
|
-
/// @param
|
|
103
|
-
function initialize(address
|
|
104
|
-
if (
|
|
102
|
+
/// @param hookAddress The hook this module serves.
|
|
103
|
+
function initialize(address hookAddress) external override {
|
|
104
|
+
if (hook != address(0)) revert JB721Checkpoints_AlreadyInitialized({hook: hook});
|
|
105
105
|
// `hook` cannot be zero when called through the deployer because `msg.sender` must equal `hook`.
|
|
106
|
-
|
|
106
|
+
hook = hookAddress;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/// @notice Called by the hook after every NFT transfer to update checkpointed voting power.
|
|
110
|
-
/// @dev Only callable by the
|
|
110
|
+
/// @dev Only callable by the hook. Looks up the token's tier voting units from the store.
|
|
111
111
|
/// @param from The previous owner (address(0) on mint).
|
|
112
112
|
/// @param to The new owner (address(0) on burn).
|
|
113
113
|
/// @param tokenId The token ID to transfer.
|
|
114
114
|
function onTransfer(address from, address to, uint256 tokenId) external override {
|
|
115
|
-
if (msg.sender !=
|
|
115
|
+
if (msg.sender != hook) revert JB721Checkpoints_Unauthorized({caller: msg.sender, hook: hook});
|
|
116
116
|
|
|
117
117
|
if (from != address(0)) {
|
|
118
118
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
@@ -120,7 +120,7 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// Look up this token's tier to get its voting units.
|
|
123
|
-
uint256 votingUnits = STORE.tierOfTokenId({hook:
|
|
123
|
+
uint256 votingUnits = STORE.tierOfTokenId({hook: hook, tokenId: tokenId, includeResolvedUri: false}).votingUnits;
|
|
124
124
|
|
|
125
125
|
// Move checkpointed voting power from the previous owner to the new owner.
|
|
126
126
|
_transferVotingUnits({from: from, to: to, amount: votingUnits});
|
|
@@ -161,6 +161,6 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
161
161
|
/// @param account The address to get the voting units of.
|
|
162
162
|
/// @return The total voting units the account holds.
|
|
163
163
|
function _getVotingUnits(address account) internal view override returns (uint256) {
|
|
164
|
-
return STORE.votingUnitsOf({hook:
|
|
164
|
+
return STORE.votingUnitsOf({hook: hook, account: account});
|
|
165
165
|
}
|
|
166
166
|
}
|
package/src/JB721TiersHook.sol
CHANGED
|
@@ -107,7 +107,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
107
107
|
|
|
108
108
|
/// @notice The checkpoint module that manages IVotes-compatible checkpointed voting power for this hook's NFTs.
|
|
109
109
|
/// @dev Lazily deployed on the first transfer. Pass this to JBTokenDistributor as the IVotes token.
|
|
110
|
-
IJB721Checkpoints public override
|
|
110
|
+
IJB721Checkpoints public override checkpoints;
|
|
111
111
|
|
|
112
112
|
//*********************************************************************//
|
|
113
113
|
// -------------------------- constructor ---------------------------- //
|
|
@@ -232,9 +232,9 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
232
232
|
/// @notice Initialize a cloned copy of the hook. Sets the project association, ERC-721 name/symbol, pricing
|
|
233
233
|
/// context (currency + decimals), metadata URIs, initial tiers, and behavioral flags. Can only be called once
|
|
234
234
|
/// per clone — the implementation contract is pre-initialized in its constructor to prevent misuse.
|
|
235
|
-
/// @dev Called by `JB721TiersHookDeployer` immediately after cloning. Reverts
|
|
236
|
-
///
|
|
237
|
-
/// @param
|
|
235
|
+
/// @dev Called by `JB721TiersHookDeployer` immediately after cloning. Reverts if called more than once or if the
|
|
236
|
+
/// project ID is zero.
|
|
237
|
+
/// @param initialProjectId The ID of the project this hook is associated with.
|
|
238
238
|
/// @param name The name of the NFT collection.
|
|
239
239
|
/// @param symbol The symbol representing the NFT collection.
|
|
240
240
|
/// @param baseUri The URI to use as a base for full NFT `tokenUri`s.
|
|
@@ -244,7 +244,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
244
244
|
/// category (from least to greatest).
|
|
245
245
|
/// @param flags A set of additional options which dictate how the hook behaves.
|
|
246
246
|
function initialize(
|
|
247
|
-
uint256
|
|
247
|
+
uint256 initialProjectId,
|
|
248
248
|
string memory name,
|
|
249
249
|
string memory symbol,
|
|
250
250
|
string memory baseUri,
|
|
@@ -258,14 +258,14 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
258
258
|
{
|
|
259
259
|
// Stop re-initialization. This protects both the implementation contract (initialized in constructor)
|
|
260
260
|
// and clones (initialized via this function).
|
|
261
|
-
if (_initialized) revert JB721TiersHook_AlreadyInitialized(
|
|
261
|
+
if (_initialized) revert JB721TiersHook_AlreadyInitialized({projectId: projectId});
|
|
262
262
|
_initialized = true;
|
|
263
263
|
|
|
264
|
-
// Make sure a
|
|
265
|
-
if (
|
|
264
|
+
// Make sure a project ID is provided.
|
|
265
|
+
if (initialProjectId == 0) revert JB721TiersHook_NoProjectId({projectId: initialProjectId});
|
|
266
266
|
|
|
267
267
|
// Initialize the superclass.
|
|
268
|
-
JB721Hook._initialize({
|
|
268
|
+
JB721Hook._initialize({initialProjectId: initialProjectId, name: name, symbol: symbol});
|
|
269
269
|
|
|
270
270
|
// Validate pricing decimals are within a reasonable range.
|
|
271
271
|
if (tiersConfig.decimals > 18) revert JB721TiersHook_InvalidPricingDecimals(tiersConfig.decimals);
|
|
@@ -326,7 +326,8 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
326
326
|
/// @return The token URI from the `tokenUriResolver` if it is set. If it isn't set, the token URI for the NFT's
|
|
327
327
|
/// tier.
|
|
328
328
|
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
|
|
329
|
-
return
|
|
329
|
+
return
|
|
330
|
+
JB721TiersHookLib.resolveTokenURI({store: STORE, hook: address(this), baseUri: baseURI, tokenId: tokenId});
|
|
330
331
|
}
|
|
331
332
|
|
|
332
333
|
/// @notice The total cash-out weight across all outstanding NFTs and pending reserves. This is the denominator
|
|
@@ -348,15 +349,13 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
348
349
|
/// @param tierIdsToRemove The IDs of the tiers to remove.
|
|
349
350
|
function adjustTiers(JB721TierConfig[] calldata tiersToAdd, uint256[] calldata tierIdsToRemove) external override {
|
|
350
351
|
// Enforce permissions.
|
|
351
|
-
_requirePermissionFrom({
|
|
352
|
-
account: owner(), projectId: PROJECT_ID, permissionId: JBPermissionIds.ADJUST_721_TIERS
|
|
353
|
-
});
|
|
352
|
+
_requirePermissionFrom({account: owner(), projectId: projectId, permissionId: JBPermissionIds.ADJUST_721_TIERS});
|
|
354
353
|
|
|
355
354
|
// Delegate to the library (via DELEGATECALL) for tier removal, addition, event emission, and split setting.
|
|
356
355
|
JB721TiersHookLib.adjustTiersFor({
|
|
357
356
|
store: STORE,
|
|
358
357
|
splits: SPLITS,
|
|
359
|
-
projectId:
|
|
358
|
+
projectId: projectId,
|
|
360
359
|
hookAddress: address(this),
|
|
361
360
|
caller: _msgSender(),
|
|
362
361
|
tiersToAdd: tiersToAdd,
|
|
@@ -379,7 +378,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
379
378
|
returns (uint256[] memory tokenIds)
|
|
380
379
|
{
|
|
381
380
|
// Enforce permissions.
|
|
382
|
-
_requirePermissionFrom({account: owner(), projectId:
|
|
381
|
+
_requirePermissionFrom({account: owner(), projectId: projectId, permissionId: JBPermissionIds.MINT_721});
|
|
383
382
|
|
|
384
383
|
// Record the mint. The token IDs returned correspond to the tiers passed in.
|
|
385
384
|
(tokenIds,,) = STORE.recordMint({
|
|
@@ -417,7 +416,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
417
416
|
function setDiscountPercentOf(uint256 tierId, uint256 discountPercent) external override {
|
|
418
417
|
// Enforce permissions.
|
|
419
418
|
_requirePermissionFrom({
|
|
420
|
-
account: owner(), projectId:
|
|
419
|
+
account: owner(), projectId: projectId, permissionId: JBPermissionIds.SET_721_DISCOUNT_PERCENT
|
|
421
420
|
});
|
|
422
421
|
_setDiscountPercentOf({tierId: tierId, discountPercent: discountPercent});
|
|
423
422
|
}
|
|
@@ -428,7 +427,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
428
427
|
function setDiscountPercentsOf(JB721TiersSetDiscountPercentConfig[] calldata configs) external override {
|
|
429
428
|
// Enforce permissions.
|
|
430
429
|
_requirePermissionFrom({
|
|
431
|
-
account: owner(), projectId:
|
|
430
|
+
account: owner(), projectId: projectId, permissionId: JBPermissionIds.SET_721_DISCOUNT_PERCENT
|
|
432
431
|
});
|
|
433
432
|
|
|
434
433
|
for (uint256 i; i < configs.length;) {
|
|
@@ -453,24 +452,22 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
453
452
|
/// @param tokenUriResolver The new URI resolver. Pass `IJB721TokenUriResolver(address(this))` as a sentinel value
|
|
454
453
|
/// to leave unchanged. `address(this)` is used instead of `address(0)` because `address(0)` is a valid value that
|
|
455
454
|
/// clears the resolver.
|
|
456
|
-
/// @param
|
|
457
|
-
/// @param
|
|
455
|
+
/// @param encodedIpfsUriTierId The ID of the tier to set the encoded IPFS URI of.
|
|
456
|
+
/// @param encodedIpfsUri The encoded IPFS URI to set.
|
|
458
457
|
function setMetadata(
|
|
459
458
|
string calldata name,
|
|
460
459
|
string calldata symbol,
|
|
461
460
|
string calldata baseUri,
|
|
462
461
|
string calldata contractUri,
|
|
463
462
|
IJB721TokenUriResolver tokenUriResolver,
|
|
464
|
-
uint256
|
|
465
|
-
bytes32
|
|
463
|
+
uint256 encodedIpfsUriTierId,
|
|
464
|
+
bytes32 encodedIpfsUri
|
|
466
465
|
)
|
|
467
466
|
external
|
|
468
467
|
override
|
|
469
468
|
{
|
|
470
469
|
// Enforce permissions.
|
|
471
|
-
_requirePermissionFrom({
|
|
472
|
-
account: owner(), projectId: PROJECT_ID, permissionId: JBPermissionIds.SET_721_METADATA
|
|
473
|
-
});
|
|
470
|
+
_requirePermissionFrom({account: owner(), projectId: projectId, permissionId: JBPermissionIds.SET_721_METADATA});
|
|
474
471
|
|
|
475
472
|
// Cache _msgSender() at function entry to avoid repeated calls.
|
|
476
473
|
address caller = _msgSender();
|
|
@@ -501,11 +498,11 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
501
498
|
// Store the new URI resolver.
|
|
502
499
|
_recordSetTokenUriResolver(tokenUriResolver);
|
|
503
500
|
}
|
|
504
|
-
if (
|
|
505
|
-
emit
|
|
501
|
+
if (encodedIpfsUriTierId != 0 && encodedIpfsUri != bytes32(0)) {
|
|
502
|
+
emit SetEncodedIpfsUri({tierId: encodedIpfsUriTierId, encodedUri: encodedIpfsUri, caller: caller});
|
|
506
503
|
|
|
507
504
|
// Store the new encoded IPFS URI.
|
|
508
|
-
STORE.
|
|
505
|
+
STORE.recordSetEncodedIpfsUriOf({tierId: encodedIpfsUriTierId, encodedIpfsUri: encodedIpfsUri});
|
|
509
506
|
}
|
|
510
507
|
}
|
|
511
508
|
|
|
@@ -519,12 +516,12 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
519
516
|
/// @param count The number of reserved NFTs to mint.
|
|
520
517
|
function mintPendingReservesFor(uint256 tierId, uint256 count) public override {
|
|
521
518
|
// Get a reference to the project's current ruleset.
|
|
522
|
-
JBRuleset memory ruleset = _currentRulesetOf(
|
|
519
|
+
JBRuleset memory ruleset = _currentRulesetOf(projectId);
|
|
523
520
|
|
|
524
521
|
// Pending reserve mints must not be paused.
|
|
525
522
|
if (JB721TiersRulesetMetadataResolver.mintPendingReservesPaused((JBRulesetMetadataResolver.metadata(ruleset))))
|
|
526
523
|
{
|
|
527
|
-
revert JB721TiersHook_MintReserveNftsPaused({projectId:
|
|
524
|
+
revert JB721TiersHook_MintReserveNftsPaused({projectId: projectId, tierId: tierId});
|
|
528
525
|
}
|
|
529
526
|
|
|
530
527
|
// Record the reserved mint for the tier.
|
|
@@ -694,7 +691,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
694
691
|
(value, valid) = JB721TiersHookLib.normalizePaymentValue({
|
|
695
692
|
packedPricingContext: _packedPricingContext,
|
|
696
693
|
prices: PRICES,
|
|
697
|
-
projectId:
|
|
694
|
+
projectId: projectId,
|
|
698
695
|
amountValue: context.amount.value,
|
|
699
696
|
amountCurrency: context.amount.currency,
|
|
700
697
|
amountDecimals: context.amount.decimals
|
|
@@ -722,7 +719,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
722
719
|
JB721TiersHookLib.distributeAll({
|
|
723
720
|
directory: DIRECTORY,
|
|
724
721
|
splits: SPLITS,
|
|
725
|
-
projectId:
|
|
722
|
+
projectId: projectId,
|
|
726
723
|
hookAddress: address(this),
|
|
727
724
|
token: context.forwardedAmount.token,
|
|
728
725
|
amount: context.forwardedAmount.value,
|
|
@@ -766,7 +763,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
766
763
|
// If transfers are pausable, check if they're paused.
|
|
767
764
|
if (transfersPausable) {
|
|
768
765
|
// Get a reference to the project's current ruleset.
|
|
769
|
-
JBRuleset memory ruleset = _currentRulesetOf(
|
|
766
|
+
JBRuleset memory ruleset = _currentRulesetOf(projectId);
|
|
770
767
|
|
|
771
768
|
// If transfers are paused and the NFT isn't being transferred to the zero address, revert.
|
|
772
769
|
if (
|
|
@@ -776,7 +773,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
776
773
|
)
|
|
777
774
|
) {
|
|
778
775
|
revert JB721TiersHook_TierTransfersPaused({
|
|
779
|
-
projectId:
|
|
776
|
+
projectId: projectId, tokenId: tokenId, from: from, to: to
|
|
780
777
|
});
|
|
781
778
|
}
|
|
782
779
|
}
|
|
@@ -789,11 +786,11 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
789
786
|
STORE.recordTransferForTier({tierId: tierId, from: from, to: to});
|
|
790
787
|
|
|
791
788
|
// Deploy the checkpoint module lazily on the first transfer.
|
|
792
|
-
if (address(
|
|
793
|
-
|
|
789
|
+
if (address(checkpoints) == address(0)) {
|
|
790
|
+
checkpoints = CHECKPOINTS_DEPLOYER.deploy(address(this));
|
|
794
791
|
}
|
|
795
792
|
|
|
796
793
|
// Notify the checkpoint module to update checkpointed voting power.
|
|
797
|
-
|
|
794
|
+
checkpoints.onTransfer({from: from, to: to, tokenId: tokenId});
|
|
798
795
|
}
|
|
799
796
|
}
|
|
@@ -89,7 +89,7 @@ contract JB721TiersHookDeployer is ERC2771Context, IJB721TiersHookDeployer {
|
|
|
89
89
|
emit HookDeployed({projectId: projectId, hook: newHook, caller: _msgSender()});
|
|
90
90
|
|
|
91
91
|
newHook.initialize({
|
|
92
|
-
|
|
92
|
+
initialProjectId: projectId,
|
|
93
93
|
name: deployTiersHookConfig.name,
|
|
94
94
|
symbol: deployTiersHookConfig.symbol,
|
|
95
95
|
baseUri: deployTiersHookConfig.baseUri,
|
|
@@ -89,8 +89,8 @@ contract JB721TiersHookProjectDeployer is
|
|
|
89
89
|
returns (uint256 projectId, IJB721TiersHook hook)
|
|
90
90
|
{
|
|
91
91
|
// Reserve the project ID up front so permissionless project creations cannot invalidate hook deployment.
|
|
92
|
-
IJBProjects
|
|
93
|
-
projectId =
|
|
92
|
+
IJBProjects projects = DIRECTORY.PROJECTS();
|
|
93
|
+
projectId = projects.createFor(address(this));
|
|
94
94
|
|
|
95
95
|
// Deploy the hook.
|
|
96
96
|
hook = HOOK_DEPLOYER.deployHookFor({
|
|
@@ -108,7 +108,7 @@ contract JB721TiersHookProjectDeployer is
|
|
|
108
108
|
JBOwnable(address(hook)).transferOwnershipToProject(projectId);
|
|
109
109
|
|
|
110
110
|
// Transfer the project NFT to its intended owner.
|
|
111
|
-
|
|
111
|
+
projects.safeTransferFrom({from: address(this), to: owner, tokenId: projectId});
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/// @notice Launches rulesets for a project with an attached 721 tiers hook.
|
|
@@ -135,8 +135,8 @@ contract JB721TiersHookProjectDeployer is
|
|
|
135
135
|
returns (uint256 rulesetId, IJB721TiersHook hook)
|
|
136
136
|
{
|
|
137
137
|
// Get the project's projects contract.
|
|
138
|
-
IJBProjects
|
|
139
|
-
address projectOwner =
|
|
138
|
+
IJBProjects projects = DIRECTORY.PROJECTS();
|
|
139
|
+
address projectOwner = projects.ownerOf(projectId);
|
|
140
140
|
|
|
141
141
|
// Enforce permissions.
|
|
142
142
|
_requirePermissionFrom({
|
|
@@ -71,7 +71,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
71
71
|
/// @custom:param hook The 721 contract that the tier belongs to.
|
|
72
72
|
/// @custom:param tierId The ID of the tier to get the encoded IPFS URI of.
|
|
73
73
|
/// @custom:returns The encoded IPFS URI.
|
|
74
|
-
mapping(address hook => mapping(uint256 tierId => bytes32)) public override
|
|
74
|
+
mapping(address hook => mapping(uint256 tierId => bytes32)) public override encodedIpfsUriOf;
|
|
75
75
|
|
|
76
76
|
/// @notice Returns the largest tier ID currently used on the provided 721 contract.
|
|
77
77
|
/// @dev This may not include the last tier ID if it has been removed.
|
|
@@ -166,7 +166,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
166
166
|
/// @return The encoded IPFS URI for the NFT's tier.
|
|
167
167
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
168
168
|
function encodedTierIPFSUriOf(address hook, uint256 tokenId) external view override returns (bytes32) {
|
|
169
|
-
return
|
|
169
|
+
return encodedIpfsUriOf[hook][tierIdOfToken(tokenId)];
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/// @notice Get the behavioral flags for a hook — such as whether transfers are pausable, whether NFT holders can
|
|
@@ -628,7 +628,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
628
628
|
// No reserve frequency if there is no reserve beneficiary.
|
|
629
629
|
reserveFrequency: reserveBeneficiary == address(0) ? 0 : storedTier.reserveFrequency,
|
|
630
630
|
reserveBeneficiary: reserveBeneficiary,
|
|
631
|
-
|
|
631
|
+
encodedIpfsUri: encodedIpfsUriOf[hook][tierId],
|
|
632
632
|
category: storedTier.category,
|
|
633
633
|
discountPercent: storedTier.discountPercent,
|
|
634
634
|
flags: JB721TierFlags({
|
|
@@ -1051,9 +1051,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1051
1051
|
}
|
|
1052
1052
|
}
|
|
1053
1053
|
|
|
1054
|
-
// Set the `
|
|
1055
|
-
if (tierToAdd.
|
|
1056
|
-
|
|
1054
|
+
// Set the `encodedIpfsUri` if needed.
|
|
1055
|
+
if (tierToAdd.encodedIpfsUri != bytes32(0)) {
|
|
1056
|
+
encodedIpfsUriOf[msg.sender][tierId] = tierToAdd.encodedIpfsUri;
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
1059
|
if (startSortedTierId != 0) {
|
|
@@ -1392,10 +1392,10 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1392
1392
|
|
|
1393
1393
|
/// @notice Record a new encoded IPFS URI for a tier.
|
|
1394
1394
|
/// @param tierId The ID of the tier to set the encoded IPFS URI of.
|
|
1395
|
-
/// @param
|
|
1395
|
+
/// @param encodedIpfsUri The encoded IPFS URI to set for the tier.
|
|
1396
1396
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
1397
|
-
function
|
|
1398
|
-
|
|
1397
|
+
function recordSetEncodedIpfsUriOf(uint256 tierId, bytes32 encodedIpfsUri) external override {
|
|
1398
|
+
encodedIpfsUriOf[msg.sender][tierId] = encodedIpfsUri;
|
|
1399
1399
|
}
|
|
1400
1400
|
|
|
1401
1401
|
/// @notice Record a newly set token URI resolver.
|
package/src/abstract/ERC721.sol
CHANGED
|
@@ -40,9 +40,9 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|
|
40
40
|
/**
|
|
41
41
|
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
|
|
42
42
|
*/
|
|
43
|
-
function _initialize(string memory
|
|
44
|
-
_name =
|
|
45
|
-
_symbol =
|
|
43
|
+
function _initialize(string memory collectionName, string memory collectionSymbol) internal {
|
|
44
|
+
_name = collectionName;
|
|
45
|
+
_symbol = collectionSymbol;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -50,7 +50,7 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
50
50
|
//*********************************************************************//
|
|
51
51
|
|
|
52
52
|
/// @notice The ID of the project that this contract is associated with.
|
|
53
|
-
uint256 public override
|
|
53
|
+
uint256 public override projectId;
|
|
54
54
|
|
|
55
55
|
//*********************************************************************//
|
|
56
56
|
// -------------------------- constructor ---------------------------- //
|
|
@@ -194,16 +194,16 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
194
194
|
override
|
|
195
195
|
{
|
|
196
196
|
// Keep a reference to the project ID.
|
|
197
|
-
uint256
|
|
197
|
+
uint256 localProjectId = projectId;
|
|
198
198
|
|
|
199
199
|
// Make sure the caller is a terminal of the project, and that the call is being made on behalf of an
|
|
200
200
|
// interaction with the correct project.
|
|
201
201
|
if (
|
|
202
|
-
msg.value != 0 || !DIRECTORY.isTerminalOf({projectId:
|
|
203
|
-
|| context.projectId !=
|
|
202
|
+
msg.value != 0 || !DIRECTORY.isTerminalOf({projectId: localProjectId, terminal: IJBTerminal(msg.sender)})
|
|
203
|
+
|| context.projectId != localProjectId
|
|
204
204
|
) {
|
|
205
205
|
revert JB721Hook_InvalidCashOut({
|
|
206
|
-
caller: msg.sender, contextProjectId: context.projectId, projectId:
|
|
206
|
+
caller: msg.sender, contextProjectId: context.projectId, projectId: localProjectId, msgValue: msg.value
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
|
|
@@ -243,15 +243,17 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
243
243
|
/// @dev Reverts if the calling contract is not one of the project's terminals.
|
|
244
244
|
/// @param context The payment context passed in by the terminal.
|
|
245
245
|
function afterPayRecordedWith(JBAfterPayRecordedContext calldata context) external payable virtual override {
|
|
246
|
-
uint256
|
|
246
|
+
uint256 localProjectId = projectId;
|
|
247
247
|
|
|
248
248
|
// Make sure the caller is a terminal of the project, and that the call is being made on behalf of an
|
|
249
249
|
// interaction with the correct project.
|
|
250
250
|
if (
|
|
251
|
-
!DIRECTORY.isTerminalOf({projectId:
|
|
252
|
-
|| context.projectId !=
|
|
251
|
+
!DIRECTORY.isTerminalOf({projectId: localProjectId, terminal: IJBTerminal(msg.sender)})
|
|
252
|
+
|| context.projectId != localProjectId
|
|
253
253
|
) {
|
|
254
|
-
revert JB721Hook_InvalidPay({
|
|
254
|
+
revert JB721Hook_InvalidPay({
|
|
255
|
+
caller: msg.sender, contextProjectId: context.projectId, projectId: localProjectId
|
|
256
|
+
});
|
|
255
257
|
}
|
|
256
258
|
|
|
257
259
|
// Process the payment.
|
|
@@ -267,12 +269,12 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
267
269
|
function _didBurn(uint256[] memory tokenIds) internal virtual;
|
|
268
270
|
|
|
269
271
|
/// @notice Initializes the contract by associating it with a project and adding ERC721 details.
|
|
270
|
-
/// @param
|
|
272
|
+
/// @param initialProjectId The ID of the project that this contract is associated with.
|
|
271
273
|
/// @param name The name of the NFT collection.
|
|
272
274
|
/// @param symbol The symbol representing the NFT collection.
|
|
273
|
-
function _initialize(uint256
|
|
274
|
-
ERC721._initialize({
|
|
275
|
-
|
|
275
|
+
function _initialize(uint256 initialProjectId, string memory name, string memory symbol) internal {
|
|
276
|
+
ERC721._initialize({collectionName: name, collectionSymbol: symbol});
|
|
277
|
+
projectId = initialProjectId;
|
|
276
278
|
}
|
|
277
279
|
|
|
278
280
|
/// @notice Process a received payment by minting NFTs and/or updating credits. Subclasses implement the
|
|
@@ -11,7 +11,7 @@ interface IJB721Checkpoints is IERC5805 {
|
|
|
11
11
|
/// @notice The hook that this module tracks voting power for.
|
|
12
12
|
/// @return The hook address.
|
|
13
13
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
14
|
-
function
|
|
14
|
+
function hook() external view returns (address);
|
|
15
15
|
|
|
16
16
|
/// @notice The owner of an NFT at a past block.
|
|
17
17
|
/// @dev Returns `address(0)` for tokens that have never been enrolled (via `delegate(address, uint256[])`) or
|
|
@@ -36,8 +36,8 @@ interface IJB721Checkpoints is IERC5805 {
|
|
|
36
36
|
|
|
37
37
|
/// @notice Initializes a cloned module with its hook reference.
|
|
38
38
|
/// @dev Can only be called once. Called by the deployer after cloning.
|
|
39
|
-
/// @param
|
|
40
|
-
function initialize(address
|
|
39
|
+
/// @param hookAddress The hook this module serves.
|
|
40
|
+
function initialize(address hookAddress) external;
|
|
41
41
|
|
|
42
42
|
/// @notice Called by the hook after every NFT transfer to update checkpointed voting power.
|
|
43
43
|
/// @dev Looks up the token's tier voting units from the store internally.
|
|
@@ -18,5 +18,5 @@ interface IJB721Hook is IJBRulesetDataHook, IJBPayHook, IJBCashOutHook {
|
|
|
18
18
|
|
|
19
19
|
/// @notice The ID of the project that this contract is associated with.
|
|
20
20
|
/// @return The project ID.
|
|
21
|
-
function
|
|
21
|
+
function projectId() external view returns (uint256);
|
|
22
22
|
}
|
|
@@ -89,7 +89,7 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
89
89
|
/// @param tierId The ID of the tier whose encoded IPFS URI was set.
|
|
90
90
|
/// @param encodedUri The new encoded IPFS URI.
|
|
91
91
|
/// @param caller The address that called the function.
|
|
92
|
-
event
|
|
92
|
+
event SetEncodedIpfsUri(uint256 indexed tierId, bytes32 encodedUri, address caller);
|
|
93
93
|
|
|
94
94
|
/// @notice Emitted when the token URI resolver is set.
|
|
95
95
|
/// @param resolver The new token URI resolver.
|
|
@@ -140,8 +140,7 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
140
140
|
/// @notice The checkpoint module that manages IVotes-compatible checkpointed voting power for this hook's NFTs.
|
|
141
141
|
/// @dev Deployed lazily on first mint. Pass this to JBTokenDistributor as the IVotes token.
|
|
142
142
|
/// @return The checkpoint module.
|
|
143
|
-
|
|
144
|
-
function CHECKPOINTS() external view returns (IJB721Checkpoints);
|
|
143
|
+
function checkpoints() external view returns (IJB721Checkpoints);
|
|
145
144
|
|
|
146
145
|
/// @notice The contract that exposes price feeds for currency conversions.
|
|
147
146
|
/// @return The prices contract.
|
|
@@ -165,7 +164,7 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
165
164
|
function adjustTiers(JB721TierConfig[] calldata tiersToAdd, uint256[] calldata tierIdsToRemove) external;
|
|
166
165
|
|
|
167
166
|
/// @notice Initializes a cloned copy of the original `JB721TiersHook` contract.
|
|
168
|
-
/// @param
|
|
167
|
+
/// @param initialProjectId The ID of the project this hook is associated with.
|
|
169
168
|
/// @param name The name of the NFT collection.
|
|
170
169
|
/// @param symbol The symbol representing the NFT collection.
|
|
171
170
|
/// @param baseUri The URI to use as a base for full NFT `tokenUri`s.
|
|
@@ -174,7 +173,7 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
174
173
|
/// @param tiersConfig The NFT tiers and pricing context to initialize the hook with.
|
|
175
174
|
/// @param flags A set of additional options which dictate how the hook behaves.
|
|
176
175
|
function initialize(
|
|
177
|
-
uint256
|
|
176
|
+
uint256 initialProjectId,
|
|
178
177
|
string memory name,
|
|
179
178
|
string memory symbol,
|
|
180
179
|
string memory baseUri,
|
|
@@ -217,16 +216,16 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
217
216
|
/// @param tokenUriResolver The new URI resolver. Pass `IJB721TokenUriResolver(address(this))` as a sentinel value
|
|
218
217
|
/// to leave unchanged. `address(this)` is used instead of `address(0)` because `address(0)` is a valid value that
|
|
219
218
|
/// clears the resolver.
|
|
220
|
-
/// @param
|
|
221
|
-
/// @param
|
|
219
|
+
/// @param encodedIpfsUriTierId The ID of the tier to set the encoded IPFS URI of.
|
|
220
|
+
/// @param encodedIpfsUri The encoded IPFS URI to set.
|
|
222
221
|
function setMetadata(
|
|
223
222
|
string calldata name,
|
|
224
223
|
string calldata symbol,
|
|
225
224
|
string calldata baseUri,
|
|
226
225
|
string calldata contractUri,
|
|
227
226
|
IJB721TokenUriResolver tokenUriResolver,
|
|
228
|
-
uint256
|
|
229
|
-
bytes32
|
|
227
|
+
uint256 encodedIpfsUriTierId,
|
|
228
|
+
bytes32 encodedIpfsUri
|
|
230
229
|
)
|
|
231
230
|
external;
|
|
232
231
|
}
|
|
@@ -42,7 +42,7 @@ interface IJB721TiersHookStore {
|
|
|
42
42
|
/// @param tierId The ID of the tier to get the encoded IPFS URI of.
|
|
43
43
|
/// @return The encoded IPFS URI.
|
|
44
44
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
45
|
-
function
|
|
45
|
+
function encodedIpfsUriOf(address hook, uint256 tierId) external view returns (bytes32);
|
|
46
46
|
|
|
47
47
|
/// @notice The encoded IPFS URI for the tier of the 721 with the provided token ID.
|
|
48
48
|
/// @param hook The 721 contract that the encoded IPFS URI belongs to.
|
|
@@ -246,9 +246,9 @@ interface IJB721TiersHookStore {
|
|
|
246
246
|
|
|
247
247
|
/// @notice Record a new encoded IPFS URI for a tier.
|
|
248
248
|
/// @param tierId The ID of the tier to set the encoded IPFS URI of.
|
|
249
|
-
/// @param
|
|
249
|
+
/// @param encodedIpfsUri The encoded IPFS URI to set for the tier.
|
|
250
250
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
251
|
-
function
|
|
251
|
+
function recordSetEncodedIpfsUriOf(uint256 tierId, bytes32 encodedIpfsUri) external;
|
|
252
252
|
|
|
253
253
|
/// @notice Record a newly set token URI resolver.
|
|
254
254
|
/// @param resolver The resolver to set.
|
|
@@ -360,7 +360,7 @@ library JB721TiersHookLib {
|
|
|
360
360
|
// attributable to tier splits. Downstream compositors (e.g. JBOmnichainDeployer) use this
|
|
361
361
|
// to preserve split credit when an extra hook (buyback) returns weight=0.
|
|
362
362
|
if (totalSplitAmount != 0 && context.amount.value != 0 && store.flagsOf(address(this)).issueTokensForSplits) {
|
|
363
|
-
splitCreditWeight = mulDiv(context.weight, totalSplitAmount, context.amount.value);
|
|
363
|
+
splitCreditWeight = mulDiv({x: context.weight, y: totalSplitAmount, denominator: context.amount.value});
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
// Resolve the effective beneficiary from payment metadata.
|
|
@@ -690,6 +690,149 @@ library JB721TiersHookLib {
|
|
|
690
690
|
// ----------------------- private helpers --------------------------- //
|
|
691
691
|
//*********************************************************************//
|
|
692
692
|
|
|
693
|
+
/// @notice Approves `terminal` to spend `amount` of `token`, calls `addToBalanceOf`, then revokes any
|
|
694
|
+
/// unconsumed allowance and reports the actual amount consumed via the allowance delta.
|
|
695
|
+
/// @dev Allowance-delta consumption protects against terminals that return successfully without pulling
|
|
696
|
+
/// the full granted amount, and the unconditional final `forceApprove(..., 0)` ensures no stale allowance
|
|
697
|
+
/// is left behind on either the success or failure path.
|
|
698
|
+
/// @param terminal The terminal to call.
|
|
699
|
+
/// @param token The ERC-20 token. Native must not be passed here.
|
|
700
|
+
/// @param projectId The destination project ID.
|
|
701
|
+
/// @param amount The amount to grant as allowance and request the terminal to pull.
|
|
702
|
+
/// @return consumed The amount actually consumed by the terminal (0 on revert; otherwise
|
|
703
|
+
/// `amount - remainingAllowance` after the call).
|
|
704
|
+
/// @return failureReason The revert reason from the terminal call, or empty bytes if the call succeeded.
|
|
705
|
+
function _approveAndAddToBalance(
|
|
706
|
+
IJBTerminal terminal,
|
|
707
|
+
address token,
|
|
708
|
+
uint256 projectId,
|
|
709
|
+
uint256 amount
|
|
710
|
+
)
|
|
711
|
+
private
|
|
712
|
+
returns (uint256 consumed, bytes memory failureReason)
|
|
713
|
+
{
|
|
714
|
+
// Grant the terminal allowance to pull up to `amount` of `token` from this contract. `forceApprove`
|
|
715
|
+
// sets the allowance to exactly `amount` regardless of any prior allowance, so a leftover non-zero
|
|
716
|
+
// allowance from a previous failed call cannot inflate this one.
|
|
717
|
+
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: amount});
|
|
718
|
+
|
|
719
|
+
// Track whether the external call returned successfully. Initialized to `false` so a revert path
|
|
720
|
+
// leaves it unset and the consumption math below is skipped.
|
|
721
|
+
bool succeeded;
|
|
722
|
+
|
|
723
|
+
// Call the terminal's `addToBalanceOf` inside try/catch so a reverting terminal can't brick the
|
|
724
|
+
// surrounding payment. The terminal is expected to `transferFrom` against the allowance set above.
|
|
725
|
+
try terminal.addToBalanceOf({
|
|
726
|
+
projectId: projectId,
|
|
727
|
+
token: token,
|
|
728
|
+
amount: amount,
|
|
729
|
+
// Don't release any held fees on this call — fee returns are handled separately.
|
|
730
|
+
shouldReturnHeldFees: false,
|
|
731
|
+
// No memo or metadata is forwarded; the leftover routing is opaque to off-chain consumers.
|
|
732
|
+
memo: "",
|
|
733
|
+
metadata: bytes("")
|
|
734
|
+
}) {
|
|
735
|
+
// The terminal returned without reverting. The actual amount it consumed is measured below
|
|
736
|
+
// via the allowance delta — `succeeded` only records that there was no revert to report.
|
|
737
|
+
succeeded = true;
|
|
738
|
+
} catch (bytes memory reason) {
|
|
739
|
+
// The terminal reverted. Capture the reason so the caller can surface it via the
|
|
740
|
+
// `SplitPayoutReverted` event (or, for the leftover fallback, the
|
|
741
|
+
// `JB721TiersHookLib_SplitFallbackFailed` error) without re-throwing here.
|
|
742
|
+
failureReason = reason;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// On success, compute how much the terminal actually pulled. The post-call allowance equals
|
|
746
|
+
// `amount - actuallyConsumed` (since `forceApprove` set the pre-call allowance to `amount`), so
|
|
747
|
+
// `consumed = amount - postAllowance`. On failure, `succeeded` is `false` and `consumed` stays at
|
|
748
|
+
// its default `0`, which lets the caller route the full `amount` to the project's leftover balance.
|
|
749
|
+
if (succeeded) {
|
|
750
|
+
// Safe to use `unchecked`: `postAllowance` cannot exceed `amount` because the terminal can
|
|
751
|
+
// only decrement (via `transferFrom`) the allowance we just set to `amount`.
|
|
752
|
+
unchecked {
|
|
753
|
+
consumed = amount - IERC20(token).allowance({owner: address(this), spender: address(terminal)});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Unconditionally revoke any remaining allowance. This is the load-bearing safety step — without
|
|
758
|
+
// it, a terminal that returns successfully without pulling the full `amount` would leave a stale
|
|
759
|
+
// non-zero allowance that the terminal could drain later. Running on both the success and failure
|
|
760
|
+
// paths means no allowance ever leaks out of this function.
|
|
761
|
+
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: 0});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/// @notice Approves `terminal` to spend `amount` of `token`, calls `pay`, then revokes any unconsumed
|
|
765
|
+
/// allowance and reports the actual amount consumed via the allowance delta.
|
|
766
|
+
/// @dev Mirrors `_approveAndAddToBalance` for the `pay` entry point.
|
|
767
|
+
/// @param terminal The terminal to call.
|
|
768
|
+
/// @param token The ERC-20 token. Native must not be passed here.
|
|
769
|
+
/// @param projectId The destination project ID.
|
|
770
|
+
/// @param amount The amount to grant as allowance and request the terminal to pull.
|
|
771
|
+
/// @param beneficiary The beneficiary for the `pay` call.
|
|
772
|
+
/// @return consumed The amount actually consumed by the terminal.
|
|
773
|
+
/// @return failureReason The revert reason from the terminal call, or empty bytes on success.
|
|
774
|
+
function _approveAndPay(
|
|
775
|
+
IJBTerminal terminal,
|
|
776
|
+
address token,
|
|
777
|
+
uint256 projectId,
|
|
778
|
+
uint256 amount,
|
|
779
|
+
address payable beneficiary
|
|
780
|
+
)
|
|
781
|
+
private
|
|
782
|
+
returns (uint256 consumed, bytes memory failureReason)
|
|
783
|
+
{
|
|
784
|
+
// Grant the terminal allowance to pull up to `amount` of `token` from this contract. `forceApprove`
|
|
785
|
+
// sets the allowance to exactly `amount` regardless of any prior allowance, so a leftover non-zero
|
|
786
|
+
// allowance from a previous failed call cannot inflate this one.
|
|
787
|
+
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: amount});
|
|
788
|
+
|
|
789
|
+
// Track whether the external call returned successfully. Initialized to `false` so a revert path
|
|
790
|
+
// leaves it unset and the consumption math below is skipped.
|
|
791
|
+
bool succeeded;
|
|
792
|
+
|
|
793
|
+
// Call the terminal's `pay` inside try/catch so a reverting terminal can't brick the surrounding
|
|
794
|
+
// payment. The terminal is expected to `transferFrom` against the allowance set above and mint
|
|
795
|
+
// project tokens to `beneficiary` in return.
|
|
796
|
+
try terminal.pay({
|
|
797
|
+
projectId: projectId,
|
|
798
|
+
token: token,
|
|
799
|
+
amount: amount,
|
|
800
|
+
beneficiary: beneficiary,
|
|
801
|
+
// No minimum is enforced on the terminal-side mint — the split-level minimum (if any) is
|
|
802
|
+
// enforced by the caller before reaching this helper.
|
|
803
|
+
minReturnedTokens: 0,
|
|
804
|
+
// No memo or metadata is forwarded; the leftover routing is opaque to off-chain consumers.
|
|
805
|
+
memo: "",
|
|
806
|
+
metadata: bytes("")
|
|
807
|
+
}) {
|
|
808
|
+
// The terminal returned without reverting. The actual amount it consumed is measured below
|
|
809
|
+
// via the allowance delta — `succeeded` only records that there was no revert to report.
|
|
810
|
+
succeeded = true;
|
|
811
|
+
} catch (bytes memory reason) {
|
|
812
|
+
// The terminal reverted. Capture the reason so the caller can surface it via the
|
|
813
|
+
// `SplitPayoutReverted` event without re-throwing here.
|
|
814
|
+
failureReason = reason;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// On success, compute how much the terminal actually pulled. The post-call allowance equals
|
|
818
|
+
// `amount - actuallyConsumed` (since `forceApprove` set the pre-call allowance to `amount`), so
|
|
819
|
+
// `consumed = amount - postAllowance`. On failure, `succeeded` is `false` and `consumed` stays at
|
|
820
|
+
// its default `0`, which lets the caller route the full `amount` to the project's leftover balance.
|
|
821
|
+
if (succeeded) {
|
|
822
|
+
// Safe to use `unchecked`: `postAllowance` cannot exceed `amount` because the terminal can
|
|
823
|
+
// only decrement (via `transferFrom`) the allowance we just set to `amount`.
|
|
824
|
+
unchecked {
|
|
825
|
+
consumed = amount - IERC20(token).allowance({owner: address(this), spender: address(terminal)});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Unconditionally revoke any remaining allowance. This is the load-bearing safety step — without
|
|
830
|
+
// it, a terminal that returns successfully without pulling the full `amount` would leave a stale
|
|
831
|
+
// non-zero allowance that the terminal could drain later. Running on both the success and failure
|
|
832
|
+
// paths means no allowance ever leaks out of this function.
|
|
833
|
+
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: 0});
|
|
834
|
+
}
|
|
835
|
+
|
|
693
836
|
/// @notice Distributes funds for a single tier's split group.
|
|
694
837
|
/// @dev `_sendPayoutToSplit` returns the actual amount that left this contract — `payoutAmount` on full
|
|
695
838
|
/// success, `0` when the split fully fails (revert or missing recipient), or a partial value when an ERC-20
|
|
@@ -779,20 +922,16 @@ library JB721TiersHookLib {
|
|
|
779
922
|
});
|
|
780
923
|
}
|
|
781
924
|
} else {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
}) {}
|
|
791
|
-
catch (bytes memory reason) {
|
|
792
|
-
// Reset approval on failure.
|
|
793
|
-
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: 0});
|
|
925
|
+
// Allowance-delta + unconditional cleanup: a terminal that returns successfully without pulling
|
|
926
|
+
// the full allowance would otherwise leave funds stranded in this library/hook and leak a
|
|
927
|
+
// non-zero allowance to the terminal. Fail closed: revert if either the call reverts OR the
|
|
928
|
+
// terminal under-consumed the granted allowance.
|
|
929
|
+
(uint256 consumed, bytes memory failureReason) = _approveAndAddToBalance({
|
|
930
|
+
terminal: terminal, token: token, projectId: projectId, amount: leftoverAmount
|
|
931
|
+
});
|
|
932
|
+
if (failureReason.length != 0 || consumed != leftoverAmount) {
|
|
794
933
|
revert JB721TiersHookLib_SplitFallbackFailed({
|
|
795
|
-
projectId: projectId, token: token, amount: leftoverAmount, reason:
|
|
934
|
+
projectId: projectId, token: token, amount: leftoverAmount, reason: failureReason
|
|
796
935
|
});
|
|
797
936
|
}
|
|
798
937
|
}
|
|
@@ -895,24 +1034,24 @@ library JB721TiersHookLib {
|
|
|
895
1034
|
return 0;
|
|
896
1035
|
}
|
|
897
1036
|
} else {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
return amount;
|
|
908
|
-
} catch (bytes memory reason) {
|
|
909
|
-
// Reset approval on failure so tokens aren't left approved to the terminal.
|
|
910
|
-
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: 0});
|
|
1037
|
+
// Allowance-delta + unconditional cleanup. `sent` reflects the amount the terminal actually
|
|
1038
|
+
// pulled; the unconsumed remainder (if any) stays in this contract and is routed back to the
|
|
1039
|
+
// project's leftover balance by the caller. On revert, `sent` stays 0 and the SplitPayoutReverted
|
|
1040
|
+
// event records the failure reason.
|
|
1041
|
+
bytes memory failureReason;
|
|
1042
|
+
(sent, failureReason) = _approveAndAddToBalance({
|
|
1043
|
+
terminal: terminal, token: token, projectId: split.projectId, amount: amount
|
|
1044
|
+
});
|
|
1045
|
+
if (failureReason.length != 0) {
|
|
911
1046
|
emit SplitPayoutReverted({
|
|
912
|
-
projectId: projectId,
|
|
1047
|
+
projectId: projectId,
|
|
1048
|
+
split: split,
|
|
1049
|
+
amount: amount,
|
|
1050
|
+
reason: failureReason,
|
|
1051
|
+
caller: msg.sender
|
|
913
1052
|
});
|
|
914
|
-
return 0;
|
|
915
1053
|
}
|
|
1054
|
+
return sent;
|
|
916
1055
|
}
|
|
917
1056
|
} else {
|
|
918
1057
|
if (isNativeToken) {
|
|
@@ -933,25 +1072,25 @@ library JB721TiersHookLib {
|
|
|
933
1072
|
return 0;
|
|
934
1073
|
}
|
|
935
1074
|
} else {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1075
|
+
// Same allowance-delta pattern as the `preferAddToBalance` branch, for the `pay` entry point.
|
|
1076
|
+
bytes memory failureReason;
|
|
1077
|
+
(sent, failureReason) = _approveAndPay({
|
|
1078
|
+
terminal: terminal,
|
|
939
1079
|
token: token,
|
|
1080
|
+
projectId: split.projectId,
|
|
940
1081
|
amount: amount,
|
|
941
|
-
beneficiary: split.beneficiary
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
metadata: bytes("")
|
|
945
|
-
}) {
|
|
946
|
-
return amount;
|
|
947
|
-
} catch (bytes memory reason) {
|
|
948
|
-
// Reset approval on failure so tokens aren't left approved to the terminal.
|
|
949
|
-
SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: 0});
|
|
1082
|
+
beneficiary: split.beneficiary
|
|
1083
|
+
});
|
|
1084
|
+
if (failureReason.length != 0) {
|
|
950
1085
|
emit SplitPayoutReverted({
|
|
951
|
-
projectId: projectId,
|
|
1086
|
+
projectId: projectId,
|
|
1087
|
+
split: split,
|
|
1088
|
+
amount: amount,
|
|
1089
|
+
reason: failureReason,
|
|
1090
|
+
caller: msg.sender
|
|
952
1091
|
});
|
|
953
|
-
return 0;
|
|
954
1092
|
}
|
|
1093
|
+
return sent;
|
|
955
1094
|
}
|
|
956
1095
|
}
|
|
957
1096
|
} else if (split.beneficiary != address(0)) {
|
|
@@ -12,7 +12,7 @@ import {JB721TierFlags} from "./JB721TierFlags.sol";
|
|
|
12
12
|
/// tier. With a `reserveFrequency` of 5, an extra NFT will be minted for the `reserveBeneficiary` for every 5 NFTs
|
|
13
13
|
/// purchased.
|
|
14
14
|
/// @custom:member reserveBeneficiary The address which receives any reserve NFTs from this tier.
|
|
15
|
-
/// @custom:member
|
|
15
|
+
/// @custom:member encodedIpfsUri The IPFS URI to use for each NFT in this tier.
|
|
16
16
|
/// @custom:member category The category that NFTs in this tier belongs to. Used to group NFT tiers.
|
|
17
17
|
/// @custom:member discountPercent The discount that should be applied to the tier.
|
|
18
18
|
/// @custom:member flags Boolean flags for this tier (allowOwnerMint, transfersPausable, cantBeRemoved,
|
|
@@ -29,7 +29,7 @@ struct JB721Tier {
|
|
|
29
29
|
uint104 votingUnits;
|
|
30
30
|
uint16 reserveFrequency;
|
|
31
31
|
address reserveBeneficiary;
|
|
32
|
-
bytes32
|
|
32
|
+
bytes32 encodedIpfsUri;
|
|
33
33
|
uint24 category;
|
|
34
34
|
uint8 discountPercent;
|
|
35
35
|
JB721TierFlags flags;
|
|
@@ -14,7 +14,7 @@ import {JB721TierConfigFlags} from "./JB721TierConfigFlags.sol";
|
|
|
14
14
|
/// purchased.
|
|
15
15
|
/// @custom:member reserveBeneficiary The address which receives any reserve NFTs from this tier. Overrides the default
|
|
16
16
|
/// reserve beneficiary if one is set.
|
|
17
|
-
/// @custom:member
|
|
17
|
+
/// @custom:member encodedIpfsUri The IPFS URI to use for each NFT in this tier.
|
|
18
18
|
/// @custom:member category The category that NFTs in this tier belongs to. Used to group NFT tiers.
|
|
19
19
|
/// @custom:member discountPercent The discount that should be applied to the tier.
|
|
20
20
|
/// @custom:member flags Boolean flags for this tier config (allowOwnerMint, useReserveBeneficiaryAsDefault,
|
|
@@ -29,7 +29,7 @@ struct JB721TierConfig {
|
|
|
29
29
|
uint32 votingUnits;
|
|
30
30
|
uint16 reserveFrequency;
|
|
31
31
|
address reserveBeneficiary;
|
|
32
|
-
bytes32
|
|
32
|
+
bytes32 encodedIpfsUri;
|
|
33
33
|
uint24 category;
|
|
34
34
|
uint8 discountPercent;
|
|
35
35
|
JB721TierConfigFlags flags;
|
|
@@ -162,7 +162,7 @@ contract ForTest_JB721TiersHookStore is JB721TiersHookStore, IJB721TiersHookStor
|
|
|
162
162
|
votingUnits: storedTier.price,
|
|
163
163
|
reserveFrequency: storedTier.reserveFrequency,
|
|
164
164
|
reserveBeneficiary: reserveBeneficiaryOf(nft, currentSortIndex),
|
|
165
|
-
|
|
165
|
+
encodedIpfsUri: encodedIpfsUriOf[nft][currentSortIndex],
|
|
166
166
|
category: storedTier.category,
|
|
167
167
|
discountPercent: storedTier.discountPercent,
|
|
168
168
|
flags: JB721TierFlags({
|
|
@@ -190,7 +190,7 @@ contract UnitTestSetup is Test {
|
|
|
190
190
|
tiers[i].price = uint104((i + 1) * 10); // The price is `tierId` * 10.
|
|
191
191
|
tiers[i].initialSupply = uint32(100);
|
|
192
192
|
tiers[i].reserveBeneficiary = reserveBeneficiary;
|
|
193
|
-
tiers[i].
|
|
193
|
+
tiers[i].encodedIpfsUri = tokenUris[i];
|
|
194
194
|
tiers[i].category = uint24(100);
|
|
195
195
|
tiers[i].flags.useVotingUnits = true;
|
|
196
196
|
}
|
|
@@ -325,7 +325,7 @@ contract UnitTestSetup is Test {
|
|
|
325
325
|
assertEq(first.votingUnits, second.votingUnits);
|
|
326
326
|
assertEq(first.reserveFrequency, second.reserveFrequency);
|
|
327
327
|
assertEq(first.reserveBeneficiary, second.reserveBeneficiary);
|
|
328
|
-
assertEq(first.
|
|
328
|
+
assertEq(first.encodedIpfsUri, second.encodedIpfsUri);
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
// Compare two arrays of `JB721Tier`s.
|
|
@@ -339,7 +339,7 @@ contract UnitTestSetup is Test {
|
|
|
339
339
|
assertEq(first[i].votingUnits, second[i].votingUnits);
|
|
340
340
|
assertEq(first[i].reserveFrequency, second[i].reserveFrequency);
|
|
341
341
|
assertEq(first[i].reserveBeneficiary, second[i].reserveBeneficiary);
|
|
342
|
-
assertEq(first[i].
|
|
342
|
+
assertEq(first[i].encodedIpfsUri, second[i].encodedIpfsUri);
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
|
|
@@ -390,7 +390,7 @@ contract UnitTestSetup is Test {
|
|
|
390
390
|
&& first.initialSupply == second.initialSupply && first.votingUnits == second.votingUnits
|
|
391
391
|
&& first.reserveFrequency == second.reserveFrequency
|
|
392
392
|
&& first.reserveBeneficiary == second.reserveBeneficiary
|
|
393
|
-
&& first.
|
|
393
|
+
&& first.encodedIpfsUri == second.encodedIpfsUri
|
|
394
394
|
&& keccak256(abi.encodePacked(first.resolvedUri)) == keccak256(abi.encodePacked(second.resolvedUri)));
|
|
395
395
|
}
|
|
396
396
|
|
|
@@ -516,7 +516,7 @@ contract UnitTestSetup is Test {
|
|
|
516
516
|
votingUnits: tierConfig.votingUnits,
|
|
517
517
|
reserveFrequency: tierConfig.reserveFrequency,
|
|
518
518
|
reserveBeneficiary: reserveBeneficiary,
|
|
519
|
-
|
|
519
|
+
encodedIpfsUri: i < tokenUris.length ? tokenUris[i] : tokenUris[0],
|
|
520
520
|
category: categoryIncrement == 0
|
|
521
521
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
522
522
|
? tierConfig.category == type(uint24).max ? uint24(i * 2 + 1) : tierConfig.category
|
|
@@ -545,7 +545,7 @@ contract UnitTestSetup is Test {
|
|
|
545
545
|
votingUnits: tierConfigs[i].votingUnits,
|
|
546
546
|
reserveFrequency: tierConfigs[i].reserveFrequency,
|
|
547
547
|
reserveBeneficiary: tierConfigs[i].reserveBeneficiary,
|
|
548
|
-
|
|
548
|
+
encodedIpfsUri: tierConfigs[i].encodedIpfsUri,
|
|
549
549
|
category: tierConfigs[i].category,
|
|
550
550
|
discountPercent: tierConfigs[i].discountPercent,
|
|
551
551
|
flags: JB721TierFlags({
|
|
@@ -556,7 +556,7 @@ contract UnitTestSetup is Test {
|
|
|
556
556
|
cantBuyWithCredits: tierConfigs[i].flags.cantBuyWithCredits
|
|
557
557
|
}),
|
|
558
558
|
splitPercent: tierConfigs[i].splitPercent,
|
|
559
|
-
resolvedUri: defaultTierConfig.
|
|
559
|
+
resolvedUri: defaultTierConfig.encodedIpfsUri == bytes32(0)
|
|
560
560
|
? ""
|
|
561
561
|
: string(abi.encodePacked("resolverURI", _generateTokenId(initialId + i + 1, 0)))
|
|
562
562
|
});
|
|
@@ -721,7 +721,7 @@ contract UnitTestSetup is Test {
|
|
|
721
721
|
votingUnits: uint16(0),
|
|
722
722
|
reserveFrequency: uint16(0),
|
|
723
723
|
reserveBeneficiary: reserveBeneficiary,
|
|
724
|
-
|
|
724
|
+
encodedIpfsUri: tokenUris[i],
|
|
725
725
|
category: uint24(100),
|
|
726
726
|
discountPercent: uint8(0),
|
|
727
727
|
flags: JB721TierConfigFlags({
|