【知识】通过CREATE2获得合约地址:解决交易所充值账号问题

CREATE2 是以太坊在2019年2月28号的君士坦丁堡(Constantinople)硬分叉[1]中引入 的一个新操作码。根据EIP1014[2]CREATE2操作码引入,主要是用于状态通道,然而,我们也可以用于解决其他问题。

例如,交易所需要为每个用户提供一个以太坊地址,以便用户可以向其充值。我们称这些地址为“充值地址”。当代币进入充值地址时,我们需要将其汇总到一个钱包(热钱包)。

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

淘汰方案:直接使用以太坊地址

最简单的解决方案是为新用户生成以太坊账号地址作为用户充值地址。需要时则在后台用充值地址的私钥签名调用transfer()把用户钱包归集到交易所热钱包。

此方法具有以下优点:

很简单
将代币从用户充值地址转到热钱包的费用与调用transfer()的费用一样(译者注:这是相对于后面需要部署合约其他方案来说)
然而,我们决定放弃这个方案,因为它有一个重大的缺陷:总是需要在一些地方保存私钥,这不仅仅是私钥可能丢失的问题,还需要仔细管理私钥的访问权限。如果其中一个私钥被盗,那么这个用户的代币就无法归集到热钱包。

淘汰方案:为用户创建独立的智能合约

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

不过我们依旧没有选择这个方案,因为在部署合约之前用户没有办法显示充值地址(实际上是可能的,但是会非常复杂,并且还有一些其他缺陷)。在交易所中,用户应该可以创建任意多的账号,这意味着需要在合约部署上浪费资金,并且还不能确认用户是否会使用这个账号。

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

为了解决上一节没有办法显示充值地址的问题,我们决定使用 CREATE2 操作码,它允许我们提前计算出要部署的合约地址,地址计算公式如下:

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

说明:
● address— 调用CREATE2的智能合约的地址
● salt— 随机数
● init_code— 要部署合约的字节码

因此,可以保证提供给用户的合约地址中包含了期望的合约字节码。此外,合约可以在需要的时候才部署。例如,当用户决定使用钱包时。

更进一步,可以随时计算出合约的地址而无需保存地址,因为公式中的:
address:是个常量,它是部署钱包的工厂合约地址
salt:使用user_id的哈希
init_code:也是个常量,因为总是部署相同合约

继续改进

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

这个解决方案类似于使用以太坊地址的方案,但是无需存储私钥。因为我们不支付智能合约部署费用,所以将钱从充值地址到热钱包的成本大约等于调用transfer()函数的成本。

最终方案

初始准备:
● 通过user_id获取随机值(salt)的函数
● 调用CREATE2操作码(使用适当的随机数)的智能合约
● 具有如下构造函数的充值钱包合约的字节码:

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
            )
        }
    }
}

.注意,这不是我们的生产环境代码,因为我们还要优化钱包合约的字节码,并且使用操作码编写了。

原文[5]由 SmartDec[6] 创作,专门从事静态代码分析,反编译和安全开发的安全团队。本翻译得到登链社区[7]及 CellNetwork[8] 支持。

参考资料
[1] 君士坦丁堡(Constantinople)硬分叉: https://learnblockchain.cn/2019/06/15/eth-history1#%E5%A4%A7%E9%83%BD%E4%BC%9A-%E5%90%9B%E5%A3%AB%E5%9D%A6%E4%B8%81%E5%A0%A1-Constantinople-%E7%A1%AC%E5%88%86%E5%8F%89--2019%E5%B9%B42%E6%9C%8828%E6%97%A5

[2] EIP1014: https://learnblockchain.cn/docs/eips/eip-1014.html

[3] 最终方案: #最终方案

[4] 智能合约: https://learnblockchain.cn/2018/01/04/understanding-smart-contracts

[5] 原文: https://blog.smartdec.net/how-to-define-smart-contract-address-before-the-deploy-create2-use-case-for-decentralized-exchange-52b7daa7873b

[6]SmartDec: https://www.smartcontracts.smartdec.net/

[7]登链社区: https://learnblockchain.cn/

[8] CellNetwork: https://www.cellnetwork.io/?utm_souce=learnblockchain

本文作者:Tiny熊
作者主页:
https://learnblockchain.cn/people/15

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容