-
Notifications
You must be signed in to change notification settings - Fork 485
/
Multiwrap.sol
256 lines (207 loc) · 9.3 KB
/
Multiwrap.sol
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
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;
// ========== External imports ==========
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol";
// ========== Internal imports ==========
import "../interfaces/IMultiwrap.sol";
import "../openzeppelin-presets/metatx/ERC2771ContextUpgradeable.sol";
// ========== Features ==========
import "../feature/ContractMetadata.sol";
import "../feature/Royalty.sol";
import "../feature/Ownable.sol";
import "../feature/PermissionsEnumerable.sol";
import "../feature/TokenStore.sol";
contract Multiwrap is
Initializable,
ContractMetadata,
Royalty,
Ownable,
PermissionsEnumerable,
TokenStore,
ReentrancyGuardUpgradeable,
ERC2771ContextUpgradeable,
MulticallUpgradeable,
ERC721Upgradeable,
IMultiwrap
{
/*///////////////////////////////////////////////////////////////
State variables
//////////////////////////////////////////////////////////////*/
bytes32 private constant MODULE_TYPE = bytes32("Multiwrap");
uint256 private constant VERSION = 1;
/// @dev Only transfers to or from TRANSFER_ROLE holders are valid, when transfers are restricted.
bytes32 private constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE");
/// @dev Only MINTER_ROLE holders can wrap tokens, when wrapping is restricted.
bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE");
/// @dev Only UNWRAP_ROLE holders can unwrap tokens, when unwrapping is restricted.
bytes32 private constant UNWRAP_ROLE = keccak256("UNWRAP_ROLE");
/// @dev Only assets with ASSET_ROLE can be wrapped, when wrapping is restricted to particular assets.
bytes32 private constant ASSET_ROLE = keccak256("ASSET_ROLE");
/// @dev The next token ID of the NFT to mint.
uint256 public nextTokenIdToMint;
/*///////////////////////////////////////////////////////////////
Constructor + initializer logic
//////////////////////////////////////////////////////////////*/
constructor(address _nativeTokenWrapper) TokenStore(_nativeTokenWrapper) initializer {}
/// @dev Initiliazes the contract, like a constructor.
function initialize(
address _defaultAdmin,
string memory _name,
string memory _symbol,
string memory _contractURI,
address[] memory _trustedForwarders,
address _royaltyRecipient,
uint256 _royaltyBps
) external initializer {
// Initialize inherited contracts, most base-like -> most derived.
__ReentrancyGuard_init();
__ERC2771Context_init(_trustedForwarders);
__ERC721_init(_name, _symbol);
// Revoked at the end of the function.
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
// Initialize this contract's state.
setDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps);
setOwner(_defaultAdmin);
setContractURI(_contractURI);
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
_setupRole(MINTER_ROLE, _defaultAdmin);
_setupRole(TRANSFER_ROLE, _defaultAdmin);
// note: see `_beforeTokenTransfer` for TRANSFER_ROLE behaviour.
_setupRole(TRANSFER_ROLE, address(0));
// note: see `onlyRoleWithSwitch` for UNWRAP_ROLE behaviour.
_setupRole(UNWRAP_ROLE, address(0));
// note: see `onlyRoleWithSwitch` for UNWRAP_ROLE behaviour.
_setupRole(ASSET_ROLE, address(0));
_revokeRole(DEFAULT_ADMIN_ROLE, _msgSender());
}
/*///////////////////////////////////////////////////////////////
Modifiers
//////////////////////////////////////////////////////////////*/
modifier onlyRoleWithSwitch(bytes32 role) {
_checkRoleWithSwitch(role, _msgSender());
_;
}
/*///////////////////////////////////////////////////////////////
Generic contract logic
//////////////////////////////////////////////////////////////*/
/// @dev Returns the type of the contract.
function contractType() external pure returns (bytes32) {
return MODULE_TYPE;
}
/// @dev Returns the version of the contract.
function contractVersion() external pure returns (uint8) {
return uint8(VERSION);
}
/*///////////////////////////////////////////////////////////////
ERC 165 / 721 / 2981 logic
//////////////////////////////////////////////////////////////*/
/// @dev Returns the URI for a given tokenId.
function tokenURI(uint256 _tokenId) public view override returns (string memory) {
return getUriOfBundle(_tokenId);
}
/// @dev See ERC 165
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC1155Receiver, ERC721Upgradeable)
returns (bool)
{
return
super.supportsInterface(interfaceId) ||
interfaceId == type(IERC721Upgradeable).interfaceId ||
interfaceId == type(IERC2981Upgradeable).interfaceId;
}
/*///////////////////////////////////////////////////////////////
Wrapping / Unwrapping logic
//////////////////////////////////////////////////////////////*/
/// @dev Wrap multiple ERC1155, ERC721, ERC20 tokens into a single wrapped NFT.
function wrap(
Token[] calldata _tokensToWrap,
string calldata _uriForWrappedToken,
address _recipient
) external payable nonReentrant onlyRoleWithSwitch(MINTER_ROLE) returns (uint256 tokenId) {
if (!hasRole(ASSET_ROLE, address(0))) {
for (uint256 i = 0; i < _tokensToWrap.length; i += 1) {
_checkRole(ASSET_ROLE, _tokensToWrap[i].assetContract);
}
}
tokenId = nextTokenIdToMint;
nextTokenIdToMint += 1;
_storeTokens(_msgSender(), _tokensToWrap, _uriForWrappedToken, tokenId);
_safeMint(_recipient, tokenId);
emit TokensWrapped(_msgSender(), _recipient, tokenId, _tokensToWrap);
}
/// @dev Unwrap a wrapped NFT to retrieve underlying ERC1155, ERC721, ERC20 tokens.
function unwrap(uint256 _tokenId, address _recipient) external nonReentrant onlyRoleWithSwitch(UNWRAP_ROLE) {
require(_tokenId < nextTokenIdToMint, "Multiwrap: wrapped NFT DNE.");
require(_isApprovedOrOwner(_msgSender(), _tokenId), "Multiwrap: caller not approved for unwrapping.");
_burn(_tokenId);
_releaseTokens(_recipient, _tokenId);
emit TokensUnwrapped(_msgSender(), _recipient, _tokenId);
}
/*///////////////////////////////////////////////////////////////
Getter functions
//////////////////////////////////////////////////////////////*/
/// @dev Returns the underlying contents of a wrapped NFT.
function getWrappedContents(uint256 _tokenId) external view returns (Token[] memory contents) {
uint256 total = getTokenCountOfBundle(_tokenId);
contents = new Token[](total);
for (uint256 i = 0; i < total; i += 1) {
contents[i] = getTokenOfBundle(_tokenId, i);
}
}
/*///////////////////////////////////////////////////////////////
Internal functions
//////////////////////////////////////////////////////////////*/
/// @dev Returns whether owner can be set in the given execution context.
function _canSetOwner() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}
/// @dev Returns whether royalty info can be set in the given execution context.
function _canSetRoyaltyInfo() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}
/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}
/*///////////////////////////////////////////////////////////////
Miscellaneous
//////////////////////////////////////////////////////////////*/
/**
* @dev See {ERC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override {
super._beforeTokenTransfer(from, to, tokenId);
// if transfer is restricted on the contract, we still want to allow burning and minting
if (!hasRole(TRANSFER_ROLE, address(0)) && from != address(0) && to != address(0)) {
require(hasRole(TRANSFER_ROLE, from) || hasRole(TRANSFER_ROLE, to), "!TRANSFER_ROLE");
}
}
function _msgSender()
internal
view
virtual
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (address sender)
{
return ERC2771ContextUpgradeable._msgSender();
}
function _msgData()
internal
view
virtual
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (bytes calldata)
{
return ERC2771ContextUpgradeable._msgData();
}
}