主页 > 海外版imtoken > 通过CREATE2获取合约地址:解决交易所充值账户问题

通过CREATE2获取合约地址:解决交易所充值账户问题

海外版imtoken 2023-01-16 21:45:01

本文巧妙地利用CREATE2为用户生成一个新创建的合约作为充值地址,当需要代币回收时,创建合约,转移代币,同时销毁合约。设置费用。

CREATE2 是以太坊于 2019 年 2 月 28 日推出的新操作码。根据 EIP1014 引入的 CREATE2 操作码主要用于状态通道,但我们也可以使用它来解决其他问题。

例如,交易所需要为每个用户提供一个以太坊地址,以便用户可以向其存入资金。我们称这些地址为“存款地址”。当代币进入存款地址时怎么查代币的合约地址,我们需要将其聚合到钱包(热钱包)中。

我们来分析一下没有CREATE2操作码如何解决上述问题,以及为什么这些解决方案不适用。如果只对最终结果感兴趣,可以直接跳到最后一段:

退休计划:直接使用以太坊地址

最简单的解决方案是为新用户生成一个以太坊账户地址作为用户存款地址。如有必要,在后台使用充值地址的私钥调用 transfer() 将用户的钱包收集到交易所热钱包。

这种方法有以下优点:

但是,我们决定放弃这个方案,因为它有一个重大缺陷:总是需要将私钥保存在某个地方,而且这不仅仅是私钥可能丢失的问题怎么查代币的合约地址,还需要小心管理对私钥的访问权限。如果其中一个私钥被盗,用户的代币将无法收集到热钱包中。

退休计划:为用户创建独立的智能合约

每个用户创建一个单独的智能合约,并将合约地址作为用户的充值地址,避免了将该地址的私钥保存在服务器上,交易调用智能合约进行代币收集。

但是我们还是没有选择这个选项,因为没有办法让用户在部署合约之前显示充值地址(其实是可以的,但是会很复杂,还有一些其他的缺陷) 在交易所,用户应该可以创建任意数量的账户,这意味着在合约部署上浪费钱,并且不清楚用户是否会使用该账户。

改进:使用 CREATE2 操作码预计算合约地址

为了解决上一节中无法显示充值地址的问题,我们决定使用CREATE2操作码,它可以让我们提前计算出要部署的合约地址。地址计算公式如下:

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]

说明:

● 地址——调用 CREATE2 的智能合约的地址

● 盐——随机数

● init_code——要部署的合约的字节码

因此,保证提供给用户的合约地​​址包含预期的合约字节码。此外,只能在需要时部署合约。例如,当用户决定使用钱包时。

更进一步,合约的地址可以随时计算,无需保存地址,因为在公式中:

继续改进

上面的方案还有一个缺陷:交易所需要付费部署智能合约。但是,这是可以避免的。可以在合约构造函数中调用 transfer() 函数,然后调用 selfdestruct()。这将退还用于部署智能合约部分的气体。与常见的误解相反,您可以使用 CREATE2 操作码在同一地址多次部署智能合约。这是因为 CREATE2 检查目标地址的 nonce 是否为零(它在构造函数的开头将其设置为 1)。在这种情况下,selfdestruct() 函数每次都会重置地址的 nonce。所以,如果再次调用CREATE2以相同的参数创建合约,则nonce检查可以通过。

此解决方案类似于使用以太坊地址,但不存储私钥。因为我们不为智能合约部署付费,所以从存款地址转账到热钱包的成本大约等于调用 transfer() 函数的成本。

最终计划

初步准备:

● 通过 user_id 获取随机值(盐)的函数

● 调用 CREATE2 操作码的智能合约(使用适当的 nonce)

● 存款钱包合约的字节码,构造函数如下:

constructor () {
    address hotWallet = 0x …;
    address token = 0x …;
    token.transfer (hotWallet, token.balanceOf (address(this)));
    selfdestruct (address (0));
}

对于每个新用户,我们通过以下公式计算充值钱包地址:

keccak256 (0xff ++ fabric_addr ++ hash (user_id) ++ keccak256 (wallet_init_code)) [12:]

当用户将代币转入充值钱包地址时,后台系统会监听Transfer事件,目标参数(_to)为充值地址。此时,在实际部署充值钱包合约之前,用户在交易所的余额已经可以增加了。

当用户在充值钱包中积累了足够的代币后,我们可以一次性将所有代币转入平台热钱包。为此,在后台调用了工厂合约的以下方法:

function deployWallet (uint256 salt) {
    bytes memory walletBytecode = …;
    // 用充值钱包合约的字节码及 salt 调用 CREATE2 
}

此时调用充值钱包智能合约的构造函数,将所有代币转入热钱包,然后自动销毁。

以下为完整代码:

// Note that this is not the production code
pragma solidity 0.5.6;
import "./IERC20.sol";
contract Wallet {
    address internal token = 0x123...<hot_wallet_addr>;
    address internal hotWallet = 0x321...<hot_wallet_addr>;
    constructor() public {
        // send all tokens from this contract to hotwallet
        IERC20(token).transfer(
            hotWallet,
            IERC20(token).balanceOf(address(this))
        );
        // selfdestruct to receive gas refund and reset nonce to 0
        selfdestruct(address(0x0));
    }
}
contract Fabric {
    function createContract(uint256 salt) public {
        // get wallet init_code
        bytes memory bytecode = type(Wallet).creationCode;
        assembly {
            let codeSize := mload(bytecode) // get size of init_bytecode
            let newAddr := create2(
                0, // 0 wei
                add(bytecode, 32), // the bytecode itself starts at the second slot. The first slot contains array length
                codeSize, // size of init_code
                salt // salt from function arguments
            )
        }
    }
}

。注意,这不是我们的生产环境代码,因为我们也优化了钱包合约的字节码,使用opcodes编写。

原文由专门从事静态代码分析、反编译和安全开发的安全团队 SmartDec 创建。这种翻译得到了 Chainlink 社区和 CellNetwork 的支持。

本文参与Chainlink社区写作激励计划,好文好收入,阅读者欢迎加入。