Skip to content

Latest commit

 

History

History
135 lines (110 loc) · 7.81 KB

File metadata and controls

135 lines (110 loc) · 7.81 KB

第三十八课 NFT交易所

Opensea是以太坊上最大的NFT交易平台,总交易总量达到了$300亿。

设计逻辑

  • 卖家:出售NFT的一方,可以挂单list、撤单revoke、修改价格update。
  • 买家:购买NFT的一方,可以购买purchase。
  • 订单:卖家发布的NFT链上订单,一个系列的同一tokenId最多存在一个订单,其中包含挂单价格price和持有人owner信息。当一个订单交易完成或被撤单后,其中信息清零。
挂单
    // 挂单: 卖家上架NFT,合约地址为_nftAddr,tokenId为_tokenId,价格_price为以太坊(单位是wei)
    function list(address _nftAddr, uint256 _tokenId, uint256 _price) public{
        IERC721 _nft = IERC721(_nftAddr); // 声明IERC721接口合约变量
        require(_nft.getApproved(_tokenId) == address(this), "Need Approval"); // 合约得到授权
        require(_price > 0); // 价格大于0

        Order storage _order = nftList[_nftAddr][_tokenId]; //设置NF持有人和价格
        _order.owner = msg.sender;
        _order.price = _price;
        // 将NFT转账到合约
        _nft.safeTransferFrom(msg.sender, address(this), _tokenId);

        // 释放List事件
        emit List(msg.sender, _nftAddr, _tokenId, _price);
    }
撤销订单
    // 撤单: 卖家取消挂单
    function revoke(address _nftAddr, uint256 _tokenId) public {
        Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order        
        require(_order.owner == msg.sender, "Not Owner"); // 必须由持有人发起
        // 声明IERC721接口合约变量
        IERC721 _nft = IERC721(_nftAddr);
        require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中
        
        // 将NFT转给卖家
        _nft.safeTransferFrom(address(this), msg.sender, _tokenId);
        delete nftList[_nftAddr][_tokenId]; // 删除order
      
        // 释放Revoke事件
        emit Revoke(msg.sender, _nftAddr, _tokenId);
    }
修改价格
    // 调整价格: 卖家调整挂单价格
    function update(address _nftAddr, uint256 _tokenId, uint256 _newPrice) public {
        require(_newPrice > 0, "Invalid Price"); // NFT价格大于0
        Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order        
        require(_order.owner == msg.sender, "Not Owner"); // 必须由持有人发起
        // 声明IERC721接口合约变量
        IERC721 _nft = IERC721(_nftAddr);
        require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中
        
        // 调整NFT价格
        _order.price = _newPrice;
      
        // 释放Update事件
        emit Update(msg.sender, _nftAddr, _tokenId, _newPrice);
    }
购买
    // 购买: 买家购买NFT,合约为_nftAddr,tokenId为_tokenId,调用函数时要附带ETH
    function purchase(address _nftAddr, uint256 _tokenId) payable public {
        Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order        
        require(_order.price > 0, "Invalid Price"); // NFT价格大于0
        require(msg.value >= _order.price, "Increase price"); // 购买价格大于标价
        // 声明IERC721接口合约变量
        IERC721 _nft = IERC721(_nftAddr);
        require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中

        // 将NFT转给买家
        _nft.safeTransferFrom(address(this), msg.sender, _tokenId);
        // 将ETH转给卖家,多余ETH给买家退款
        payable(_order.owner).transfer(_order.price);
        payable(msg.sender).transfer(msg.value-_order.price);

        delete nftList[_nftAddr][_tokenId]; // 删除order

        // 释放Purchase事件
        emit Purchase(msg.sender, _nftAddr, _tokenId, _order.price);
    }

第三十九课 随机数生成

很多以太坊上的应用都需要用到随机数,例如NFT随机抽取tokenId、抽盲盒、gamefi战斗中随机分胜负等等。但是由于以太坊上所有数据都是公开透明(public)且确定性(deterministic)的,它没法像其他编程语言一样给开发者提供生成随机数的方法。这一讲我们将介绍链上(哈希函数)和链下(chainlink预言机)随机数生成的两种方法。

链上随机数生成

我们可以将一些链上的全局变量作为种子,利用keccak256()哈希函数来获取伪随机数。这是因为哈希函数具有灵敏性和均一性,可以得到“看似”随机的结果。下面的getRandomOnchain()函数利用全局变量block.timestamp,msg.sender和blockhash(block.number-1)作为种子来获取随机数:

    /** 
    * 链上伪随机数生成
    * 利用keccak256()打包一些链上的全局变量/自定义变量
    * 返回时转换成uint256类型
    */
    function getRandomOnchain() public view returns(uint256){
        // remix运行blockhash会报错
        bytes32 randomBytes = keccak256(abi.encodePacked(block.timestamp, msg.sender, blockhash(block.number-1)));
        
        return uint256(randomBytes);
    }

但是链上随机数不安全,首先,block.timestamp,msg.sender和blockhash(block.number-1)这些变量都是公开的,使用者可以预测出用这些种子生成出的随机数,并挑出他们想要的随机数执行合约。 其次,矿工可以操纵blockhash和block.timestamp,使得生成的随机数符合他的利益。

链下随机数生成

我们可以在链下生成随机数,然后通过预言机把随机数上传到链上。

目前比较火的链下随机数是通过Chainlink提供VRF(可验证随机函数)服务来获取的。但是申请随机数和获取到随机数是两笔交易。且获取随机数需要消耗LINK代币。

第四十课 ERC1155

在前面,有同质化代币标准ERC20, 和非同质化代币标准ERC721。且每个合约都对应一个独立的代币。假设我们要在以太坊上打造一个类似《魔兽世界》的大型游戏,这需要我们对每个装备都部署一个合约。上千种装备就要部署和管理上千个合约,这非常麻烦。因此,以太坊EIP1155提出了一个多代币标准ERC1155,允许一个合约包含多个同质化和非同质化代币。

原理

在ERC721中,每个代币都有一个tokenId作为唯一标识,每个tokenId只对应一个代币;而在ERC1155中,每一种代币都有一个id作为唯一标识,每个id对应一种代币。这样,代币种类就可以非同质的在同一个合约里管理了,并且每种代币都有一个网址uri来存储它的元数据,类似ERC721的tokenURI。

如何区分ERC1155中的某类代币是同质化还是非同质化代币呢?

其实很简单:如果某个id对应的代币总量为1,那么它就是非同质化代币,类似ERC721;如果某个id对应的代币总量大于1,那么他就是同质化代币,因为这些代币都分享同一个id,类似ERC20。

其中,IERC1155定义的函数有:

    balanceOf():单币种余额查询,返回account拥有的id种类的代币的持仓量。
    balanceOfBatch():多币种余额查询,查询的地址accounts数组和代币种类ids数组的长度要相等。
    setApprovalForAll():批量授权,将调用者的代币授权给operator地址。。
    isApprovedForAll():查询批量授权信息,如果授权地址operator被account授权,则返回truesafeTransferFrom():安全单币转账,将amount单位id种类的代币从from地址转账给to地址。如果to地址是合约,则会验证是否实现了onERC1155Received()接收函数。
    safeBatchTransferFrom():安全多币转账,与单币转账类似,只不过转账数量amounts和代币种类ids变为数组,且长度相等。如果to地址是合约,则会验证是否实现了onERC1155BatchReceived()接收函数。