學習以太坊智能合約開發最好的方法,莫過於結合具體的應用場景案例,舉例如投票、衆籌、ERC20或ERC721代幣發行等等,經過一個實例的實現,邊實踐邊學習了。在這篇教程中,咱們將使用以太坊solidity來實現一個遵循以太坊ERC20代幣規範的智能合約,並給出最終實現的solidity所有代碼。前端
若是你但願立刻開始學習以太坊DApp開發,能夠訪問匯智網提供的出色的在線互動教程:git
ERC20約定了一個代幣合約須要實現的接口,規範參見ERC20github
// 接口標準 contract ERC20 { function totalSupply() constant returns (uint totalSupply); // 總髮行量 function balanceOf(address _owner) constant returns (uint balance); // 代幣分發(注意, 這個只有合約的Creator 能夠調用) function transfer(address _to, uint _value) returns (bool success); // 這裏是擁有者和擁有者之間的代幣轉移 function transferFrom(address _from, address _to, uint _value) returns (bool success); function approve(address _spender, uint _value) returns (bool success); function allowance(address _owner, address _spender) constant returns (uint remaining); event Transfer(address indexed _from, address indexed _to, uint _value); event Approval(address indexed _owner, address indexed _spender, uint _value); // Token信息 string public constant name = "4FunCoin"; string public constant symbol = "4FC"; uint8 public constant decimals = 18; // token的精度, 大部分都是18 }
上面的代碼是一個標準的ERC20標準的代碼, 規範給出了框架, 咱們只須要實現相應的函數就行了, 這裏給出函數說明。app
函數的形參是局部有效, 因此前面使用下劃線, 與其餘的變量區別開來. 如 _owner.框架
這裏兩個Event是重點, 事件,能夠被前端js代碼捕獲到並進行相應的處理:函數
理解了上面的函數, 下面的代碼,就實現了Token合約的函數填充學習
pragma solidity ^0.4.16; interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; } // token的 接受者 這裏聲明接口, 將會在咱們的ABI裏 contract TokenERC20 { /*********Token的屬性說明************/ string public name = 4FunCoin; string public symbol = 4FC; uint8 public decimals = 18; // 18 是建議的默認值 uint256 public totalSupply; // 發行量 // 創建映射 地址對應了 uint' 即是他的餘額 mapping (address => uint256) public balanceOf; // 地址對應餘額 mapping (address => mapping (address => uint256)) public allowance; // 事件,用來通知客戶端Token交易發生 event Transfer(address indexed from, address indexed to, uint256 value); // 事件,用來通知客戶端代幣被消耗(這裏就不是轉移, 是token用了就沒了) 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; // 這裏就比較重要, 這裏至關於實現了, 把token 所有給合約的Creator name = tokenName; symbol = tokenSymbol; } // token的發送函數 function _transfer(address _from, address _to, uint _value) internal { require(_to != 0x0); // 不是零地址 require(balanceOf[_from] >= _value); // 有足夠的餘額來發送 require(balanceOf[_to] + _value > balanceOf[_to]); // 這裏也有意思, 不能發送負數的值(hhhh) uint previousBalances = balanceOf[_from] + balanceOf[_to]; // 這個是爲了校驗, 避免過程出錯, 總量不變對吧? balanceOf[_from] -= _value; //發錢 很少說 balanceOf[_to] += _value; Transfer(_from, _to, _value); // 這裏觸發了轉帳的事件 , 見上event 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]); // 這句很重要, 地址對應的合約地址(也就是token餘額) 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; } } // 正如其名, 這個是燒幣(SB)的.. ,用於把建立者的 token 燒掉 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; } // 這個是用戶銷燬token..... 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; } }
上面的代碼閱讀難度不大, 也寫了大多處的註釋, 這裏簡單介紹幾個要點:ui
// 這裏是構造函數, 實例建立時候執行 function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public { totalSupply = initialSupply * 10 ** uint256(decimals); // 這裏肯定了總髮行量 balanceOf[msg.sender] = totalSupply; // 這裏就比較重要, 這裏至關於實現了, 把token 所有給合約的Creator name = tokenName; symbol = tokenSymbol; }
在Solidity裏面, Contract 咱們能夠直接理解成一個Class吧, 如C++ 同樣, 這裏面也存在一個 構造函數並且他們的功能也是近乎相同, 在合約建立的時候執行一次.(沒錯 , 合約整個生命週期裏只能執行這樣一次) , 因此他的做用就是實現合約信息的初始化, 一旦數據寫入區塊數據, 將是沒法更改的了(永固性).this
構造函數的是不能有返回值的(有也沒法接受), 可是能夠帶參數, 像是此處代碼, 把發行量, token的名稱和token的 符號做爲參數留出. 在合約初始化時候咱們即可以自行定義..net
函數體中可見, 咱們對貨幣總量, 名稱和 符號進行賦值, 這樣,這些值就永遠的記錄在了咱們的合約的區塊數據中了
// 創建映射 地址對應了 uint' 即是他的餘額 mapping (address => uint256) public balanceOf; // 地址對應餘額 mapping (address => mapping (address => uint256)) public allowance;
這個形式,乍一眼看上去是沒那麼好懂. 其實慢慢的 也是理解了, 這裏的映射, 通俗的講,就是至關於咱們的字典, 是一個鍵值對. 上述的代碼也是創建了一個 address 到 uint類型的映射關係.
balanceOf[msg.sender] = 10000; //msg.sender 是一個地址
這樣簡單的方法, 至關於對帳戶進行餘額的賦值;