初探 GasToken

近期公司的项目准备将 GasToken 接入,以期在 GasPrice 较高时降低用户的使用成本。这里有 GasToken 相关的介绍:https://gastoken.io ,交易平台 1inch 近期也推出了一个新的 GasToken,原理都是一样的,通过在 GasPrice 较低的时间段去创建一些空的合约,在 GasPrice 较高是去销毁他以获得一些 Gas 抵消。

在国庆长假之前我已经做过一次两个 GasToken 的对比实验,但是结果不太理想,因为只是在单个合约内去简单的向 mapping 里面储存值,最终的效果不太好。今天又一次重新捋了一下整个逻辑,通过 deployer.eth 这个合约,来测试两种 GasToken 的区别。

测试GasToken

  1. 首先我在 Kovan 上面部署了 ChiToken(没有用的,合约内部一些 assembly 代码需要调整,博文后面会讲到)
  2. 然后将 ChiToken@KovanGST2@Kovan 配置到 deployer 合约中。
  3. 部署 Deployer 合约。
  4. Mint 一些 Token 以供销毁使用。
  5. 分别 Approve 两个 Token 给 Deployer 合约。
  6. 找到一个稍大点的合约进行部署测试。

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 是非常建议的。

Comments