代币门控的概念用于限制访问,并为特定代币或一组代币的持有者提供独家内容、权利或成员资格。智能合约应该通过自动和手动来进行测试。使用HardHat或Truffle可以编写一组测试,以确保合约及其功能会按预期工作,最重要的是确保合约的安全性很高。
代币门控的概念用于限制访问,并为特定代币或一组代币的持有者提供独家内容、权利或成员资格。
在这个场景中,我们将编写一个简单的智能合约,它在提供对NFT社区的成员访问时实现了相同的概念。
在这个社区中,新成员通过从社区中购买至少一个NFT来参加,这给了他们出售自己的NFT的权利,以及对任何已发布的NFT发表评论的权利。
我们将跳过所有的安装和设置,因为这不是本文的重点,但是需要安装的最相关的库是@openzeppelin。
首先,我们将设置Solidity版本,并从Openzeppelin导入两个重要的合约,这两个合约是实现ERC721所必需的。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
ERC721URIStorage.sol是一个ERC721代币,带有基于存储的代币URI管理,Counters.sol这是一个实用程序,将帮助我们增加代币ID。
接下来,我们将创建PrivateMarket合约并从ERC721URIStorage继承,声明计数器和两个主要结构来存储代币数据和消息。
contract PrivateMarket is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
address private _owner;
struct TokenData {
uint256 price;
string name;
string image;
uint256 tokenId;
}
struct Message {
address sender;
string message;
uint date;
}
}
我们声明_owner变量来允许合约所有者发布第一组NFT,并允许社区成员购买并获得初始发布权。
在我们的构造函数中,我们将把_owner的值初始化为发布智能合约的地址。
contract PrivateMarket is ERC721URIStorage {
constructor() ERC721("NFTPrivateMarket", "NFTPM") {
_owner = msg.sender;
}
}
在我们创建了上面的框架之后,我们将声明一组变量来保存和映射代币、代币数据、所有者和附加到代币的消息:
contract PrivateMarket is ERC721URIStorage {
// An array to store the data of all the tokens for a quick listing of all NFTs
TokenData[] public tokensData;
// Mapping of owners to an array of all the tokens they own
mapping(address => uint256[]) private ownerByTokenId;
// Mapping of tokenIds to the owner of the token
mapping(uint256 => address) private tokenIdByOwner;
// Mapping of tokenIds to the data of the token
mapping(uint256 => TokenData) private tokenIdByTokenData;
// Mapping of tokenIds to the comments of the token
mapping(uint256 => Message[]) private messages;
}
TokensData将保存所有代币的数据,并将返回以在前端列出所有代币。
ownerByTokenId是所有者到其代币ID的映射,该代ID将用于检索属于某个地址的所有代币,或检查给定地址是否拥有代币,以便授予发布或评论的权利。
为了验证访问权限,我们将创建ownsToken修饰符,它只允许拥有代币的地址或_owner在要被调用的函数中执行操作。
contract PrivateMarket is ERC721URIStorage {
modifier ownsToken() {
require(
ownerByTokenId[msg.sender].length > 0 || msg.sender == _owner,
"You must own a token"
);
_;
}
}
使用ownsToken修饰符,就可以创建合约中最重要的功能之一,它生成一个新的NFT,并将代币分配给发送者地址。
contract PrivateMarket is ERC721URIStorage {
function createToken(
string memory tokenURI,
uint256 price,
string memory name
) public ownsToken returns (uint256) {
// Increment to the new tokenId
_tokenIds.increment();
// Mint a new token
uint256 currentId = _tokenIds.current();
_mint(msg.sender, currentId);
_setTokenURI(currentId, tokenURI);
// Assign the token to the owner
ownerByTokenId[msg.sender].push(currentId);
// Store the data of the token
TokenData memory tokenData = TokenData(
price,
name,
tokenURI,
currentId
);
tokenIdByTokenData[currentId] = tokenData;
tokensData.push(tokenData);
// Map the token to the owner
tokenIdByOwner[currentId] = msg.sender;
// Get token transfer approval
_approve(msg.sender, currentId);
return currentId;
}
}
为了购买代币,发送者将向purchaseToken函数提起一个交易,作为回报,该函数将代币转移到发送者那边,并向代币所有者付款。
contract PrivateMarket is ERC721URIStorage {
function purchaseToken(uint256 tokenId) public payable returns (uint256) {
// Get the token price
TokenData memory tokenData = tokenIdByTokenData[tokenId];
uint256 price = tokenData.price;
require(msg.value == price, "You must pay the full price");
// Get the token owner
address owner = tokenIdByOwner[tokenId];
require(owner != msg.sender, "You cannot buy your own token");
// Transfer the token
_transfer(owner, msg.sender, tokenId);
tokenIdByOwner[tokenId] = msg.sender;
delete ownerByTokenId[owner][tokenId];
ownerByTokenId[msg.sender].push(tokenId);
// Make a payment to the owner of the token
(bool sent,) = payable(owner).call{value:msg.value}("");
require(sent, "Payment failed");
return tokenId;
}
}
为了避免重入攻击,我们将首先转移代币,然后向代币的所有者进行支付,但在此之前,我们首先需要检查代币是否属于调用该函数的同一地址。
我们将使用Message结构体并创建一个函数,该函数只允许社区成员(至少拥有一个代币的地址)添加代币的消息,并创建一个函数来检索属于代币的所有消息。
contract PrivateMarket is ERC721URIStorage {
function createMessage(
uint256 tokenId,
string memory message
) public ownsToken {
Message memory messageData = Message(msg.sender, message, block.timestamp);
messages[tokenId].push(messageData);
}
function getMessages(uint256 tokenId)
public
view
returns (Message[] memory)
{
return messages[tokenId];
}
}
最后,我们将添加一组附加函数来获取与代币和代币所有者相关的数据。
contract PrivateMarket is ERC721URIStorage {
function getTokenData(uint256 tokenId)
public
view
returns (TokenData memory)
{
return tokenIdByTokenData[tokenId];
}
function getOwnerTokens() public view returns (uint256[] memory) {
return ownerByTokenId[msg.sender];
}
function getAddressTokens(address owner)
public
view
returns (uint256[] memory)
{
return ownerByTokenId[owner];
}
function getAllTokenData() public view returns (TokenData[] memory) {
return tokensData;
}
}
智能合约应该通过自动和手动来进行测试。使用HardHat或Truffle可以编写一组测试,以确保合约及其功能会按预期工作,最重要的是确保合约的安全性很高。
ChinaDeFi - ChinaDeFi.com 是一个研究驱动的DeFi创新组织,同时我们也是区块链开发团队。每天从全球超过500个优质信息源的近900篇内容中,寻找思考更具深度、梳理更为系统的内容,以最快的速度同步到中国市场提供决策辅助材料。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!