-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathMintableERC721.sol
More file actions
283 lines (227 loc) · 11.1 KB
/
MintableERC721.sol
File metadata and controls
283 lines (227 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;
import {Module} from "../../../Module.sol";
import {console} from "forge-std/console.sol";
import {Role} from "../../../Role.sol";
import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol";
import {IMintFeeManager} from "../../../interface/IMintFeeManager.sol";
import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
import {BeforeMintCallbackERC721} from "../../../callback/BeforeMintCallbackERC721.sol";
import {BeforeMintWithSignatureCallbackERC721} from "../../../callback/BeforeMintWithSignatureCallbackERC721.sol";
library MintableStorage {
/// @custom:storage-location erc7201:token.minting.mintable
bytes32 public constant MINTABLE_STORAGE_POSITION =
keccak256(abi.encode(uint256(keccak256("token.minting.mintable.erc721")) - 1)) & ~bytes32(uint256(0xff));
struct Data {
// UID => whether it has been used
mapping(bytes32 => bool) uidUsed;
// sale config
MintableERC721.SaleConfig saleConfig;
}
function data() internal pure returns (Data storage data_) {
bytes32 position = MINTABLE_STORAGE_POSITION;
assembly {
data_.slot := position
}
}
}
contract MintableERC721 is
Module,
BeforeMintCallbackERC721,
BeforeMintWithSignatureCallbackERC721,
IInstallationCallback
{
/*//////////////////////////////////////////////////////////////
STRUCTS & ENUMS
//////////////////////////////////////////////////////////////*/
/**
* @notice The request struct signed by an authorized party to mint tokens.
*
* @param startTimestamp The timestamp at which the minting request is valid.
* @param endTimestamp The timestamp at which the minting request expires.
* @param currency The address of the currency used to pay for the minted tokens.
* @param pricePerUnit The price per unit of the minted tokens.
* @param uid A unique identifier for the minting request.
*/
struct MintSignatureParamsERC721 {
uint48 startTimestamp;
uint48 endTimestamp;
address currency;
uint256 pricePerUnit;
bytes32 uid;
}
/**
* @notice The configuration of a token's sale value distribution.
*
* @param primarySaleRecipient The address that receives the primary sale value.
*/
struct SaleConfig {
address primarySaleRecipient;
}
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @dev Emitted when an incorrect amount of native token is sent.
error MintableIncorrectNativeTokenSent();
/// @dev Emitted when the minting request has expired.
error MintableRequestOutOfTimeWindow();
/// @dev Emitted when the minting request UID has been reused.
error MintableRequestUidReused();
/// @dev Emitted when the minting request token is invalid.
error MintableRequestInvalidToken();
/// @dev Emitted when the minting request does not match the expected values.
error MintableRequestMismatch();
/// @dev Emitted when the minting request signature is unauthorized.
error MintableRequestUnauthorized();
/// @dev Emitted when trying to fetch metadata for a token that has no metadata.
error MintableNoMetadataForTokenId();
/// @dev Emitted when the minting request signature is unauthorized.
error MintableSignatureMintUnauthorized();
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address private immutable mintFeeManager;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address _mintFeeManager) {
mintFeeManager = _mintFeeManager;
}
/*//////////////////////////////////////////////////////////////
MODULE CONFIG
//////////////////////////////////////////////////////////////*/
/// @notice Returns all implemented callback and fallback functions.
function getModuleConfig() external pure override returns (ModuleConfig memory config) {
config.callbackFunctions = new CallbackFunction[](2);
config.fallbackFunctions = new FallbackFunction[](2);
config.callbackFunctions[0] = CallbackFunction(this.beforeMintERC721.selector);
config.callbackFunctions[1] = CallbackFunction(this.beforeMintWithSignatureERC721.selector);
config.fallbackFunctions[0] = FallbackFunction({selector: this.getSaleConfig.selector, permissionBits: 0});
config.fallbackFunctions[1] =
FallbackFunction({selector: this.setSaleConfig.selector, permissionBits: Role._MANAGER_ROLE});
config.requiredInterfaces = new bytes4[](1);
config.requiredInterfaces[0] = 0x80ac58cd; // ERC721.
config.registerInstallationCallback = true;
}
/*//////////////////////////////////////////////////////////////
CALLBACK FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Callback function for the ERC721Core.mint function.
function beforeMintERC721(address _to, uint256 _startTokenId, uint256 _quantity, bytes memory _data)
external
payable
virtual
override
returns (bytes memory)
{
if (
OwnableRoles(address(this)).owner() != msg.sender
&& !OwnableRoles(address(this)).hasAllRoles(msg.sender, Role._MINTER_ROLE)
) {
revert MintableRequestUnauthorized();
}
}
/// @notice Callback function for the ERC721Core.mint function.
function beforeMintWithSignatureERC721(
address _to,
uint256 _startTokenId,
uint256 _quantity,
bytes memory _data,
address _signer
) external payable virtual override returns (bytes memory) {
if (!OwnableRoles(address(this)).hasAllRoles(_signer, Role._MINTER_ROLE)) {
revert MintableSignatureMintUnauthorized();
}
MintSignatureParamsERC721 memory _params = abi.decode(_data, (MintSignatureParamsERC721));
_mintWithSignatureERC721(_params);
_distributeMintPrice(msg.sender, _params.currency, _quantity * _params.pricePerUnit);
}
/// @dev Called by a Core into an Module during the installation of the Module.
function onInstall(bytes calldata data) external {
address primarySaleRecipient = abi.decode(data, (address));
_mintableStorage().saleConfig = SaleConfig(primarySaleRecipient);
}
/// @dev Called by a Core into an Module during the uninstallation of the Module.
function onUninstall(bytes calldata data) external {}
/*//////////////////////////////////////////////////////////////
Encode install / uninstall data
//////////////////////////////////////////////////////////////*/
/// @dev Returns bytes encoded install params, to be sent to `onInstall` function
function encodeBytesOnInstall(address primarySaleRecipient) external pure returns (bytes memory) {
return abi.encode(primarySaleRecipient);
}
/// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function
function encodeBytesOnUninstall() external pure returns (bytes memory) {
return "";
}
/*//////////////////////////////////////////////////////////////
Encode mint params
//////////////////////////////////////////////////////////////*/
/// @dev Returns bytes encoded mint params, to be used in `beforeMint` fallback function
function encodeBytesBeforeMintWithSignatureERC721(MintSignatureParamsERC721 memory params)
external
pure
returns (bytes memory)
{
return abi.encode(params);
}
/*//////////////////////////////////////////////////////////////
FALLBACK FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Returns the sale configuration for a token.
function getSaleConfig() external view returns (address primarySaleRecipient) {
SaleConfig memory saleConfig = _mintableStorage().saleConfig;
return (saleConfig.primarySaleRecipient);
}
/// @notice Sets the sale configuration for a token.
function setSaleConfig(address _primarySaleRecipient) external {
_mintableStorage().saleConfig = SaleConfig(_primarySaleRecipient);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @dev Mints tokens on verifying a signature from an authorized party.
function _mintWithSignatureERC721(MintSignatureParamsERC721 memory _req) internal {
if (block.timestamp < _req.startTimestamp || _req.endTimestamp <= block.timestamp) {
revert MintableRequestOutOfTimeWindow();
}
if (_mintableStorage().uidUsed[_req.uid]) {
revert MintableRequestUidReused();
}
_mintableStorage().uidUsed[_req.uid] = true;
}
/// @dev Distributes the minting price to the primary sale recipient and platform fee recipient.
function _distributeMintPrice(address _owner, address _currency, uint256 _price) internal {
if (_price == 0) {
if (msg.value > 0) {
revert MintableIncorrectNativeTokenSent();
}
return;
}
SaleConfig memory saleConfig = _mintableStorage().saleConfig;
if (_currency == NATIVE_TOKEN_ADDRESS) {
if (msg.value != _price) {
revert MintableIncorrectNativeTokenSent();
}
(uint256 platformFeeAmount, address feeRecipient) =
IMintFeeManager(mintFeeManager).calculatePlatformFeeAndRecipient(_price);
uint256 primarySaleAmount = _price - platformFeeAmount;
SafeTransferLib.safeTransferETH(feeRecipient, platformFeeAmount);
SafeTransferLib.safeTransferETH(saleConfig.primarySaleRecipient, primarySaleAmount);
} else {
if (msg.value > 0) {
revert MintableIncorrectNativeTokenSent();
}
(uint256 platformFeeAmount, address feeRecipient) =
IMintFeeManager(mintFeeManager).calculatePlatformFeeAndRecipient(_price);
uint256 primarySaleAmount = _price - platformFeeAmount;
SafeTransferLib.safeTransferFrom(_currency, _owner, feeRecipient, platformFeeAmount);
SafeTransferLib.safeTransferFrom(_currency, _owner, saleConfig.primarySaleRecipient, primarySaleAmount);
}
}
function _mintableStorage() internal pure returns (MintableStorage.Data storage) {
return MintableStorage.data();
}
}