ERC20代幣批量轉賬實現和解析(一)

ywxm_發表於2018-12-27

如果你對區塊鏈和ERC20代幣沒有概念,請先閱讀入門知識

  1. 以太坊是什麼
  2. 區塊鏈技術-智慧合約-以太坊

批量轉賬,指的是在一筆 ETH 交易中轉多筆代幣到不同的賬戶,一般用於 ERC20 代幣專案啟動時對使用者進行空投。

批量空投的好處主要有兩個,一是省 GAS 費,但事實上省得不多;二是省時間,這是最主要目的。以太坊是以交易為粒度打包,如果轉賬只能單對單,即使一次發起多筆單對單的交易,等待這些交易被打包的時間也非常漫長,而且還有筆數上限限制。將多筆轉賬放到同一個交易中,被打包確認的速度就會非常快。一般 ERC20 代幣專案啟動時都會大撒幣,空投地址動輒都是幾萬幾十萬,批量空投介面對效率會有上百倍的提升。

文章主要內容

  1. 在本機部署ETH私有鏈
  2. 在ETH私有鏈上釋出ERC20代幣
  3. 三種批量轉賬方式的原理
  4. 三種批量轉賬方式的實現
  5. 如何解析批量轉賬

由於文章較長,所以這裡分為兩篇,本篇主要講前兩個部署環境的部分,熟悉部署的同學可以跳過本章

在本機部署ETH私有鏈

本機預設是在Mac OS X上進行安裝,別的系統的安裝可以在github上檢視Ethereum Installation Instructions

geth安裝

推薦使用Homebrew進行安裝,簡單快捷

brew 是 Mac 下的包管理工具,和Ubuntu裡的apt-get類似

brew tap ethereum/ethereum

brew install ethereum
複製程式碼

建立私有鏈

首先新建一個檔案目錄,例如ethprivate

要搭建私有鏈必須有一個創世區塊,創世區塊的資訊寫在一個json檔案中,例如genesis.json檔案,在ethprivate/genesis.json中寫入下面的內容

{
  "config": {
    "chainID": 1024,
    "homesteadBlock": 0,
    "eip155Block": 0,
    "eip158Block": 0
  },
  "alloc": {},
  "coinbase": "0x0000000000000000000000000000000000000000",
  "difficulty": "0x400",
  "extraData": "0x",
  "gasLimit": "0x84c060",
  "nonce": "0xdeadbeefdeadbeef",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp": "0x00"
}
複製程式碼

這裡將gasLimit設定大一點,防止後面部署合約的時候報Error: exceeds block gas limit undefined的錯誤資訊,這是因為合約所需的gas超過了區塊的最大gas

建立好創世區塊後,我們需要利用該創世區塊去初始化區塊鏈,需要新建一個data0目錄存放區塊資訊

接著在主目錄下,執行初始化區塊鏈的命令

geth --datadir data0 init genesis.json
複製程式碼

看到如下資訊則表明初始化成功

ERC20代幣批量轉賬實現和解析(一)

啟動私有鏈

geth --datadir 'data0' --identity 'my_block' --port '30304' console 2>>eth_output.log
複製程式碼

引數含義

–identity:指定節點 ID;
–rpc:表示開啟 HTTP-RPC 服務;
–rpcport:指定 HTTP-RPC 服務監聽埠號(預設為 8545);
–datadir:指定區塊鏈資料的儲存位置;
–port:指定和其他節點連線所用的埠號(預設為 30303);
–networkid: 指定私有鏈網路號;
–nodiscover:關閉節點發現機制,防止加入有同樣初始配置的陌生節點。
複製程式碼

看到下面的資訊表示私有鏈成功啟動

ERC20代幣批量轉賬實現和解析(一)

建立賬戶

現在剛啟動還沒有任何賬戶

ERC20代幣批量轉賬實現和解析(一)

我們利用personal建立賬戶

personal.newAccount()
複製程式碼

ERC20代幣批量轉賬實現和解析(一)

Passphrase 就是密碼,連續輸入兩次密碼便可以建立一個賬戶

除了personal,geth命令列中還包含了另外的物件

eth:包含一些跟操作區塊鏈相關的方法
net:包含以下檢視p2p網路狀態的方法
admin:包含一些與管理節點相關的方法
miner:包含啟動&停止挖礦的一些方法
personal:主要包含一些管理賬戶的方法
txpool:包含一些檢視交易記憶體池的方法
web3:包含了以上物件,還包含一些單位換算的方法
複製程式碼

按照這中方式可以多建立幾個賬戶,在後面使用

挖礦

我們剛開始還沒有進行挖礦,所以eth.accounts[0]中的餘額為0

eth.getBalance(eth.accounts[0])
複製程式碼

ERC20代幣批量轉賬實現和解析(一)

挖礦所得的ETH會預設存在eth.accounts[0]的賬戶當中

通過miner.start()來啟動挖礦,利用miner.stop()停止挖礦

ERC20代幣批量轉賬實現和解析(一)

miner.start()會返回null,推薦使用tail -f eth_output.log檢視日誌,這樣就可以看到挖礦的資訊

ERC20代幣批量轉賬實現和解析(一)
現在我們檢視賬戶的餘額就不在是0了

ERC20代幣批量轉賬實現和解析(一)

每挖到一個區塊會獎勵5個以太幣,日誌中可以看到到目前為止挖到了5個區塊,那麼就應該是20個以太幣,可是檢視的餘額卻不是這樣,這是因為這裡預設的單位是Wei,有關單位可以去移步以太幣(Ether)單位

可以利用下面的命令轉換單位

ERC20代幣批量轉賬實現和解析(一)

這裡只做簡單的介紹,其餘的命令大家有感興趣的可以自行查詢

