Skip to content

Commit 92dca09

Browse files
authored
Create OpenEdition ERC-721 (#359)
* Make all top level functions virtual on ContractKit bases * pkg update * Make MAX_BPS private in Marketplace plugins * Create OpenCollectionERC721 * Add flat platform fee option for OpenCollectionERC721 * Replace extension LazyMintSharedMetadata with SharedMetadata * Rename OpenCollection -> OpenEdition * Add tests for OpenEditionERC721 * solhint disable for using single quotes * NFTMetadataRenderer: solhint disable quotes * Remove redundant call * docs update
1 parent 30adb10 commit 92dca09

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4554
-53
lines changed

contracts/OpenEditionERC721.sol

+344
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
/// @author thirdweb
5+
6+
// $$\ $$\ $$\ $$\ $$\
7+
// $$ | $$ | \__| $$ | $$ |
8+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
9+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
10+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
11+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
12+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
13+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
14+
15+
// ========== External imports ==========
16+
17+
import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";
18+
import "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol";
19+
20+
import "./eip/ERC721AVirtualApproveUpgradeable.sol";
21+
22+
// ========== Internal imports ==========
23+
24+
import "./openzeppelin-presets/metatx/ERC2771ContextUpgradeable.sol";
25+
import "./lib/CurrencyTransferLib.sol";
26+
27+
// ========== Features ==========
28+
29+
import "./extension/Multicall.sol";
30+
import "./extension/ContractMetadata.sol";
31+
import "./extension/PlatformFee.sol";
32+
import "./extension/Royalty.sol";
33+
import "./extension/PrimarySale.sol";
34+
import "./extension/Ownable.sol";
35+
import "./extension/DelayedReveal.sol";
36+
import "./extension/SharedMetadata.sol";
37+
import "./extension/PermissionsEnumerable.sol";
38+
import "./extension/Drop.sol";
39+
40+
// OpenSea operator filter
41+
import "./extension/DefaultOperatorFiltererUpgradeable.sol";
42+
43+
contract OpenEditionERC721 is
44+
Initializable,
45+
ContractMetadata,
46+
PlatformFee,
47+
Royalty,
48+
PrimarySale,
49+
Ownable,
50+
SharedMetadata,
51+
PermissionsEnumerable,
52+
Drop,
53+
ERC2771ContextUpgradeable,
54+
Multicall,
55+
DefaultOperatorFiltererUpgradeable,
56+
ERC721AUpgradeable
57+
{
58+
using StringsUpgradeable for uint256;
59+
60+
/*///////////////////////////////////////////////////////////////
61+
State variables
62+
//////////////////////////////////////////////////////////////*/
63+
64+
/// @dev Only transfers to or from TRANSFER_ROLE holders are valid, when transfers are restricted.
65+
bytes32 private transferRole;
66+
/// @dev Only MINTER_ROLE holders can sign off on `MintRequest`s and lazy mint tokens.
67+
bytes32 private minterRole;
68+
69+
/// @dev Max bps in the thirdweb system.
70+
uint256 private constant MAX_BPS = 10_000;
71+
72+
/*///////////////////////////////////////////////////////////////
73+
Constructor + initializer logic
74+
//////////////////////////////////////////////////////////////*/
75+
76+
constructor() initializer {}
77+
78+
/// @dev Initiliazes the contract, like a constructor.
79+
function initialize(
80+
address _defaultAdmin,
81+
string memory _name,
82+
string memory _symbol,
83+
string memory _contractURI,
84+
address[] memory _trustedForwarders,
85+
address _saleRecipient,
86+
address _royaltyRecipient,
87+
uint128 _royaltyBps,
88+
uint128 _platformFeeBps,
89+
address _platformFeeRecipient
90+
) external initializer {
91+
bytes32 _transferRole = keccak256("TRANSFER_ROLE");
92+
bytes32 _minterRole = keccak256("MINTER_ROLE");
93+
94+
// Initialize inherited contracts, most base-like -> most derived.
95+
__ERC2771Context_init(_trustedForwarders);
96+
__ERC721A_init(_name, _symbol);
97+
__DefaultOperatorFilterer_init();
98+
99+
_setupContractURI(_contractURI);
100+
_setupOwner(_defaultAdmin);
101+
_setOperatorRestriction(true);
102+
103+
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
104+
_setupRole(_minterRole, _defaultAdmin);
105+
_setupRole(_transferRole, _defaultAdmin);
106+
_setupRole(_transferRole, address(0));
107+
108+
_setupPlatformFeeInfo(_platformFeeRecipient, _platformFeeBps);
109+
_setupDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps);
110+
_setupPrimarySaleRecipient(_saleRecipient);
111+
112+
transferRole = _transferRole;
113+
minterRole = _minterRole;
114+
}
115+
116+
/*///////////////////////////////////////////////////////////////
117+
ERC 165 / 721 / 2981 logic
118+
//////////////////////////////////////////////////////////////*/
119+
120+
/// @dev Returns the URI for a given tokenId.
121+
function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
122+
if (!_exists(_tokenId)) {
123+
revert("OpenCollectionERC721: URI query for nonexistent token.");
124+
}
125+
126+
return _getURIFromSharedMetadata(_tokenId);
127+
}
128+
129+
/// @dev See ERC 165
130+
function supportsInterface(bytes4 interfaceId)
131+
public
132+
view
133+
virtual
134+
override(ERC721AUpgradeable, IERC165)
135+
returns (bool)
136+
{
137+
return super.supportsInterface(interfaceId) || type(IERC2981Upgradeable).interfaceId == interfaceId;
138+
}
139+
140+
/// @dev The start token ID for the contract.
141+
function _startTokenId() internal pure override returns (uint256) {
142+
return 1;
143+
}
144+
145+
/*///////////////////////////////////////////////////////////////
146+
Internal functions
147+
//////////////////////////////////////////////////////////////*/
148+
149+
/// @dev Collects and distributes the primary sale value of NFTs being claimed.
150+
function _collectPriceOnClaim(
151+
address _primarySaleRecipient,
152+
uint256 _quantityToClaim,
153+
address _currency,
154+
uint256 _pricePerToken
155+
) internal override {
156+
if (_pricePerToken == 0) {
157+
return;
158+
}
159+
160+
uint256 totalPrice = _quantityToClaim * _pricePerToken;
161+
address saleRecipient = _primarySaleRecipient == address(0) ? primarySaleRecipient() : _primarySaleRecipient;
162+
163+
if (_currency == CurrencyTransferLib.NATIVE_TOKEN) {
164+
if (msg.value != totalPrice) {
165+
revert("!Price");
166+
}
167+
}
168+
169+
uint256 fees;
170+
address feeRecipient;
171+
172+
PlatformFeeType feeType = getPlatformFeeType();
173+
if (feeType == PlatformFeeType.Flat) {
174+
(feeRecipient, fees) = getFlatPlatformFeeInfo();
175+
} else {
176+
uint16 platformFeeBps;
177+
(feeRecipient, platformFeeBps) = getPlatformFeeInfo();
178+
fees = (totalPrice * platformFeeBps) / MAX_BPS;
179+
}
180+
181+
require(totalPrice >= fees, "Price < fees");
182+
183+
CurrencyTransferLib.transferCurrency(_currency, _msgSender(), feeRecipient, fees);
184+
CurrencyTransferLib.transferCurrency(_currency, _msgSender(), saleRecipient, totalPrice - fees);
185+
}
186+
187+
/// @dev Transfers the NFTs being claimed.
188+
function _transferTokensOnClaim(address _to, uint256 _quantityBeingClaimed)
189+
internal
190+
override
191+
returns (uint256 startTokenId)
192+
{
193+
startTokenId = _currentIndex;
194+
_safeMint(_to, _quantityBeingClaimed);
195+
}
196+
197+
/// @dev Checks whether platform fee info can be set in the given execution context.
198+
function _canSetPlatformFeeInfo() internal view override returns (bool) {
199+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
200+
}
201+
202+
/// @dev Checks whether primary sale recipient can be set in the given execution context.
203+
function _canSetPrimarySaleRecipient() internal view override returns (bool) {
204+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
205+
}
206+
207+
/// @dev Checks whether owner can be set in the given execution context.
208+
function _canSetOwner() internal view override returns (bool) {
209+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
210+
}
211+
212+
/// @dev Checks whether royalty info can be set in the given execution context.
213+
function _canSetRoyaltyInfo() internal view override returns (bool) {
214+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
215+
}
216+
217+
/// @dev Checks whether contract metadata can be set in the given execution context.
218+
function _canSetContractURI() internal view override returns (bool) {
219+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
220+
}
221+
222+
/// @dev Checks whether platform fee info can be set in the given execution context.
223+
function _canSetClaimConditions() internal view override returns (bool) {
224+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
225+
}
226+
227+
/// @dev Returns whether lazy minting can be done in the given execution context.
228+
function _canSetSharedMetadata() internal view virtual override returns (bool) {
229+
return hasRole(minterRole, _msgSender());
230+
}
231+
232+
/// @dev Returns whether operator restriction can be set in the given execution context.
233+
function _canSetOperatorRestriction() internal virtual override returns (bool) {
234+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
235+
}
236+
237+
/*///////////////////////////////////////////////////////////////
238+
Miscellaneous
239+
//////////////////////////////////////////////////////////////*/
240+
241+
/**
242+
* Returns the total amount of tokens minted in the contract.
243+
*/
244+
function totalMinted() external view returns (uint256) {
245+
unchecked {
246+
return _currentIndex - _startTokenId();
247+
}
248+
}
249+
250+
/// @dev The tokenId of the next NFT that will be minted / lazy minted.
251+
function nextTokenIdToMint() external view returns (uint256) {
252+
return _currentIndex;
253+
}
254+
255+
/// @dev The next token ID of the NFT that can be claimed.
256+
function nextTokenIdToClaim() external view returns (uint256) {
257+
return _currentIndex;
258+
}
259+
260+
/// @dev Burns `tokenId`. See {ERC721-_burn}.
261+
function burn(uint256 tokenId) external virtual {
262+
// note: ERC721AUpgradeable's `_burn(uint256,bool)` internally checks for token approvals.
263+
_burn(tokenId, true);
264+
}
265+
266+
/// @dev See {ERC721-_beforeTokenTransfer}.
267+
function _beforeTokenTransfers(
268+
address from,
269+
address to,
270+
uint256 startTokenId,
271+
uint256 quantity
272+
) internal virtual override {
273+
super._beforeTokenTransfers(from, to, startTokenId, quantity);
274+
275+
// if transfer is restricted on the contract, we still want to allow burning and minting
276+
if (!hasRole(transferRole, address(0)) && from != address(0) && to != address(0)) {
277+
if (!hasRole(transferRole, from) && !hasRole(transferRole, to)) {
278+
revert("!Transfer-Role");
279+
}
280+
}
281+
}
282+
283+
/// @dev See {ERC721-setApprovalForAll}.
284+
function setApprovalForAll(address operator, bool approved) public override onlyAllowedOperatorApproval(operator) {
285+
super.setApprovalForAll(operator, approved);
286+
}
287+
288+
/// @dev See {ERC721-approve}.
289+
function approve(address operator, uint256 tokenId) public override onlyAllowedOperatorApproval(operator) {
290+
super.approve(operator, tokenId);
291+
}
292+
293+
/// @dev See {ERC721-_transferFrom}.
294+
function transferFrom(
295+
address from,
296+
address to,
297+
uint256 tokenId
298+
) public override(ERC721AUpgradeable) onlyAllowedOperator(from) {
299+
super.transferFrom(from, to, tokenId);
300+
}
301+
302+
/// @dev See {ERC721-_safeTransferFrom}.
303+
function safeTransferFrom(
304+
address from,
305+
address to,
306+
uint256 tokenId
307+
) public override(ERC721AUpgradeable) onlyAllowedOperator(from) {
308+
super.safeTransferFrom(from, to, tokenId);
309+
}
310+
311+
/// @dev See {ERC721-_safeTransferFrom}.
312+
function safeTransferFrom(
313+
address from,
314+
address to,
315+
uint256 tokenId,
316+
bytes memory data
317+
) public override(ERC721AUpgradeable) onlyAllowedOperator(from) {
318+
super.safeTransferFrom(from, to, tokenId, data);
319+
}
320+
321+
function _dropMsgSender() internal view virtual override returns (address) {
322+
return _msgSender();
323+
}
324+
325+
function _msgSender()
326+
internal
327+
view
328+
virtual
329+
override(ContextUpgradeable, ERC2771ContextUpgradeable)
330+
returns (address sender)
331+
{
332+
return ERC2771ContextUpgradeable._msgSender();
333+
}
334+
335+
function _msgData()
336+
internal
337+
view
338+
virtual
339+
override(ContextUpgradeable, ERC2771ContextUpgradeable)
340+
returns (bytes calldata)
341+
{
342+
return ERC2771ContextUpgradeable._msgData();
343+
}
344+
}

0 commit comments

Comments
 (0)