forked from limitbreakinc/creator-token-standards
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ERC721CW.sol
330 lines (276 loc) · 14.4 KB
/
ERC721CW.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
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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../ERC721C.sol";
import "../../interfaces/ICreatorTokenWrapperERC721.sol";
import "../../interfaces/IEOARegistry.sol";
import "../../utils/WithdrawETH.sol";
/**
* @title ERC721WrapperBase
* @author Limit Break, Inc.
* @notice Base contract extending ERC721-C contracts and adding a staking feature used to wrap another ERC721 contract.
* The wrapper token gives the developer access to the same set of controls present in the ERC721-C standard.
* Holders opt-in to this contract by staking, and it is possible for holders to unstake at the developers' discretion.
* The intent of this contract is to allow developers to upgrade existing NFT collections and provide enhanced features.
* The base contract is intended to be inherited by either a constructable or initializable contract.
*
* @notice Creators also have discretion to set optional staker constraints should they wish to restrict staking to
* EOA accounts only.
*/
abstract contract ERC721WrapperBase is WithdrawETH, ICreatorTokenWrapperERC721 {
error ERC721WrapperBase__CallerNotOwnerOfWrappingToken();
error ERC721WrapperBase__CallerNotOwnerOfWrappedToken();
error ERC721WrapperBase__CallerSignatureNotVerifiedInEOARegistry();
error ERC721WrapperBase__DefaultImplementationOfStakeDoesNotAcceptPayment();
error ERC721WrapperBase__DefaultImplementationOfUnstakeDoesNotAcceptPayment();
error ERC721WrapperBase__InvalidERC721Collection();
error ERC721WrapperBase__SmartContractsNotPermittedToStake();
/// @dev The staking constraints that will be used to determine if an address is eligible to stake tokens.
StakerConstraints private stakerConstraints;
/// @notice Allows the contract owner to update the staker constraints.
///
/// @dev Throws when caller is not the contract owner.
///
/// Postconditions:
/// ---------------
/// The staker constraints have been updated.
/// A `StakerConstraintsSet` event has been emitted.
function setStakerConstraints(StakerConstraints stakerConstraints_) public {
_requireCallerIsContractOwner();
stakerConstraints = stakerConstraints_;
emit StakerConstraintsSet(stakerConstraints_);
}
/// @notice Allows holders of the wrapped ERC721 token to stake into this enhanced ERC721 token.
/// The out of the box enhancement is ERC721-C standard, but developers can extend the functionality of this
/// contract with additional powered up features.
///
/// Throws when staker constraints is `CallerIsTxOrigin` and the caller is not the tx.origin.
/// Throws when staker constraints is `EOA` and the caller has not verified their signature in the transfer
/// validator contract.
/// Throws when caller does not own the token id on the wrapped collection.
/// Throws when inheriting contract reverts in the _onStake function (for example, in a pay to stake scenario).
/// Throws when _mint function reverts (for example, when additional mint validation logic reverts).
/// Throws when transferFrom function reverts (e.g. if this contract does not have approval to transfer token).
///
/// Postconditions:
/// ---------------
/// The staker's token is now owned by this contract.
/// The staker has received a wrapper token on this contract with the same token id.
/// A `Staked` event has been emitted.
function stake(uint256 tokenId) public virtual payable override {
StakerConstraints stakerConstraints_ = stakerConstraints;
if (stakerConstraints_ == StakerConstraints.CallerIsTxOrigin) {
if(_msgSender() != tx.origin) {
revert ERC721WrapperBase__SmartContractsNotPermittedToStake();
}
} else if (stakerConstraints_ == StakerConstraints.EOA) {
_requireAccountIsVerifiedEOA(_msgSender());
}
IERC721 wrappedCollection_ = IERC721(getWrappedCollectionAddress());
address tokenOwner = wrappedCollection_.ownerOf(tokenId);
if(tokenOwner != _msgSender()) {
revert ERC721WrapperBase__CallerNotOwnerOfWrappedToken();
}
_onStake(tokenId, msg.value);
emit Staked(tokenId, tokenOwner);
wrappedCollection_.transferFrom(tokenOwner, address(this), tokenId);
_doTokenMint(tokenOwner, tokenId);
}
/// @notice Allows holders of the wrapped ERC721 token to stake into this enhanced ERC721 token.
/// The out of the box enhancement is ERC721-C standard, but developers can extend the functionality of this
/// contract with additional powered up features.
///
/// Throws when staker constraints is `CallerIsTxOrigin` and the `to` address is not the tx.origin.
/// Throws when staker constraints is `EOA` and the `to` address has not verified their signature in the transfer
/// validator contract.
/// Throws when caller does not own the token id on the wrapped collection.
/// Throws when inheriting contract reverts in the _onStake function (for example, in a pay to stake scenario).
/// Throws when _mint function reverts (for example, when additional mint validation logic reverts).
/// Throws when transferFrom function reverts (e.g. if this contract does not have approval to transfer token).
///
/// Postconditions:
/// ---------------
/// The staker's token is now owned by this contract.
/// The `to` address has received a wrapper token on this contract with the same token id.
/// A `Staked` event has been emitted.
function stakeTo(uint256 tokenId, address to) public virtual payable override {
StakerConstraints stakerConstraints_ = stakerConstraints;
if (stakerConstraints_ == StakerConstraints.CallerIsTxOrigin) {
if(to != tx.origin) {
revert ERC721WrapperBase__SmartContractsNotPermittedToStake();
}
} else if (stakerConstraints_ == StakerConstraints.EOA) {
_requireAccountIsVerifiedEOA(to);
}
IERC721 wrappedCollection_ = IERC721(getWrappedCollectionAddress());
address tokenOwner = wrappedCollection_.ownerOf(tokenId);
if(tokenOwner != _msgSender()) {
revert ERC721WrapperBase__CallerNotOwnerOfWrappedToken();
}
_onStake(tokenId, msg.value);
emit Staked(tokenId, to);
wrappedCollection_.transferFrom(tokenOwner, address(this), tokenId);
_doTokenMint(to, tokenId);
}
/// @notice Allows holders of this wrapper ERC721 token to unstake and receive the original wrapped token.
///
/// Throws when caller does not own the token id of this wrapper collection.
/// Throws when inheriting contract reverts in the _onUnstake function (for example, in a pay to unstake scenario).
/// Throws when _burn function reverts (for example, when additional burn validation logic reverts).
/// Throws when transferFrom function reverts (should not be the case, unless wrapped token has additional transfer validation logic).
///
/// Postconditions:
/// ---------------
/// The wrapper token has been burned.
/// The wrapped token with the same token id has been transferred to the address that owned the wrapper token.
/// An `Unstaked` event has been emitted.
function unstake(uint256 tokenId) public virtual payable override {
address tokenOwner = _getOwnerOfToken(tokenId);
if(tokenOwner != _msgSender()) {
revert ERC721WrapperBase__CallerNotOwnerOfWrappingToken();
}
_onUnstake(tokenId, msg.value);
emit Unstaked(tokenId, tokenOwner);
_doTokenBurn(tokenId);
IERC721(getWrappedCollectionAddress()).transferFrom(address(this), tokenOwner, tokenId);
}
/// @notice Returns true if the specified token id is available to be unstaked, false otherwise.
/// @dev This should be overridden in most cases by inheriting contracts to implement the proper constraints.
/// In the base implementation, a token is available to be unstaked if the wrapped token is owned by this contract
/// and the wrapper token exists.
function canUnstake(uint256 tokenId) public virtual view override returns (bool) {
return _tokenExists(tokenId) && IERC721(getWrappedCollectionAddress()).ownerOf(tokenId) == address(this);
}
/// @notice Returns the staker constraints that are currently in effect.
function getStakerConstraints() public view override returns (StakerConstraints) {
return stakerConstraints;
}
/// @notice Returns the address of the wrapped ERC721 contract.
function getWrappedCollectionAddress() public virtual view override returns (address);
/// @dev Optional logic hook that fires during stake transaction.
function _onStake(uint256 /*tokenId*/, uint256 value) internal virtual {
if(value > 0) {
revert ERC721WrapperBase__DefaultImplementationOfStakeDoesNotAcceptPayment();
}
}
/// @dev Optional logic hook that fires during unstake transaction.
function _onUnstake(uint256 /*tokenId*/, uint256 value) internal virtual {
if(value > 0) {
revert ERC721WrapperBase__DefaultImplementationOfUnstakeDoesNotAcceptPayment();
}
}
function _validateWrappedCollectionAddress(address wrappedCollectionAddress_) internal view {
if(wrappedCollectionAddress_ == address(0) || wrappedCollectionAddress_.code.length == 0) {
revert ERC721WrapperBase__InvalidERC721Collection();
}
}
function _requireAccountIsVerifiedEOA(address account) internal view virtual;
function _doTokenMint(address to, uint256 tokenId) internal virtual;
function _doTokenBurn(uint256 tokenId) internal virtual;
function _getOwnerOfToken(uint256 tokenId) internal view virtual returns (address);
function _tokenExists(uint256 tokenId) internal view virtual returns (bool);
}
/**
* @title ERC721CW
* @author Limit Break, Inc.
* @notice Constructable ERC721C Wrapper Contract implementation
*/
abstract contract ERC721CW is ERC721WrapperBase, ERC721C {
IERC721 private immutable wrappedCollectionImmutable;
constructor(address wrappedCollectionAddress_) {
_validateWrappedCollectionAddress(wrappedCollectionAddress_);
wrappedCollectionImmutable = IERC721(wrappedCollectionAddress_);
}
/**
* @notice Indicates whether the contract implements the specified interface.
* @dev Overrides supportsInterface in ERC165.
* @param interfaceId The interface id
* @return true if the contract implements the specified interface, false otherwise
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(ICreatorTokenWrapperERC721).interfaceId ||
interfaceId == type(ICreatorToken).interfaceId ||
interfaceId == type(ICreatorTokenLegacy).interfaceId ||
super.supportsInterface(interfaceId);
}
function getWrappedCollectionAddress() public virtual view override returns (address) {
return address(wrappedCollectionImmutable);
}
function _requireAccountIsVerifiedEOA(address account) internal view virtual override {
address validator = getTransferValidator();
if(validator != address(0)) {
if(!IEOARegistry(validator).isVerifiedEOA(account)) {
revert ERC721WrapperBase__CallerSignatureNotVerifiedInEOARegistry();
}
}
}
function _doTokenMint(address to, uint256 tokenId) internal virtual override {
_mint(to, tokenId);
}
function _doTokenBurn(uint256 tokenId) internal virtual override {
_burn(tokenId);
}
function _getOwnerOfToken(uint256 tokenId) internal view virtual override returns (address) {
return ownerOf(tokenId);
}
function _tokenExists(uint256 tokenId) internal view virtual override returns (bool) {
return _exists(tokenId);
}
}
/**
* @title ERC721CWInitializable
* @author Limit Break, Inc.
* @notice Initializable ERC721C Wrapper Contract implementation to allow for EIP-1167 clones.
*/
abstract contract ERC721CWInitializable is ERC721WrapperBase, ERC721CInitializable {
error ERC721CWInitializable__AlreadyInitializedWrappedCollection();
/// @dev Points to an external ERC721 contract that will be wrapped via staking.
IERC721 private wrappedCollection;
bool private _wrappedCollectionInitialized;
function initializeWrappedCollectionAddress(address wrappedCollectionAddress_) public {
_requireCallerIsContractOwner();
if(_wrappedCollectionInitialized) {
revert ERC721CWInitializable__AlreadyInitializedWrappedCollection();
}
_wrappedCollectionInitialized = true;
_validateWrappedCollectionAddress(wrappedCollectionAddress_);
wrappedCollection = IERC721(wrappedCollectionAddress_);
}
/**
* @notice Indicates whether the contract implements the specified interface.
* @dev Overrides supportsInterface in ERC165.
* @param interfaceId The interface id
* @return true if the contract implements the specified interface, false otherwise
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(ICreatorTokenWrapperERC721).interfaceId ||
interfaceId == type(ICreatorToken).interfaceId ||
interfaceId == type(ICreatorTokenLegacy).interfaceId ||
super.supportsInterface(interfaceId);
}
/// @notice Returns the address of the wrapped ERC721 contract.
function getWrappedCollectionAddress() public virtual view override returns (address) {
return address(wrappedCollection);
}
function _requireAccountIsVerifiedEOA(address account) internal view virtual override {
address validator = getTransferValidator();
if(validator != address(0)) {
if(!IEOARegistry(validator).isVerifiedEOA(account)) {
revert ERC721WrapperBase__CallerSignatureNotVerifiedInEOARegistry();
}
}
}
function _doTokenMint(address to, uint256 tokenId) internal virtual override {
_mint(to, tokenId);
}
function _doTokenBurn(uint256 tokenId) internal virtual override {
_burn(tokenId);
}
function _getOwnerOfToken(uint256 tokenId) internal view virtual override returns (address) {
return ownerOf(tokenId);
}
function _tokenExists(uint256 tokenId) internal view virtual override returns (bool) {
return _exists(tokenId);
}
}