初探 GasToken
近期公司的项目准备将 GasToken 接入,以期在 GasPrice 较高时降低用户的使用成本。这里有 GasToken 相关的介绍:https://gastoken.io ,交易平台 1inch 近期也推出了一个新的 GasToken,原理都是一样的,通过在 GasPrice 较低的时间段去创建一些空的合约,在 GasPrice 较高是去销毁他以获得一些 Gas 抵消。
- 1inch 的 ChiToken:https://github.com/CryptoManiacsZone/chi
- GasToken 1/2:https://github.com/projectchicago/gastoken
在国庆长假之前我已经做过一次两个 GasToken 的对比实验,但是结果不太理想,因为只是在单个合约内去简单的向 mapping
里面储存值,最终的效果不太好。今天又一次重新捋了一下整个逻辑,通过 deployer.eth
这个合约,来测试两种 GasToken 的区别。
测试 GasToken
- 首先我在 Kovan 上面部署了 ChiToken(没有用的,合约内部一些 assembly 代码需要调整,博文后面会讲到)
- 然后将 ChiToken@Kovan 和 GST2@Kovan 配置到 deployer 合约中。
- 部署 Deployer 合约。
- Mint 一些 Token 以供销毁使用。
- 分别 Approve 两个 Token 给 Deployer 合约。
- 找到一个稍大点的合约进行部署测试。
deployer 合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface IFreeFromUpTo {
function freeFromUpTo(address from, uint256 value) external returns (uint256 freed);
}
contract Deployer {
IFreeFromUpTo public constant gst = IFreeFromUpTo(*GST2的合约地址*);
IFreeFromUpTo public constant chi = IFreeFromUpTo(*ChiToken的合约地址*);
modifier discountGST {
uint256 gasStart = gasleft();
_;
uint256 gasSpent = 21000 + gasStart - gasleft() + 16 * msg.data.length;
gst.freeFromUpTo(msg.sender, (gasSpent + 14154) / 41130);
}
modifier discountCHI {
uint256 gasStart = gasleft();
_;
uint256 gasSpent = 21000 + gasStart - gasleft() + 16 * msg.data.length;
chi.freeFromUpTo(msg.sender, (gasSpent + 14154) / 41130);
}
function deploy(bytes memory data) public returns(address contractAddress) {
assembly {
contractAddress := create(0, add(data, 32), mload(data))
}
}
function gstDeploy(bytes memory data) public discountGST returns(address contractAddress) {
assembly {
contractAddress := create(0, add(data, 32), mload(data))
}
}
function chiDeploy(bytes memory data) public discountCHI returns(address contractAddress) {
assembly {
contractAddress := create(0, add(data, 32), mload(data))
}
}
function gstDeploy2(uint256 salt, bytes memory data) public discountGST returns(address contractAddress) {
assembly {
contractAddress := create2(0, add(data, 32), mload(data), salt)
}
}
function chiDeploy2(uint256 salt, bytes memory data) public discountCHI returns(address contractAddress) {
assembly {
contractAddress := create2(0, add(data, 32), mload(data), salt)
}
}
}
结果
GasUsed 消耗Token 备注
3598402 直接部署:https://kovan.etherscan.io/tx/0x448437aead1694a2ae42883be57f5fced86cd5301c69bd239f2a16adbe32ed03
3603313 通过deployer,未使用GasToken:https://kovan.etherscan.io/tx/0x3292959af0d902e760587ad8a7a76e67ca1f9fc486120c96490033d177d9042d
3718254 88 通过deployer,使用 ChiToken:https://kovan.etherscan.io/tx/0x39786b2289720da3974e66feab2f745978aae1b6451990591d660f19766cbad5
2102987 88 通过deployer,使用 GST2: https://kovan.etherscan.io/tx/0xad004eecf0a053d4edb2f900fa00d8af223a28b957accc7b3c6e2c3de8232aa4
通过结果来看,GST2 实实在在产生了效果,但是 ChiToken 并未产生效果反而是 Gas 消耗增加。
导致博主非常疑惑,然后奶爸分别研究了两个 Token 的代码,GST2 中创建空合约的部分是这么实现的,
// Creates a child contract that can only be destroyed by this contract.
function makeChild() internal returns (address addr) {
assembly {
// EVM assembler of runtime portion of child contract:
// ;; Pseudocode: if (msg.sender != 0x0000000000b3f879cb30fe243b4dfee438691c04) { throw; }
// ;; suicide(msg.sender)
// PUSH15 0xb3f879cb30fe243b4dfee438691c04 ;; hardcoded address of this contract
// CALLER
// XOR
// PC
// JUMPI
// CALLER
// SELFDESTRUCT
// Or in binary: 6eb3f879cb30fe243b4dfee438691c043318585733ff
// Since the binary is so short (22 bytes), we can get away
// with a very simple initcode:
// PUSH22 0x6eb3f879cb30fe243b4dfee438691c043318585733ff
// PUSH1 0
// MSTORE ;; at this point, memory locations mem[10] through
// ;; mem[31] contain the runtime portion of the child
// ;; contract. all that's left to do is to RETURN this
// ;; chunk of memory.
// PUSH1 22 ;; length
// PUSH1 10 ;; offset
// RETURN
// Or in binary: 756eb3f879cb30fe243b4dfee438691c043318585733ff6000526016600af3
// Almost done! All we have to do is put this short (31 bytes) blob into
// memory and call CREATE with the appropriate offsets.
let solidity_free_mem_ptr := mload(0x40)
mstore(solidity_free_mem_ptr, 0x00756eb3f879cb30fe243b4dfee438691c043318585733ff6000526016600af3)
addr := create(0, add(solidity_free_mem_ptr, 1), 31)
}
}
部署时会预先将合约的地址计算出来,然后放入 assembly 代码中硬编码作为后面 selfdestruct 的鉴权使用。然后 GST2@Kovan 是 GST 开发人员在部署之前就已经算好并修改好了合约代码,然后部署的,在生成合约及后续销毁时不会有问题。
而奶爸在 Kovan 上面自行部署的 ChiToken,由于部署的空合约的 assembly 编码中的鉴权地址为 ChiToken 在主网部署时的 合约地址,奶爸部署的 ChiToken 只能 mint,并不能 burn,所以就导致没有测试出效果来。
总结
总体来说 ChiToken & GST2 都是有效果的,两者差距不大且接口相同,可以无缝接入到自己合约中。在 GasPrice 起伏较大时,使用 GasToken 是非常建议的。