1. 程式人生 > >《我學區塊鏈》—— 三十六、以太坊批量轉賬(空投)節省費用

《我學區塊鏈》—— 三十六、以太坊批量轉賬(空投)節省費用

三十五、智慧合約收發 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。