contract CryptoCoven is ERC721, IERC2981, Ownable, ReentrancyGuard {
using Counters for Counters.Counter;
using Strings for uint256;
Counters.Counter private tokenCounter;
修改器让代码更简洁和清晰
合约中使用修改器对权限进行控制,其中包括:
publicSaleActive 公开销售状态
communitySaleActive 社区销售状态
maxWitchesPerWallet 每个钱包最大 token 数量
canMintWitches 控制token总数量
canGiftWitches
isCorrectPayment 判断购买时价格是否正确
isValidMerkleProof 用于白名单机制中的 Merkle 验证
这些修改器可以使得权限控制更简便,代码的可读性也大大提升。
// ============ ACCESS CONTROL/SANITY MODIFIERS ============
modifier publicSaleActive() {
require(isPublicSaleActive, "Public sale is not open");
_;
}
modifier communitySaleActive() {
require(isCommunitySaleActive, "Community sale is not open");
_;
}
modifier maxWitchesPerWallet(uint256 numberOfTokens) {
require(
balanceOf(msg.sender) + numberOfTokens <= MAX_WITCHES_PER_WALLET,
"Max witches to mint is three"
);
_;
}
modifier canMintWitches(uint256 numberOfTokens) {
require(
tokenCounter.current() + numberOfTokens <=
maxWitches - maxGiftedWitches,
"Not enough witches remaining to mint"
);
_;
}
modifier canGiftWitches(uint256 num) {
require(
numGiftedWitches + num <= maxGiftedWitches,
"Not enough witches remaining to gift"
);
require(
tokenCounter.current() + num <= maxWitches,
"Not enough witches remaining to mint"
);
_;
}
modifier isCorrectPayment(uint256 price, uint256 numberOfTokens) {
require(
price * numberOfTokens == msg.value,
"Incorrect ETH value sent"
);
_;
}
modifier isValidMerkleProof(bytes32[] calldata merkleProof, bytes32 root) {
require(
MerkleProof.verify(
merkleProof,
root,
keccak256(abi.encodePacked(msg.sender))
),
"Address does not exist in list"
);
_;
}
/**
* @dev Override isApprovedForAll to allowlist user's OpenSea proxy accounts to enable gas-less listings.
*/
function isApprovedForAll(address owner, address operator)
public
view
override
returns (bool)
{
// Get a reference to OpenSea's proxy registry contract by instantiating
// the contract using the already existing address.
ProxyRegistry proxyRegistry = ProxyRegistry(
openSeaProxyRegistryAddress
);
if (
isOpenSeaProxyActive &&
address(proxyRegistry.proxies(owner)) == operator
) {
return true;
}
return super.isApprovedForAll(owner, operator);
}
...
// These contract definitions are used to create a reference to the OpenSea
// ProxyRegistry contract by using the registry's address (see isApprovedForAll).
contract OwnableDelegateProxy {
}
contract ProxyRegistry {
mapping(address => OwnableDelegateProxy) public proxies;
}
// function to disable gasless listings for security in case
// opensea ever shuts down or is compromised
function setIsOpenSeaProxyActive(bool _isOpenSeaProxyActive)
external
onlyOwner
{
isOpenSeaProxyActive = _isOpenSeaProxyActive;
}
interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
加密女巫实现复写这个方法是因为它额外实现了 EIP2981 这个标准,需要指出。
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721, IERC165)
returns (bool)
{
return
interfaceId == type(IERC2981).interfaceId ||
super.supportsInterface(interfaceId);
}
本文主要是对 @mannynotfound 的推文 https://twitter.com/mannynotfound/status/1470535464922845187 的整理和补充。
加密女巫的合约代码堪称艺术品。代码出自工程师 Matthew Di Ferrante(@matthewdif),涉及 gas 优化,修改器以及 Opensea 预授权等诸多优化措施,对于学习 NFT 合约是个很好的参考材料。
基本情况
名称;Crypto Coven
符号: WITCH
合约地址:0x5180db8f5c931aae63c74266b211f580155ecac8
合约代码地址:https://etherscan.io/address/0x5180db8f5c931aae63c74266b211f580155ecac8#code
Solidity版本:
^0.8.0
Banner
这个 banner 可以体会到项目方想要做的不是像 Crypto Punks 或者其他像素风格 NFT 一样的作品。
避免使用 ERC721Enumerable
使用
ERC721Enumerable
会带来大量 gas 消耗,合约中使用ERC721 + Counters
的方式节省 Gas。主要原因是由于totalSupply()
函数的使用。详细可以阅读文章:Cut Minting Gas Costs By Up To 70% With One Smart Contract Tweak
修改器让代码更简洁和清晰
合约中使用修改器对权限进行控制,其中包括:
publicSaleActive
公开销售状态communitySaleActive
社区销售状态maxWitchesPerWallet
每个钱包最大 token 数量canMintWitches
控制token总数量canGiftWitches
isCorrectPayment
判断购买时价格是否正确isValidMerkleProof
用于白名单机制中的 Merkle 验证这些修改器可以使得权限控制更简便,代码的可读性也大大提升。
NFT 素材的存储
NFT 项目都需要包含图片的存储,合约将 NFT 对应的元信息存储在 IPFS 中,并将对应的图片存储都在 Amazon S3 存储中。
比如
tokenId
为1
的 NFT,对应的tokenURI
为ipfs://QmZHKZDavkvNfA9gSAg7HALv8jF7BJaKjUc9U2LSuvUySB/1.json
,在 IPFS 中可以看到这里面的内容为:其中包含女巫的ID,名称,图片地址,属性等信息。
不得不说,如果 Amazon S3 出问题了,可能这些图片就没法显示了。
使用 Merkle 证明实现白名单机制
对于预售,项目方使用白名单方式进行,而对于白名单验证,合约中使用 Merkle 证明的方式进行验证。
在 mint 时,只需发送正确的 Merkle 证明来验证即可实现白名单功能,这个方法不仅效率高,而且省去了在合约中存储所有白名单地址造成的 Gas 消耗。
详细细节可以参考我之前的一篇文章:
预先批准 Opensea 合约
可以看到在 OpenSea 上列出这些 NFT 费用为 0 gas,因为合约预先批准了 OpenSea 合约以节省用户的 gas,同时合约还包括一个紧急功能来消除这种行为!
为了防止 Opensea 关闭或者被入侵,合约可以通过
setIsOpenSeaProxyActive
方法关闭预先批准。ERC165
这是一种发布并能检测到一个智能合约实现了什么接口的标准,用于实现对合约实现的接口的查询。这个标准需要实现
suppoetsInterface
方法:加密女巫实现复写这个方法是因为它额外实现了 EIP2981 这个标准,需要指出。
EIP2981:NFT 版税标准
EIP-2981 实现了标准化的版税信息检索,可被任何类型的 NFT 市场接受。EIP-2981 支持所有市场检索特定 NFT 的版税支付信息,从而实现无论 NFT 在哪个市场出售或转售都可以实现准确的版税支付。
NFT 市场和个人可通过检索版税支付信息
royaltyInfo()
来实施该标准,它指定为特定的 NFT 销售价格向指定的单一地址支付特定比例的金额。对于特定的tokenId
和salePrice
,在请求时需提供一个版税接收者的地址和要支付的预期版税金额(百分比表示)。女巫合约规定了 5% 的版税,但是这个标准并不是强制性的,需要靠市场去实施此标准。
不太确定此函数中的注释
See {IERC165-royaltyInfo}.
是否正确,需要确认。其他细节
没有
tokensOfOwner
方法可能是基于女巫NFT的具体场景与优化 Gas 做的权衡,查询 token 所有者的功能需要靠 Opensea 的 API 或者 The Graph 去实现。
在没有外部调用的函数中也加了
nonReentrant
msg.sender
可能是合约对
onlyOwner
也加了nonReentrant
,避免可能的被利用。参考