Skip to content

Commit 03da7c2

Browse files
authored
Implement tokenIdERC1155 module to handle tokenId management (#147)
* initial commit * created tests for tokenIdERC1155 * updated to be optional * updated naming and tests
1 parent 4a877ab commit 03da7c2

File tree

8 files changed

+275
-15
lines changed

8 files changed

+275
-15
lines changed

Diff for: src/callback/UpdateTokenIdERC1155.sol

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.20;
3+
4+
contract UpdateTokenIdCallbackERC1155 {
5+
6+
/*//////////////////////////////////////////////////////////////
7+
ERRORS
8+
//////////////////////////////////////////////////////////////*/
9+
10+
error UpdateTokenIdCallbackERC1155NotImplemented();
11+
12+
/*//////////////////////////////////////////////////////////////
13+
EXTERNAL FUNCTIONS
14+
//////////////////////////////////////////////////////////////*/
15+
16+
/**
17+
* @notice The updateTokenIdERC1155 hook that is called by a core token before minting tokens.
18+
*
19+
* @dev If the tokenId is type(uint256).max, the next tokenId will be set to the current next tokenId + amount.
20+
*
21+
* @param _tokenId The tokenId to mint.
22+
* @return result tokenId to mint.
23+
*/
24+
function updateTokenIdERC1155(uint256 _tokenId) external payable virtual returns (uint256) {
25+
revert UpdateTokenIdCallbackERC1155NotImplemented();
26+
}
27+
28+
}

Diff for: src/core/token/ERC1155Core.sol

+30-9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {BeforeMintCallbackERC1155} from "../../callback/BeforeMintCallbackERC115
1515
import {BeforeMintWithSignatureCallbackERC1155} from "../../callback/BeforeMintWithSignatureCallbackERC1155.sol";
1616
import {BeforeTransferCallbackERC1155} from "../../callback/BeforeTransferCallbackERC1155.sol";
1717
import {UpdateMetadataCallbackERC1155} from "../../callback/UpdateMetadataCallbackERC1155.sol";
18+
import {UpdateTokenIdCallbackERC1155} from "../../callback/UpdateTokenIdERC1155.sol";
1819

1920
import {OnTokenURICallback} from "../../callback/OnTokenURICallback.sol";
2021

@@ -142,7 +143,7 @@ contract ERC1155Core is ERC1155, Core, Multicallable, EIP712 {
142143
override
143144
returns (SupportedCallbackFunction[] memory supportedCallbackFunctions)
144145
{
145-
supportedCallbackFunctions = new SupportedCallbackFunction[](8);
146+
supportedCallbackFunctions = new SupportedCallbackFunction[](9);
146147
supportedCallbackFunctions[0] = SupportedCallbackFunction({
147148
selector: BeforeMintCallbackERC1155.beforeMintERC1155.selector,
148149
mode: CallbackMode.REQUIRED
@@ -173,6 +174,10 @@ contract ERC1155Core is ERC1155, Core, Multicallable, EIP712 {
173174
selector: UpdateMetadataCallbackERC1155.updateMetadataERC1155.selector,
174175
mode: CallbackMode.REQUIRED
175176
});
177+
supportedCallbackFunctions[8] = SupportedCallbackFunction({
178+
selector: UpdateTokenIdCallbackERC1155.updateTokenIdERC1155.selector,
179+
mode: CallbackMode.OPTIONAL
180+
});
176181
}
177182

178183
/*//////////////////////////////////////////////////////////////
@@ -201,13 +206,14 @@ contract ERC1155Core is ERC1155, Core, Multicallable, EIP712 {
201206
external
202207
payable
203208
{
209+
uint256 tokenIdToMint = _updateTokenId(tokenId);
204210
if (bytes(baseURI).length > 0) {
205-
_updateMetadata(to, tokenId, amount, baseURI);
211+
_updateMetadata(to, tokenIdToMint, amount, baseURI);
206212
}
207-
_beforeMint(to, tokenId, amount, data);
213+
_beforeMint(to, tokenIdToMint, amount, data);
208214

209-
_totalSupply[tokenId] += amount;
210-
_mint(to, tokenId, amount, "");
215+
_totalSupply[tokenIdToMint] += amount;
216+
_mint(to, tokenIdToMint, amount, "");
211217
}
212218

213219
/**
@@ -236,13 +242,14 @@ contract ERC1155Core is ERC1155, Core, Multicallable, EIP712 {
236242
)
237243
).recover(signature);
238244

245+
uint256 tokenIdToMint = _updateTokenId(tokenId);
239246
if (bytes(baseURI).length > 0) {
240-
_updateMetadata(to, tokenId, amount, baseURI);
247+
_updateMetadata(to, tokenIdToMint, amount, baseURI);
241248
}
242-
_beforeMintWithSignature(to, tokenId, amount, data, signer);
249+
_beforeMintWithSignature(to, tokenIdToMint, amount, data, signer);
243250

244-
_totalSupply[tokenId] += amount;
245-
_mint(to, tokenId, amount, "");
251+
_totalSupply[tokenIdToMint] += amount;
252+
_mint(to, tokenIdToMint, amount, "");
246253
}
247254

248255
/**
@@ -393,6 +400,20 @@ contract ERC1155Core is ERC1155, Core, Multicallable, EIP712 {
393400
);
394401
}
395402

403+
/// @dev Calls the updateTokenId hook, if installed.
404+
function _updateTokenId(uint256 tokenId) internal virtual returns (uint256 tokenIdToMint) {
405+
(bool success, bytes memory returndata) = _executeCallbackFunction(
406+
UpdateTokenIdCallbackERC1155.updateTokenIdERC1155.selector,
407+
abi.encodeCall(UpdateTokenIdCallbackERC1155.updateTokenIdERC1155, (tokenId))
408+
);
409+
if (success) {
410+
tokenIdToMint = abi.decode(returndata, (uint256));
411+
} else {
412+
// this will only occur when the callback is not implemented
413+
tokenIdToMint = tokenId;
414+
}
415+
}
416+
396417
/// @dev Returns the domain name and version for EIP712.
397418
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
398419
name = "ERC1155Core";

Diff for: src/core/token/ERC1155CoreInitializable.sol

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {BeforeMintCallbackERC1155} from "../../callback/BeforeMintCallbackERC115
1717
import {BeforeMintWithSignatureCallbackERC1155} from "../../callback/BeforeMintWithSignatureCallbackERC1155.sol";
1818
import {BeforeTransferCallbackERC1155} from "../../callback/BeforeTransferCallbackERC1155.sol";
1919
import {UpdateMetadataCallbackERC1155} from "../../callback/UpdateMetadataCallbackERC1155.sol";
20+
import {UpdateTokenIdCallbackERC1155} from "../../callback/UpdateTokenIdERC1155.sol";
2021

2122
import {OnTokenURICallback} from "../../callback/OnTokenURICallback.sol";
2223

@@ -142,7 +143,7 @@ contract ERC1155CoreInitializable is ERC1155, Core, Multicallable, Initializable
142143
override
143144
returns (SupportedCallbackFunction[] memory supportedCallbackFunctions)
144145
{
145-
supportedCallbackFunctions = new SupportedCallbackFunction[](8);
146+
supportedCallbackFunctions = new SupportedCallbackFunction[](9);
146147
supportedCallbackFunctions[0] = SupportedCallbackFunction({
147148
selector: BeforeMintCallbackERC1155.beforeMintERC1155.selector,
148149
mode: CallbackMode.REQUIRED
@@ -173,6 +174,10 @@ contract ERC1155CoreInitializable is ERC1155, Core, Multicallable, Initializable
173174
selector: UpdateMetadataCallbackERC1155.updateMetadataERC1155.selector,
174175
mode: CallbackMode.REQUIRED
175176
});
177+
supportedCallbackFunctions[8] = SupportedCallbackFunction({
178+
selector: UpdateTokenIdCallbackERC1155.updateTokenIdERC1155.selector,
179+
mode: CallbackMode.OPTIONAL
180+
});
176181
}
177182

178183
/*//////////////////////////////////////////////////////////////
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.20;
3+
4+
import {Module} from "../../../Module.sol";
5+
6+
import {Role} from "../../../Role.sol";
7+
8+
import {UpdateTokenIdCallbackERC1155} from "../../../callback/UpdateTokenIdERC1155.sol";
9+
import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol";
10+
11+
library SequentialTokenIdStorage {
12+
13+
/// @custom:storage-location erc7201:token.minting.tokenId
14+
bytes32 public constant SEQUENTIAL_TOKEN_ID_STORAGE_POSITION =
15+
keccak256(abi.encode(uint256(keccak256("token.tokenId.erc1155")) - 1)) & ~bytes32(uint256(0xff));
16+
17+
struct Data {
18+
uint256 nextTokenId;
19+
}
20+
21+
function data() internal pure returns (Data storage data_) {
22+
bytes32 position = SEQUENTIAL_TOKEN_ID_STORAGE_POSITION;
23+
assembly {
24+
data_.slot := position
25+
}
26+
}
27+
28+
}
29+
30+
contract SequentialTokenIdERC1155 is Module, UpdateTokenIdCallbackERC1155 {
31+
32+
/*//////////////////////////////////////////////////////////////
33+
ERRORS
34+
//////////////////////////////////////////////////////////////*/
35+
36+
/// @dev Emitted when the tokenId is invalid.
37+
error SequentialTokenIdInvalidTokenId();
38+
39+
/*//////////////////////////////////////////////////////////////
40+
MODULE CONFIG
41+
//////////////////////////////////////////////////////////////*/
42+
43+
/// @notice Returns all implemented callback and fallback functions.
44+
function getModuleConfig() external pure override returns (ModuleConfig memory config) {
45+
config.callbackFunctions = new CallbackFunction[](1);
46+
config.fallbackFunctions = new FallbackFunction[](1);
47+
48+
config.callbackFunctions[0] = CallbackFunction(this.updateTokenIdERC1155.selector);
49+
50+
config.fallbackFunctions[0] = FallbackFunction({selector: this.getNextTokenId.selector, permissionBits: 0});
51+
52+
config.requiredInterfaces = new bytes4[](1);
53+
config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155
54+
}
55+
56+
/*//////////////////////////////////////////////////////////////
57+
CALLBACK FUNCTION
58+
//////////////////////////////////////////////////////////////*/
59+
60+
function updateTokenIdERC1155(uint256 _tokenId) external payable override returns (uint256) {
61+
uint256 _nextTokenId = _tokenIdStorage().nextTokenId;
62+
63+
if (_tokenId == type(uint256).max) {
64+
_tokenIdStorage().nextTokenId = _nextTokenId + 1;
65+
66+
return _nextTokenId;
67+
}
68+
69+
if (_tokenId > _nextTokenId) {
70+
revert SequentialTokenIdInvalidTokenId();
71+
}
72+
73+
return _tokenId;
74+
}
75+
76+
/*//////////////////////////////////////////////////////////////
77+
FALLBACK FUNCTIONS
78+
//////////////////////////////////////////////////////////////*/
79+
80+
/// @notice Returns the sale configuration for a token.
81+
function getNextTokenId() external view returns (uint256) {
82+
return _tokenIdStorage().nextTokenId;
83+
}
84+
85+
function _tokenIdStorage() internal pure returns (SequentialTokenIdStorage.Data storage) {
86+
return SequentialTokenIdStorage.data();
87+
}
88+
89+
}

Diff for: test/module/minting/ClaimableERC1155.t.sol

+1-2
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,8 @@ contract ClaimableERC1155Test is Test {
115115
core.installModule(address(claimableModule), encodedInstallParams);
116116

117117
// install module
118-
bytes memory encodedBatchMetadataInstallParams = "";
119118
vm.prank(owner);
120-
core.installModule(address(batchMetadataModule), encodedBatchMetadataInstallParams);
119+
core.installModule(address(batchMetadataModule), "");
121120

122121
// Setup signature vars
123122
typehashClaimSignatureParams =

Diff for: test/module/minting/MintableERC1155.t.sol

+1-2
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,8 @@ contract MintableERC1155Test is Test {
113113
vm.prank(owner);
114114
core.installModule(address(mintableModule), encodedInstallParams);
115115

116-
bytes memory encodedBatchMetadataInstallParams = "";
117116
vm.prank(owner);
118-
core.installModule(address(batchMetadataModule), encodedBatchMetadataInstallParams);
117+
core.installModule(address(batchMetadataModule), "");
119118

120119
// Setup signature vars
121120
typehashMintSignatureParams =

Diff for: test/module/tokenId/sequentialTokenIdERC1155.t.sol

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.0;
3+
4+
import "lib/forge-std/src/console.sol";
5+
6+
import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
7+
import {ERC20} from "@solady/tokens/ERC20.sol";
8+
import {Test} from "forge-std/Test.sol";
9+
10+
// Target contract
11+
12+
import {Core} from "src/Core.sol";
13+
import {Module} from "src/Module.sol";
14+
15+
import {Role} from "src/Role.sol";
16+
import {ERC1155Core} from "src/core/token/ERC1155Core.sol";
17+
18+
import {ICore} from "src/interface/ICore.sol";
19+
import {IModuleConfig} from "src/interface/IModuleConfig.sol";
20+
21+
import {BatchMetadataERC1155} from "src/module/token/metadata/BatchMetadataERC1155.sol";
22+
import {BatchMetadataERC721} from "src/module/token/metadata/BatchMetadataERC721.sol";
23+
import {MintableERC1155} from "src/module/token/minting/MintableERC1155.sol";
24+
import {SequentialTokenIdERC1155} from "src/module/token/tokenId/sequentialTokenIdERC1155.sol";
25+
26+
contract MockCurrency is ERC20 {
27+
28+
function mintTo(address _recipient, uint256 _amount) public {
29+
_mint(_recipient, _amount);
30+
}
31+
32+
/// @dev Returns the name of the token.
33+
function name() public view virtual override returns (string memory) {
34+
return "MockCurrency";
35+
}
36+
37+
/// @dev Returns the symbol of the token.
38+
function symbol() public view virtual override returns (string memory) {
39+
return "MOCK";
40+
}
41+
42+
}
43+
44+
contract MintableERC1155Test is Test {
45+
46+
ERC1155Core public core;
47+
48+
MintableERC1155 public mintableModule;
49+
BatchMetadataERC1155 public batchMetadataModule;
50+
SequentialTokenIdERC1155 public sequentialTokenIdModule;
51+
52+
uint256 ownerPrivateKey = 1;
53+
address public owner;
54+
55+
address tokenRecipient = address(0x123);
56+
57+
MintableERC1155.MintSignatureParamsERC1155 public mintRequest;
58+
59+
// Constants
60+
address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
61+
62+
function setUp() public {
63+
owner = vm.addr(ownerPrivateKey);
64+
65+
address[] memory modules;
66+
bytes[] memory moduleData;
67+
68+
core = new ERC1155Core("test", "TEST", "", owner, modules, moduleData);
69+
mintableModule = new MintableERC1155();
70+
batchMetadataModule = new BatchMetadataERC1155();
71+
sequentialTokenIdModule = new SequentialTokenIdERC1155();
72+
73+
// install module
74+
bytes memory encodedInstallParams = abi.encode(owner);
75+
vm.prank(owner);
76+
core.installModule(address(mintableModule), encodedInstallParams);
77+
78+
vm.prank(owner);
79+
core.installModule(address(batchMetadataModule), "");
80+
81+
vm.prank(owner);
82+
core.installModule(address(sequentialTokenIdModule), "");
83+
84+
// Give permissioned actor minter role
85+
vm.prank(owner);
86+
core.grantRoles(owner, Role._MINTER_ROLE);
87+
}
88+
89+
/*//////////////////////////////////////////////////////////////
90+
Tests: beforeMintERC1155
91+
//////////////////////////////////////////////////////////////*/
92+
93+
function test_state_updateTokenId() public {
94+
assertEq(SequentialTokenIdERC1155(address(core)).getNextTokenId(), 0);
95+
96+
// increments the tokenId
97+
vm.prank(owner);
98+
core.mint(owner, type(uint256).max, 10, "", "");
99+
100+
assertEq(SequentialTokenIdERC1155(address(core)).getNextTokenId(), 1);
101+
102+
// does not increment the tokenId
103+
vm.prank(owner);
104+
core.mint(owner, 1, 10, "", "");
105+
106+
assertEq(SequentialTokenIdERC1155(address(core)).getNextTokenId(), 1);
107+
}
108+
109+
function test_revert_updateTokenId() public {
110+
vm.expectRevert(SequentialTokenIdERC1155.SequentialTokenIdInvalidTokenId.selector);
111+
vm.prank(owner);
112+
core.mint(owner, 2, 1, "", "");
113+
}
114+
115+
}

Diff for: test/module/transferable/TransferableERC1155.t.sol

+5-1
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ contract Core is ERC1155Core {
2727
bytes[] memory moduleInstallData
2828
) ERC1155Core(name, symbol, contractURI, owner, modules, moduleInstallData) {}
2929

30-
// disable mint and approve callbacks for these tests
30+
// disable mint, approve and tokenId callbacks for these tests
3131
function _beforeMint(address to, uint256 tokenId, uint256 value, bytes memory data) internal override {}
3232
function _beforeApproveForAll(address from, address to, bool approved) internal override {}
3333

34+
function _updateTokenId(uint256 tokenId) internal override returns (uint256) {
35+
return tokenId;
36+
}
37+
3438
}
3539

3640
contract TransferableERC1155Test is Test {

0 commit comments

Comments
 (0)