内容简介:本文巧妙的利用 CREATE2 为用户生成一个还为创建的合约作为充值地址,并在需要归集代币时,同时完成创建合约、转移代币及销毁合约,通过同时完成创建和销毁可以归集费用。本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
本文巧妙的利用 CREATE2 为用户生成一个还为创建的合约作为充值地址,并在需要归集代币时,同时完成创建合约、转移代币及销毁合约,通过同时完成创建和销毁可以归集费用。
CREATE2 是以太坊在2019年2月28号的 君士坦丁堡(Constantinople)硬分叉 中引入 的一个新操作码。根据EIP1014CREATE2操作码引入主要是用于状态通道,然而,我们也可以用于解决其他问题。
例如,交易所需要为每个用户提供一个以太坊地址,以便用户可以向其充值。 我们称这些地址为“充值地址”。 当代币进入充值地址时,我们需要将其汇总到一个钱包(热钱包)。
下面我们分析一下在没有CREATE2操作码时,如何解决上述问题, 以及为什么这些方案不适用。如果你只对最终结果感兴趣,可以直接跳到最后一节:。
淘汰方案:直接使用以太坊地址
最简单的解决方案是为新用户生成以太坊账号地址作为用户充值地址。需要时则在后台用充值地址的私钥签名调用 transfer()
把用户钱包归集到交易所热钱包。
此方法具有以下优点:
transfer()
然而,我们决定放弃这个方案,因为它有一个重大的缺陷:总是需要在一些地方保存私钥,这不仅仅是私钥可能丢失的问题,还需要仔细管理私钥的访问权限。如果其中一个私钥被盗,那么这个用户的代币就无法归集到热钱包。
淘汰方案:为用户创建独立的智能合约
每个用户创建一个单独的智能合约并用合约地址作为用户的充值地址,这避免了在服务器上保存地址的私钥, 交易通过调用智能合约进行代币归集。
不过我们依旧没有选择这个方案,因为在部署合约之前用户没有办法显示充值地址(实际上是可能的,但是会非常复杂,并且还有一些其他缺陷)。 在交易所中,用户应该可以创建任意多的账号,这意味着需要在合约部署上浪费资金,并且还不能确认用户是否会使用这个账号。
改进:使用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 ) } } }
.注意,这不是我们的生产环境代码,因为我们还要优化钱包合约的字节码,并且使用操作码编写了。
原文 由 SmartDec 创作,专门从事静态代码分析,反编译和安全开发的安全团队。 本翻译得到登链社区及 CellNetwork 支持。
CREATE2 是以太坊在2019年2月28号的 君士坦丁堡(Constantinople)硬分叉 中引入 的一个新操作码。根据EIP1014CREATE2操作码引入主要是用于状态通道,然而,我们也可以用于解决其他问题。
例如,交易所需要为每个用户提供一个以太坊地址,以便用户可以向其充值。 我们称这些地址为“充值地址”。 当代币进入充值地址时,我们需要将其汇总到一个钱包(热钱包)。
下面我们分析一下在没有CREATE2操作码时,如何解决上述问题, 以及为什么这些方案不适用。如果你只对最终结果感兴趣,可以直接跳到最后一节:。
淘汰方案:直接使用以太坊地址
最简单的解决方案是为新用户生成以太坊账号地址作为用户充值地址。需要时则在后台用充值地址的私钥签名调用 transfer()
把用户钱包归集到交易所热钱包。
此方法具有以下优点:
transfer()
然而,我们决定放弃这个方案,因为它有一个重大的缺陷:总是需要在一些地方保存私钥,这不仅仅是私钥可能丢失的问题,还需要仔细管理私钥的访问权限。如果其中一个私钥被盗,那么这个用户的代币就无法归集到热钱包。
淘汰方案:为用户创建独立的智能合约
每个用户创建一个单独的智能合约并用合约地址作为用户的充值地址,这避免了在服务器上保存地址的私钥, 交易通过调用智能合约进行代币归集。
不过我们依旧没有选择这个方案,因为在部署合约之前用户没有办法显示充值地址(实际上是可能的,但是会非常复杂,并且还有一些其他缺陷)。 在交易所中,用户应该可以创建任意多的账号,这意味着需要在合约部署上浪费资金,并且还不能确认用户是否会使用这个账号。
改进:使用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 ) } } }
.注意,这不是我们的生产环境代码,因为我们还要优化钱包合约的字节码,并且使用操作码编写了。
原文 由 SmartDec 创作,专门从事静态代码分析,反编译和安全开发的安全团队。 本翻译得到登链社区及 CellNetwork 支持。
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 20分钟前
- 阅读 ( 5 )
- 学分 ( 0 )
- 分类:智能合约
以上所述就是小编给大家介绍的《通过CREATE2获得合约地址:解决交易所充值账号》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 交易所安全测试:信息收集
- 交易所安全测试:安全审计指南
- POSCMS交易所系统多个高危漏洞详解
- 漏洞再藏交易所,黑客盯上行情区
- 资源就是生产力,使用BiClub交易所资源可免交易手续费
- 因为它,中心化交易所要慌(黄)了吗?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。