《我学区块链》—— 三十六、以太坊批量转账(空投)节省费用

三十五、智能合约收发 ETH 详解

       前段时间 fcoin 的空投把 eth 堵得不成样,看见好几个空投竟然是一个个地 transfer转账,但是实际上如果用合约实现批量转账,不管是成功率还是效率都会高很多,还省 gas。

       整个过程的模型如下:

airdrop-construct

一、空投合约

       本文讲解如何用合约实现批量转账,下面直接上合约。

pragma solidity ^0.4.23;

contract Airdrop {

    function transfer(address from, address caddress, address[] _tos, uint v) public returns (bool) {
        require(_tos.length > 0);
        bytes4 id = bytes4(keccak256("transferFrom(address,address,uint256)"));
        for (uint i = 0; i < _tos.length; i++) {
            require(caddress.call(id, from, _tos[i], v));
        }
        return true;
    }
}

       代码不复杂,最关键的一点是用到了 solidity 中对地址的操作,在合约中 call 另一个合约,首先得知道要调用的是哪一个函数,因为是批量转账,这里用 transferFrom 函数,为什么不用 transfer 呢?因为这里发起交易的主体是合约地址,而不是原来的账户地址,我们可以看看 transfer 函数和 transferFrom 函数有什么不同

function transfer(address _recipient, uint256 _value) onlyPayloadSize(2 * 32) public {
    require(balances[msg.sender] >= _value && _value > 0);
    balances[msg.sender] -= _value;
    balances[_recipient] += _value;
    Transfer(msg.sender, _recipient, _value);
}
function transferFrom(address _from, address _to, uint256 _value) public {
    require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0);
    balances[_to] += _value;
    balances[_from] -= _value;
    allowed[_from][msg.sender] -= _value;
    Transfer(_from, _to, _value);
}

       让合约实现代币转账首先需要做得是让合约得到操纵一定量代币的权利,可以看到transfer 函数首先检查地址的余额,意味着要让合约替你转账,你得先转给它一部分代币才行,但让合约直接拥有代币是不太安全的,而 transferFrom 就不同了,它检查的是授信额度,意思是你只需要先授信给合约它能操纵的代币数量,这样的好处是降低风险。

二、代币合约

       另外,再准备一份代币合约。

pragma solidity ^0.4.23;

import 'zeppelin-solidity/contracts/token/ERC20/MintableToken.sol';

contract XToken is MintableToken {

    string public name = "X Token";
    string public symbol = "XTH";
    uint8 public decimals = 8;
    uint public INITIAL_SUPPLY = 10 ** 11;  // equal 10 ^ 11, 1000亿

    constructor() public {
        totalSupply_ = INITIAL_SUPPLY * 10 ** uint256(decimals);
        balances[owner] = totalSupply_;
    }

    /**
     * 覆盖 Ownable -> transferOwnership 方法.
     */
    function transferOwnership(address _newOwner) public onlyOwner {
        balances[_newOwner] = balances[owner].add(balances[_newOwner]);  // Owner 余额要转移给 新所有者。
        balances[owner] = 0;    // 清空旧所有人余额。
        super._transferOwnership(_newOwner);
    }
}

三、发布合约

       笔者的合约是由 truffle 构建的,在部署文件中配置好两份合约,并发布到网络中。

var XToken = artifacts.require("XToken");
var Airdrop = artifacts.require("Airdrop");

module.exports = function(deployer) {
    deployer.deploy(XToken);
    deployer.deploy(Airdrop);
};

       发布合约

truffle migrate --reset

四、授权额度

       编写 node 脚本,执行授权。

#!/usr/bin/env node

const fs = require('fs');
const Web3 = require('web3');
const web3 = new Web3('http://127.0.0.1:8545/');
const json = JSON.parse(fs.readFileSync('build/contracts/NPToken.json'));

/**
 * Code: approve(c99, Airdrop.sol, 1000 eth)
 */
let code = web3.eth.abi.encodeFunctionCall({
    name: 'approve',
    type: 'function',
    inputs: [{
        type: 'address',
        name: '_spender'
    }, {
        type: 'uint256',
        name: '_value'
    }]
}, ['{airdroip address}', '1000000000000000000000']);

/**
 * ETH 合约调用
 */
web3.eth.accounts.signTransaction({
    to: '{airdrop address}',
    value: '0',                 // 0 ether
    gas: '21000',               // 21000 个
    gasPrice: '5000000000',     // 5 gwei
    data: code
}, '{private-key}')
    .then(res => {
        web3.eth.sendSignedTransaction(res.rawTransaction)
            .on('transactionHash', hash => {
                console.log('### hash: ' + hash);
            })
            .on('receipt', receipt => {
                console.log('### receipt: ' + JSON.stringify(receipt));
            })
            .on('confirmation', (confirmationNumber, receipt) => {
                console.log('### confirmation: ' + confirmationNumber);
            })
            .on('error', err => {
                console.error('### error: ' + err);
            });
    });

       修改脚本中的 private-key、airdrop-address 等信息,并执行脚本,进行授权。

node airdrop.js

五、批量空投

       修改上面脚本中的 let code 值为:

let code = web3.eth.abi.encodeFunctionCall({
    name: 'transfer',
    type: 'function',
    inputs: [{
        type: 'address',
        name: 'from'
    }, {
        type: 'address',
        name: 'caddress'
    }, {
        type: 'address[]',
        name: '_tos'
    }, {
        type: 'uint256',
        name: 'v'
    }]
}, ['{XTH-owner-address}','{airdrop-address}',['0x25387B.....fFdEEcBb',
    '0x25387B.....fFdEEcBb','0x25387B.....fFdEEcBb','0x25387B.....fFdEEcBb',
    '0x25387B.....fFdEEcBb','0x25387B.....fFdEEcBb','0x25387B.....fFdEEcBb',
    '0x25387B.....fFdEEcBb','0x25387B.....fFdEEcBb','0x25387B.....fFdEEcBb',],
    '1000000000000000']);

       注意修改其中的 XTH-owner-address,airdrop-address,以及后面要批量空投的地址。

       执行脚本即可开始批量空投。

六、验证结果

       下面笔者记录的一组数据。

笔数 1 2 3 4 5 6 7 8 9
GAS(个数) 51871 85016 92168 114320 136472 158624 180776 202928 225080
GAS平均 51871 42508 30723 28580 27294 26437 25825 25366 25009

       绘制出如下图形

airdrop-line

       经分析可知,虽然以太坊原币交易的标准费用为 21000 gas,但代币交易的费用却要 51871 gas,且当使用批量空投合约时,批量数 > 3 后,每增加一笔,费用增加 22152 gas。

参考资料:https://blog.csdn.net/qq_35513792/article/details/81116241

猜你喜欢

转载自blog.csdn.net/xuguangyuansh/article/details/83541496