在ETH私有鏈上釋出ERC20代幣

ERC20是Fabian Vogelsteller在2015年末提出的以太坊改進建議,它是許多流行的合約都在遵循的標準。

ERC20使通證智慧合約的行為非常類似於傳統的加密貨幣,例如在不同賬戶之間傳送和接收、檢視通證總供應量或者檢視某個地址可用的通證餘額​,就像比特幣或以太幣一樣。這類似於用以太坊錢包 傳送和接收以太幣、檢視流通中的以太幣總量、檢視特定錢包的貨幣餘額等。

ERC20規定了一些標準的介面

contract ERC20Interface {

    string public constant name = "Token Name";
    string public constant symbol = "SYM";
    uint8 public constant decimals = 18;  // 18 is the most common number of decimal places

    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
複製程式碼

簡單的介紹一下,更加詳細的介紹可以在github檢視

name : 代幣名稱
symbol: 代幣符號
decimals: 代幣小數點位數,代幣的最小單位
totalSupply() : 發行代幣總量。
balanceOf(): 檢視對應賬號的代幣餘額。
transfer(): 實現代幣交易,用於給使用者傳送代幣(從我們的賬戶裡)
transferFrom(): 實現代幣使用者之間的交易
allowance(): 控制代幣的交易,如可交易賬號及資產
approve(): 允許使用者可花費的代幣數
複製程式碼

在ETH私有鏈上釋出ERC20代幣

ERC20是Fabian Vogelsteller在2015年末提出的以太坊改進建議,它是許多流行的合約都在遵循的標準。

ERC20使通證智慧合約的行為非常類似於傳統的加密貨幣,例如在不同賬戶之間傳送和接收、檢視通證總供應量或者檢視某個地址可用的通證餘額​,就像比特幣或以太幣一樣。這類似於用以太坊錢包 傳送和接收以太幣、檢視流通中的以太幣總量、檢視特定錢包的貨幣餘額等。

ERC20規定了一些標準的介面

contract ERC20Interface {

    string public constant name = "Token Name";
    string public constant symbol = "SYM";
    uint8 public constant decimals = 18;  // 18 is the most common number of decimal places

    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
複製程式碼

簡單的介紹一下,更加詳細的介紹可以在github檢視

name : 代幣名稱
symbol: 代幣符號
decimals: 代幣小數點位數,代幣的最小單位
totalSupply() : 發行代幣總量。
balanceOf(): 檢視對應賬號的代幣餘額。
transfer(): 實現代幣交易,用於給使用者傳送代幣(從我們的賬戶裡)
transferFrom(): 實現代幣使用者之間的交易
allowance(): 控制代幣的交易,如可交易賬號及資產
approve(): 允許使用者可花費的代幣數
複製程式碼

編寫合約程式碼

先編寫一個符合ERC20標準的程式碼

pragma solidity ^0.4.16;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }

contract TokenERC20 {
    string public name;
    string public symbol;
    uint8 public decimals = 18;  // 18 是建議的預設值
    uint256 public totalSupply;

    mapping (address => uint256) public balanceOf;  // 
    mapping (address => mapping (address => uint256)) public allowance;

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Burn(address indexed from, uint256 value);


    function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public {
        totalSupply = initialSupply * 10 ** uint256(decimals);
        balanceOf[msg.sender] = totalSupply;
        name = tokenName;
        symbol = tokenSymbol;
    }


    function _transfer(address _from, address _to, uint _value) internal {
        require(_to != 0x0);
        require(balanceOf[_from] >= _value);
        require(balanceOf[_to] + _value > balanceOf[_to]);
        uint previousBalances = balanceOf[_from] + balanceOf[_to];
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        Transfer(_from, _to, _value);
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }

    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_value <= allowance[_from][msg.sender]);     // Check allowance
        allowance[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public
        returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        return true;
    }

    function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
        tokenRecipient spender = tokenRecipient(_spender);
        if (approve(_spender, _value)) {
            spender.receiveApproval(msg.sender, _value, this, _extraData);
            return true;
        }
    }

    function burn(uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);
        balanceOf[msg.sender] -= _value;
        totalSupply -= _value;
        Burn(msg.sender, _value);
        return true;
    }

    function burnFrom(address _from, uint256 _value) public returns (bool success) {
        require(balanceOf[_from] >= _value);
        require(_value <= allowance[_from][msg.sender]);
        balanceOf[_from] -= _value;
        allowance[_from][msg.sender] -= _value;
        totalSupply -= _value;
        Burn(_from, _value);
        return true;
    }
}
複製程式碼

然後把這段程式碼拷貝到Solidity,點選旁邊的Details獲取部署程式碼

ERC20代幣批量轉賬實現和解析(一)

然後在彈出層中複製WEB3DEPLOY中的程式碼到編輯器中

ERC20代幣批量轉賬實現和解析(一)

修改程式碼如圖中所示

ERC20代幣批量轉賬實現和解析(一)

然後將程式碼複製到geth命令列中,按回車。然後執行miner.start(),如果出現以下資訊則表明代幣部署成功

ERC20代幣批量轉賬實現和解析(一)

如果出現Error: authentication needed: password or unlock undefined錯誤,是因為賬戶沒有被解鎖,執行personal.unlockAccount(eth.accounts[0])然後輸入密碼解鎖賬戶

接著我們在命令列中輸入tokenerc20則可以看到返回的介面資訊

ERC20代幣批量轉賬實現和解析(一)

address,表示合約地址

可以利用balanceOf函式檢視地址的代幣餘額

ERC20代幣批量轉賬實現和解析(一)

至此,合約部署完畢

下一篇文章說說ERC20空投合約的三種實現方式

相關文章