初探 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