# 关于使用合约批量铸造的原因分析与解决方案 **Published by:** [jackygu's blog](https://paragraph.com/@jackygu/) **Published on:** 2024-01-25 **URL:** https://paragraph.com/@jackygu/R8dA32cBiKlSSJ9BiOkk ## Content Cirth.meme上线4天,各方面表现良好。但铸造代码中,发现存在使用合约来批量铸造的可能,这严重影响了公平铸造。 类似的交易记录见:https://etherscan.io/tx/0xa58e8039740892f59b25ec0e14ce63d3d5c62eba0b16cdcd61c65553d5c67874。 原因 社区提交上述交易后,经技术团队研究,复现了其操作,大概的方式为:写两个智能合约,一个为主控制合约,一个为铸造合约,在主控制合约中创建多个铸造合约,通过铸造合约伪造正常用户,去调用Cirth合约的mint方法。 下面是实现类似功能的智能合约源码: // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "@openzeppelin/contracts/proxy/Clones.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; interface IFERC721C { function mint(address to) external; } interface ICallMint { function mint() external; function destroy() external; } contract BatchMint is Ownable { address public immutable tokenImplementation; event Deploy(address); constructor(address _ferc721) { tokenImplementation = address(new CallMint(msg.sender, _ferc721)); } function deployAndMint(uint times) public onlyOwner() { for(uint i; i < times; i++) { address tokenAddress = Clones.clone(tokenImplementation); ICallMint(tokenAddress).mint(); ICallMint(tokenAddress).destroy(); emit Deploy(tokenAddress); } } } contract CallMint { address public immutable ferc721; address public immutable receiver; constructor(address _receiver, address _ferc721) { ferc721 = _ferc721; receiver = _receiver; } function mint() public { IFERC721C(ferc721).mint(receiver); } function destroy() public { selfdestruct(payable(receiver)); } } 解释: BatchMint:主控制合约 批量铸造合约,该合约在构建时,创造一个CallMint合约的模版,在批量铸造方法deployAndMint中,通过Clone方法生成多个调用合约(即B合约) CallMint:铸造合约 单次铸造后,立即销毁自己,在链上不留痕迹。铸造时,由合约调用人接收NFT。 该方法经测试,单个NFT铸造的Gas费比正常铸造高30%。 如果在切换调用合约时,同时转Ferc,可以实现只要拥有10个Ferc,就能给N个铸造合约使用。 该方法不需要手动在钱包中开多个账户,并且可以绕开冷冻时间。 解决方案 在铸造方法中增加一个对调用账户的限制,即仅允许EOA账户调用,拒绝合约账户调用(包括AA账户),如下: function mint(address _to) public payable { require(_tokenData.totalSupply < _tokenData.max / _tokenData.limit, "touch the max batch"); require(_to != address(0), "mint to zero address"); require(msg.sender.code.length == 0 && _to.code.length == 0, "contract account not support"); // 增加该行 ... } 以上更新将在下一版中实施,但这个解决方案的缺陷在于,将无法支持AA账户。 ## Publication Information - [jackygu's blog](https://paragraph.com/@jackygu/): Publication homepage - [All Posts](https://paragraph.com/@jackygu/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@jackygu): Subscribe to updates