-
Notifications
You must be signed in to change notification settings - Fork 567
[Do Not Merge] benchmark tokenerc721 alt #625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
function initialize( | ||
address _defaultAdmin, | ||
string memory _name, | ||
string memory _symbol, |
Check notice
Code scanning / Slither
Local variable shadowing
/// @dev Initializes the contract, like a constructor. | ||
function initialize( | ||
address _defaultAdmin, | ||
string memory _name, |
Check notice
Code scanning / Slither
Local variable shadowing
contract TokenERC721Alt is | ||
Initializable, | ||
IThirdwebContract, | ||
IOwnable, | ||
IRoyalty, | ||
ReentrancyGuardUpgradeable, | ||
EIP712Upgradeable, | ||
ERC2771ContextUpgradeable, | ||
Multicall, | ||
AccessControlEnumerableUpgradeable, | ||
ERC721EnumerableUpgradeable, | ||
ITokenERC721Alt, | ||
NFTMetadata | ||
{ | ||
using ECDSAUpgradeable for bytes32; | ||
using StringsUpgradeable for uint256; | ||
|
||
bytes32 private constant MODULE_TYPE = bytes32("TokenERC721"); | ||
uint256 private constant VERSION = 1; | ||
|
||
bytes32 private constant TYPEHASH = | ||
keccak256("MintRequest(address to,uint128 validityStartTimestamp,uint128 validityEndTimestamp,bytes32 uid)"); | ||
|
||
/// @dev Only TRANSFER_ROLE holders can have tokens transferred from or to them, during restricted transfers. | ||
bytes32 private constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE"); | ||
/// @dev Only MINTER_ROLE holders can sign off on `MintRequest`s. | ||
bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE"); | ||
/// @dev Only METADATA_ROLE holders can update NFT metadata. | ||
bytes32 private constant METADATA_ROLE = keccak256("METADATA_ROLE"); | ||
|
||
/// @dev Max bps in the thirdweb system | ||
uint256 private constant MAX_BPS = 10_000; | ||
|
||
/// @dev Owner of the contract (purpose: OpenSea compatibility, etc.) | ||
address private _owner; | ||
|
||
/// @dev The token ID of the next token to mint. | ||
uint256 public nextTokenIdToMint; | ||
|
||
/// @dev The recipient of who gets the royalty. | ||
address private royaltyRecipient; | ||
|
||
/// @dev The percentage of royalty how much royalty in basis points. | ||
uint128 private royaltyBps; | ||
|
||
/// @dev Contract level metadata. | ||
string public contractURI; | ||
|
||
/// @dev Mapping from mint request UID => whether the mint request is processed. | ||
mapping(bytes32 => bool) private minted; | ||
|
||
/// @dev Token ID => royalty recipient and bps for token | ||
mapping(uint256 => RoyaltyInfo) private royaltyInfoForToken; | ||
|
||
constructor() initializer {} | ||
|
||
/// @dev Initializes the contract, like a constructor. | ||
function initialize( | ||
address _defaultAdmin, | ||
string memory _name, | ||
string memory _symbol, | ||
string memory _contractURI, | ||
address[] memory _trustedForwarders, | ||
address _royaltyRecipient, | ||
uint128 _royaltyBps | ||
) external initializer { | ||
// Initialize inherited contracts, most base-like -> most derived. | ||
__ReentrancyGuard_init(); | ||
__EIP712_init("TokenERC721", "1"); | ||
__ERC2771Context_init(_trustedForwarders); | ||
__ERC721_init(_name, _symbol); | ||
|
||
// Initialize this contract's state. | ||
royaltyRecipient = _royaltyRecipient; | ||
royaltyBps = _royaltyBps; | ||
contractURI = _contractURI; | ||
|
||
_owner = _defaultAdmin; | ||
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin); | ||
_setupRole(MINTER_ROLE, _defaultAdmin); | ||
|
||
_setupRole(METADATA_ROLE, _defaultAdmin); | ||
_setRoleAdmin(METADATA_ROLE, METADATA_ROLE); | ||
|
||
_setupRole(TRANSFER_ROLE, _defaultAdmin); | ||
_setupRole(TRANSFER_ROLE, address(0)); | ||
|
||
emit DefaultRoyalty(_royaltyRecipient, _royaltyBps); | ||
} | ||
|
||
/// ===== Public functions ===== | ||
|
||
/// @dev Returns the module 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); | ||
} | ||
|
||
/** | ||
* @dev Returns the address of the current owner. | ||
*/ | ||
function owner() public view returns (address) { | ||
return hasRole(DEFAULT_ADMIN_ROLE, _owner) ? _owner : address(0); | ||
} | ||
|
||
/// @dev Verifies that a mint request is signed by an account holding MINTER_ROLE (at the time of the function call). | ||
function verify(MintRequest calldata _req, bytes calldata _signature) public view returns (bool, address) { | ||
address signer = recoverAddress(_req, _signature); | ||
return (!minted[_req.uid] && hasRole(MINTER_ROLE, signer), signer); | ||
} | ||
|
||
/// @dev Returns the URI for a tokenId | ||
function tokenURI(uint256 _tokenId) public view override returns (string memory) { | ||
return _tokenURI[_tokenId]; | ||
} | ||
|
||
/// @dev Lets an account with MINTER_ROLE mint an NFT. | ||
function mintTo(address _to, string calldata _uri) external nonReentrant onlyRole(MINTER_ROLE) returns (uint256) { | ||
// `_mintTo` is re-used. `mintTo` just adds a minter role check. | ||
return _mintTo(_to); | ||
} | ||
|
||
/// ===== External functions ===== | ||
|
||
/// @dev See EIP-2981 | ||
function royaltyInfo( | ||
uint256 tokenId, | ||
uint256 salePrice | ||
) external view virtual returns (address receiver, uint256 royaltyAmount) { | ||
(address recipient, uint256 bps) = getRoyaltyInfoForToken(tokenId); | ||
receiver = recipient; | ||
royaltyAmount = (salePrice * bps) / MAX_BPS; | ||
} | ||
|
||
/// @dev Mints an NFT according to the provided mint request. | ||
function mintWithSignature( | ||
MintRequest calldata _req, | ||
bytes calldata _signature | ||
) external payable nonReentrant returns (uint256 tokenIdMinted) { | ||
address signer = verifyRequest(_req, _signature); | ||
address receiver = _req.to; | ||
|
||
tokenIdMinted = _mintTo(receiver); | ||
|
||
// if (_req.to != address(0)) { | ||
// royaltyInfoForToken[tokenIdMinted] = RoyaltyInfo({ recipient: _req.to, bps: 100 }); | ||
// } | ||
|
||
// collectPrice(_req); | ||
|
||
emit TokensMintedWithSignature(signer, receiver, tokenIdMinted, _req); | ||
} | ||
|
||
/// @dev Collects and distributes the primary sale value of tokens being claimed. | ||
function collectPrice(MintRequest calldata _req) internal { | ||
if (_req.uid != bytes32(0)) { | ||
require(_req.uid != bytes32(0), "!Value"); | ||
return; | ||
} | ||
} | ||
|
||
// ===== Setter functions ===== | ||
|
||
/// @dev Lets a module admin update the royalty bps and recipient. | ||
function setDefaultRoyaltyInfo( | ||
address _royaltyRecipient, | ||
uint256 _royaltyBps | ||
) external onlyRole(DEFAULT_ADMIN_ROLE) { | ||
require(_royaltyBps <= MAX_BPS, "exceed royalty bps"); | ||
|
||
royaltyRecipient = _royaltyRecipient; | ||
royaltyBps = uint128(_royaltyBps); | ||
|
||
emit DefaultRoyalty(_royaltyRecipient, _royaltyBps); | ||
} | ||
|
||
/// @dev Lets a module admin set the royalty recipient for a particular token Id. | ||
function setRoyaltyInfoForToken( | ||
uint256 _tokenId, | ||
address _recipient, | ||
uint256 _bps | ||
) external onlyRole(DEFAULT_ADMIN_ROLE) { | ||
require(_bps <= MAX_BPS, "exceed royalty bps"); | ||
|
||
royaltyInfoForToken[_tokenId] = RoyaltyInfo({ recipient: _recipient, bps: _bps }); | ||
|
||
emit RoyaltyForToken(_tokenId, _recipient, _bps); | ||
} | ||
|
||
/// @dev Lets a module admin set a new owner for the contract. The new owner must be a module admin. | ||
function setOwner(address _newOwner) external onlyRole(DEFAULT_ADMIN_ROLE) { | ||
require(hasRole(DEFAULT_ADMIN_ROLE, _newOwner), "new owner not module admin."); | ||
address _prevOwner = _owner; | ||
_owner = _newOwner; | ||
|
||
emit OwnerUpdated(_prevOwner, _newOwner); | ||
} | ||
|
||
/// @dev Lets a module admin set the URI for contract-level metadata. | ||
function setContractURI(string calldata _uri) external onlyRole(DEFAULT_ADMIN_ROLE) { | ||
contractURI = _uri; | ||
} | ||
|
||
/// ===== Getter functions ===== | ||
|
||
/// @dev Returns the platform fee bps and recipient. | ||
function getDefaultRoyaltyInfo() external view returns (address, uint16) { | ||
return (royaltyRecipient, uint16(royaltyBps)); | ||
} | ||
|
||
/// @dev Returns the royalty recipient for a particular token Id. | ||
function getRoyaltyInfoForToken(uint256 _tokenId) public view returns (address, uint16) { | ||
RoyaltyInfo memory royaltyForToken = royaltyInfoForToken[_tokenId]; | ||
|
||
return | ||
royaltyForToken.recipient == address(0) | ||
? (royaltyRecipient, uint16(royaltyBps)) | ||
: (royaltyForToken.recipient, uint16(royaltyForToken.bps)); | ||
} | ||
|
||
/// ===== Internal functions ===== | ||
|
||
/// @dev Mints an NFT to `to` | ||
function _mintTo(address _to) internal returns (uint256 tokenIdToMint) { | ||
tokenIdToMint = nextTokenIdToMint; | ||
nextTokenIdToMint += 1; | ||
|
||
_safeMint(_to, tokenIdToMint); | ||
|
||
emit TokensMinted(_to, tokenIdToMint, ""); | ||
} | ||
|
||
/// @dev Returns the address of the signer of the mint request. | ||
function recoverAddress(MintRequest calldata _req, bytes calldata _signature) private view returns (address) { | ||
return _hashTypedDataV4(keccak256(_encodeRequest(_req))).recover(_signature); | ||
} | ||
|
||
/// @dev Resolves 'stack too deep' error in `recoverAddress`. | ||
function _encodeRequest(MintRequest calldata _req) private pure returns (bytes memory) { | ||
return abi.encode(TYPEHASH, _req.to, _req.validityStartTimestamp, _req.validityEndTimestamp, _req.uid); | ||
} | ||
|
||
/// @dev Verifies that a mint request is valid. | ||
function verifyRequest(MintRequest calldata _req, bytes calldata _signature) internal returns (address) { | ||
(bool success, address signer) = verify(_req, _signature); | ||
require(success, "invalid signature"); | ||
|
||
require( | ||
_req.validityStartTimestamp <= block.timestamp && _req.validityEndTimestamp >= block.timestamp, | ||
"request expired" | ||
); | ||
require(_req.to != address(0), "recipient undefined"); | ||
|
||
minted[_req.uid] = true; | ||
|
||
return signer; | ||
} | ||
|
||
/// ===== Low-level overrides ===== | ||
|
||
/// @dev Burns `tokenId`. See {ERC721-_burn}. | ||
function burn(uint256 tokenId) public virtual { | ||
//solhint-disable-next-line max-line-length | ||
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Burnable: caller is not owner nor approved"); | ||
_burn(tokenId); | ||
} | ||
|
||
/// @dev See {ERC721-_beforeTokenTransfer}. | ||
function _beforeTokenTransfer( | ||
address from, | ||
address to, | ||
uint256 tokenId, | ||
uint256 batchSize | ||
) internal virtual override(ERC721EnumerableUpgradeable) { | ||
super._beforeTokenTransfer(from, to, tokenId, batchSize); | ||
|
||
// 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), "restricted to TRANSFER_ROLE holders"); | ||
} | ||
} | ||
|
||
/// @dev Returns whether metadata can be set in the given execution context. | ||
function _canSetMetadata() internal view virtual override returns (bool) { | ||
return hasRole(METADATA_ROLE, _msgSender()); | ||
} | ||
|
||
/// @dev Returns whether metadata can be frozen in the given execution context. | ||
function _canFreezeMetadata() internal view virtual override returns (bool) { | ||
return hasRole(METADATA_ROLE, _msgSender()); | ||
} | ||
|
||
function supportsInterface( | ||
bytes4 interfaceId | ||
) | ||
public | ||
view | ||
virtual | ||
override(AccessControlEnumerableUpgradeable, ERC721EnumerableUpgradeable, IERC165Upgradeable, IERC165) | ||
returns (bool) | ||
{ | ||
return super.supportsInterface(interfaceId) || interfaceId == type(IERC2981Upgradeable).interfaceId; | ||
} | ||
|
||
function _msgSender() | ||
internal | ||
view | ||
virtual | ||
override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) | ||
returns (address sender) | ||
{ | ||
return ERC2771ContextUpgradeable._msgSender(); | ||
} | ||
|
||
function _msgData() | ||
internal | ||
view | ||
virtual | ||
override(ContextUpgradeable, ERC2771ContextUpgradeable) | ||
returns (bytes calldata) | ||
{ | ||
return ERC2771ContextUpgradeable._msgData(); | ||
} | ||
} |
Check warning
Code scanning / Slither
Missing inheritance
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #625 +/- ##
==========================================
- Coverage 64.72% 64.58% -0.14%
==========================================
Files 216 217 +1
Lines 6702 6772 +70
==========================================
+ Hits 4338 4374 +36
- Misses 2364 2398 +34 ☔ View full report in Codecov by Sentry. |
No description provided.