# MetaFriends智能合约的7天无理由退款功能浅析 **Published by:** [JCONE](https://paragraph.com/@jcone/) **Published on:** 2022-04-21 **URL:** https://paragraph.com/@jcone/metafriends-7 ## Content MetaFriends智能合约的7天无理由退款功能浅析 MetaFriends是新一个以ERC721 A+R 作为宣传点的NFT项目,并在今天开启了Presale Mint(官网)。 ERC721R是在ERC721A基础上添加了某个期限内无理由退款功能,允许铸造者退还NFT并获得铸造价格的全额退款(相当于只损失了gas fee),使得项目方在这个期限内无法withdrawn铸造所得资金,从而一定程度上保护了NFT买家的利益,也有助于防止项目在退款期内地板价破发。 让我们阅读MetaFriends智能合约退款的源代码,看看其天无理由退款功能是如何实现,是否有漏洞。uint256 public immutable refundPeriod = 7 days; uint256 public lastMintTime; 首先设定为退款期限refundPeriod为7天,不可更改,并定义了一个整型变量名为lastMintTime;address public refundAddress; 这里设定了一个退货地址refundAddress,用于接收退回的NFT,MetaFriends设置了与合约部署者同一个钱包地址为退货地址;mapping(uint256 => bool) public hasRefunded; 为每一个tokenId映射了一个hasRefunded的bool变量,记录某个tokenId是否已经退过款,两种状态为true或false;mapping(uint256=>uint256) private timestampID; 为每一个tokenId映射了一个timestampID时间值,function _setMintTime(uint256 amount) internal{ if (amount == 5){ timestampID[totalSupply()-1] = block.timestamp; timestampID[totalSupply()-2] = block.timestamp; timestampID[totalSupply()-3] = block.timestamp; timestampID[totalSupply()-4] = block.timestamp; timestampID[totalSupply()-5] = block.timestamp; } if (amount == 4){ timestampID[totalSupply()-1] = block.timestamp; timestampID[totalSupply()-2] = block.timestamp; timestampID[totalSupply()-3] = block.timestamp; timestampID[totalSupply()-4] = block.timestamp; } if (amount == 3){ timestampID[totalSupply()-1] = block.timestamp; timestampID[totalSupply()-2] = block.timestamp; timestampID[totalSupply()-3] = block.timestamp; } if (amount == 2){ timestampID[totalSupply()-1] = block.timestamp; timestampID[totalSupply()-2] = block.timestamp; }else{ timestampID[totalSupply()-1] = block.timestamp; } lastMintTime = block.timestamp; } 因为MetaFriends设定的最大单笔交易mint数量maxMintPerTx为5,所以这里区分了铸造1-5个不同数量的5种交易情况,把每个tokenId的timestampID设定为当前交易的时间戳,并定义当前交易时间戳为lastMintTime;function refund(uint256 tokenId) external { require(msg.sender == ownerOf(tokenId), "Not token owner"); require(!hasRefunded[tokenId], "Already refunded"); require(block.timestamp - timestampID[tokenId] < refundPeriod, "refund time expire"); hasRefunded[tokenId] = true; transferFrom(msg.sender, refundAddress, tokenId); Address.sendValue(payable(msg.sender), PRICE); } 这里定义了退款功能函数,先设定三个退款的条件,一是必须为tokenId的owner(msg.sender == ownerOf(tokenId)),二是此tokenId之前没有退过款(!hasRefunded[tokenId]),三是当前时间戳减去tokenId铸造时间戳必须小于refundPeriod7天(block.timestamp - timestampID[tokenId] < refundPeriod),如果三个条件都满足,则将hasRefunded设定为true,然后转移退款者钱包中的tokenId到退货地址refundAddress,并从智能合约地址里把铸造费用发送给退款者,实现了NFT的退货退款。 这里!hasRefunded的require非常重要,部分早期ERC721R代码因为缺乏这一个约束条件,产生了一个项目方可以提前攫取资金的漏洞,通过refundAddress拿到退回来的NFT,继续在智能合约中进行循环退款refund操作,直至完全耗尽合约的资金,从而导致其他tokenId的refund无法实现;有了!hasRefunded的require之后,MetaFriends的合约并无此问题; 另外一个早期ERC721R的问题是价格归零漏洞,项目方在mint结束后,通过将PRICE设置为0,致使退款者将NFT退回之后无法获得任何退款,导致财货两空,因此用户需要对退款行为极为小心;MetaFriends的合约中并没有修改PRICE的功能,没有此问题;function withdrawAll() external onlyOwner { require(block.timestamp - lastMintTime > refundPeriod, "Refund period not over"); uint256 balance = address(this).balance; require(balance > 0); _widthdraw(0xc2AE244F0c0025864ac1Fd87Af4c8202bcD9a78F, balance.mul(70).div(100)); _widthdraw(0x902e3BF232Da0cBf91232a15460171f613ADad8e, address(this).balance); } 最后一个是项目方withdrawAll资金的功能函数,增加了一个约束条件,即操作时间戳减去最后一个mint时间戳lastMintTime必须大于refundPeriod7天,因为refundPeriod又是不可更改的常量,因此保证了退款期内项目方无法取走合约中的资金; 总结:通过增加约束条件限制循环退款,未设置mint价格修改功能,可以说MetaFriends合约是真正在代码端实现了无需信任(trustless)的7天无理由退款。 JCONE.eth 2022/4/21 ## Publication Information - [JCONE](https://paragraph.com/@jcone/): Publication homepage - [All Posts](https://paragraph.com/@jcone/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@jcone): Subscribe to updates