在本系列關於使用以太坊構建DApps教程的第1部分中,咱們引導你們作了兩個版本的本地區塊鏈進行開發:一個Ganache版本和一個完整的私有PoA版本。php
在這一部分中,咱們將深刻研究並構建咱們的TNS代幣:用戶將使用代幣對Story DAO中的提案進行投票。html
按照上一部分,啓動並運行Ganache版本。或者,若是你沒有從第一部分開始跟蹤,則能夠運行任何本地版本的區塊鏈,但請確保你可使用咱們須要的工具鏈接到它。java
咱們假設你有一個有效的私有區塊鏈,可以經過終端應用程序在其控制檯和操做系統終端中輸入命令,或者在Windows上,經過Git Bash,Console,CMD Prompt,Powershell等應用程序輸入命令。node
爲了開發咱們的應用程序,咱們可使用幾種框架和入門開發包中的一種:Dapp,eth-utils,Populus,Embark......等等。但咱們會選擇如今的生態系統之王Truffle。python
使用如下命令安裝它:android
npm install -g truffle
複製代碼
這將使truffle
命令無處不在。如今咱們能夠用truffle init
啓動項目。git
讓咱們直接進入它並構建咱們的代幣。它將是一個有點標準的千篇一概的ERC20代幣。(你會看到這篇文章中那個更標準的。)首先,咱們將引入一些依賴關係。OpenZeppelin庫是通過實戰考驗的高質量的solidity
合約,可用於擴展和構建合約。程序員
npm install openzeppelin-solidity
複製代碼
接下來,讓咱們建立一個新的代幣文件:github
truffle create contract TNSToken
複製代碼
truffle
在這裏生成的默認模板有點過期了,因此讓咱們更新它:web
pragma solidity ^0.4.24;
contract TNStoken {
constructor() public {
}
}
複製代碼
到目前爲止,代幣合約的構造函數應該與合約自己同樣被調用,但爲了清楚起見,它被更改成constructor
。它也應該老是有一個修飾符告訴編譯器誰被容許部署和與此合約交互(public意味着每一個人)。
咱們將在這種狀況下使用的惟一Zeppelin合約是他們的SafeMath合約。在Solidity中,咱們使用import關鍵字導入合約,而編譯器一般不須要完整路徑,只須要相對的路徑,以下所示:
pragma solidity ^0.4.24;
import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract TNStoken {
using SafeMath for uint256;
constructor() public {
}
}
複製代碼
那麼,什麼是SafeMath
?好久之前,因爲代碼中的數學問題,出現了1840億比特幣的問題。爲了防止相似於這些問題(並不是特別只在以太坊中可能存在這一問題),SafeMath庫仍然存在。當兩個數字具備MAX_INT
大小(即操做系統中的最大可能數量)時,將它們相加會使值wrap around
從新歸零,就像汽車的里程錶在達到999999千米後重置爲0。因此SafeMath庫具備如下功能:
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
複製代碼
此函數能夠防止此問題:它檢查兩個數字的總和是否仍然大於兩個操做數中的每個。
雖然在撰寫Solidity合約時犯下如此愚蠢的錯誤並不容易,但保持安全比抱歉更好。
經過using SafeMath for uint256
,咱們用這些「安全」版本替換Solidity(256bit unsigned - aka positive-only - whole numbers)中的標準uint256
數字。而不是像這樣求和數:sum=someBigNumber+someBiggerNumber
,咱們將這樣求和:sum=someBigNumber.add(someBiggerNumber)
,從而在咱們的計算中是安全的。
咱們的數學計算安全了,咱們能夠建立咱們的代幣。
ERC20是一個定義明確的標準,因此做爲參考,咱們將它添加到合約中。在這裏閱讀代幣標準 。
因此ERC20代幣應該具備的功能是:
pragma solidity ^0.4.24;
import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract ERC20 {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
function allowance(address owner, address spender) public view returns (uint256);
function transferFrom(address from, address to, uint256 value) public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract TNStoken {
using SafeMath for uint256;
constructor() public {
}
}
複製代碼
這可能看起來很複雜,但實際上很是簡單。這是咱們代幣須要具備的函數的「目錄」,咱們將逐個構建它們,解釋每一個函數的含義。考慮上面的代幣接口。在建立Story DAO應用程序時,咱們將看到它如何以及爲什麼有用。
開始吧。代幣實際上只是以太坊區塊鏈中的「電子表格」,以下所示:
Name | Amount |
---|---|
Bruno | 4000 |
Joe | 5000 |
Anne | 0 |
Mike | 300 |
因此讓咱們建立一個mapping
,它基本上就像合約中的電子表格:
mapping(address => uint256) balances;
複製代碼
根據上面的接口,這須要伴隨一個balanceOf
函數,它能夠讀取此表:
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
複製代碼
函數balanceOf接受一個參數:_owner
是public
(能夠被任何人使用),是一個view
函數(意思是它能夠自由使用——不須要交易),並返回一個uint256
編碼,地址全部者的餘額放在裏面。每一個人的代幣餘額都是公開可讀的。
知道代幣的總供應量對於其用戶和代幣跟蹤應用程序很是重要,因此讓咱們定義一個合約屬性(變量)來跟蹤這個和另外一個自由函數來讀取它:
uint256 totalSupply_;
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
複製代碼
接下來,讓咱們確保一些代幣的全部者能夠將它們發送給其餘人。咱們還想知道發送什麼時候發生,所以咱們也將定義發送事件。Transfer
事件容許咱們經過JavaScript監聽區塊鏈中的傳輸,以便咱們的應用程序能夠知道什麼時候發出這些事件,而不是不斷地手動檢查傳輸是否發生。事件與合約中的變量一塊兒聲明,並使用emit
關鍵字發出。咱們如今將如下內容添加到合約中:
event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
複製代碼
此函數接受兩個參數:_to
,它是將接收代幣的目標地址,以及value
,即代幣的數量。重要的是要記住,value
是代幣的最小單位數,而不是整個單位。所以,若是一個代幣被聲明具備10位小數的話,那麼爲了發送一個代幣,你將發送10000000000。這種粒度級別容許咱們處理極小數量。
該函數是公共的,這意味着任何人均可以使用它,包括其餘合約和用戶,而且若是操做成功則返回true
。
而後該功能進行一些健全性檢查。首先,它檢查目標地址是否爲空地址。換句話說,不得將代幣必須正常發送。接下來,它經過比較它們的餘額(balances[msg.sender]
)和傳入的發送值來檢查發件人是否甚至被容許發送這麼多代幣。若是這些檢查中的任何一個失敗,該函數將拒絕該交易並失敗。它將退還所發送的任何代幣,可是在此以前用於執行該功能的gas將被花費。
接下來的兩行從發件人的餘額中減去代幣數量,並將該金額添加到目的地餘額中。而後使用emit
事件,並傳入一些值:發件人,收件人和金額。如今,任何訂閱了此合約上的發送事件的客戶都將收到此事件的通知。
好的,如今咱們的代幣持有者能夠發送代幣。信不信由你,這就是基本代幣所須要的一切。但咱們已經要超越了這一點,並增長了一些功能。
有時可能會容許第三方退出其餘賬戶的餘額。這對於可能促進遊戲內購買,去中心化交易等的遊戲應用很是有用。咱們經過構建一個名爲allowance
的多維mapping
實現這一點,該mapping
存儲了全部這些權限。咱們添加如下內容:
mapping (address => mapping (address => uint256)) internal allowed;
event Approval(address indexed owner, address indexed spender, uint256 value);
複製代碼
這個事件就在那裏,以便應用程序能夠知道有人預先批准了其餘人的餘額支出,一個有用的功能,以及標準的一部分。
映射將地址與另外一個映射相結合,該映射將地址與數字組合在一塊兒,它基本上造成了一個像這樣的電子表格:
因此Bob的餘額可能由Mary支付,最多可達1000個代幣,Billy最多可達50個代幣。Bob能夠將Mary的餘額花費750代幣。Billy的餘額最多能夠由Mary花費300個,而Joe花費1500。
鑑於此映射是internal
映射,它只能由此合約中的函數和使用此合約做爲基礎的合約使用。
要批准其餘人從你的賬戶中扣款,你可使用容許使用代幣的人的地址,容許他們支付的金額以及你發出Approval
事件的功能來調用approve
功能:
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
複製代碼
咱們還須要一種方法來讀取用戶能夠從其餘用戶的賬戶中花費多少:
function allowance(address _owner, address _spender) public view returns (uint256) {
return allowed[_owner][_spender];
}
複製代碼
因此它是另外一個read only
函數(view
),這意味着它能夠自由執行。它只是讀取剩餘的可提取餘額。
那麼如何爲別人發送?使用新的transferFrom
功能:
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}
複製代碼
和之前同樣,有健全性檢查:目標地址不能是空地址,所以不要將代幣發送到不存在的地方。發送的值還須要不只小於或等於發送值當前賬戶的餘額,並且還須要小於或等於消息發送者(發起此交易的地址)仍然容許爲他們花費的餘額。
接下來,更新餘額並使容許的餘額與發出有關發送事件以前的餘額同步。
注意:代幣持有者能夠在不更新allowed
映射的狀況下allowed
代幣。若是代幣持有者使用transfer
手動發送代幣,則會發生這種狀況。在這種狀況下,持有人的代幣可能比第三方能夠支付的額外費用少。
經過批准和許可,咱們還能夠建立讓代幣持有者增長或減小某人津貼的功能,而不是徹底覆蓋該值。嘗試將此做爲練習,而後參考下面的源代碼以得到解決方案。
function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
allowed[msg.sender][_spender] = (
allowed[msg.sender][_spender].add(_addedValue));
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
uint oldValue = allowed[msg.sender][_spender];
if (_subtractedValue > oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
複製代碼
到目前爲止,咱們只是創建了一個代幣「合約」。可是這個標記是什麼?它叫什麼?它有多少位小數?咱們如何使用它?
在一開始,咱們定義了一個constructor
函數。如今,讓咱們完成它的主體並添加屬性name
,symbol
和decimals
:
string public name;
string public symbol;
uint8 public decimals;
constructor(string _name, string _symbol, uint8 _decimals, uint256 _totalSupply) public {
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply_ = _totalSupply;
}
複製代碼
這樣作可讓咱們稍後重複使用同一類型的其餘代幣。可是,當咱們確切知道咱們正在構建的內容時,讓咱們對這些值進行硬編碼:
string public name;
string public symbol;
uint8 public decimals;
constructor() public {
name = "The Neverending Story Token; symbol = "TNS"; decimals = 18; totalSupply_ = 100 * 10**6 * 10**18; } 複製代碼
顯示代幣信息時,各類以太坊工具和平臺會讀取這些詳細信息。將合約部署到以太坊網絡時會自動調用構造函數,所以這些值將在部署時自動配置。
關於totalSupply_ = 100*10**6*10**18
,這句話只是讓人們更容易閱讀數字的一種方式。因爲以太坊中的全部發送都是使用最小的以太單位或代幣(包括小數)完成的,所以最小單位是小數點後18位小數。這就是說單個TNS代幣爲1*10**18*
。此外,咱們想要1億,因此100*10**6
或100*10*10*10*10*10*10
。這使得數字比100000000000000000000000000
更易讀。
或者,咱們也能夠擴展Zeppelin合約,修改一些屬性,而後咱們就擁有代幣了。這就是大多數人所作的,但在處理可能數百萬其餘人的錢的軟件時,我我的傾向於想知道我在代碼中的確切內容,所以盲目代碼重用在個人我的狀況下是要最小化的。
pragma solidity ^0.4.24;
import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/token/ERC827/ERC20Token.sol";
contract TNStoken is ERC20Token {
using SafeMath for uint256;
string public name;
string public symbol;
uint8 public decimals;
uint256 totalSupply_;
constructor() public {
name = "The Neverending Story Token";
symbol = "TNS";
decimals = 18;
totalSupply_ = 100 * 10**6 * 10**18;
}
}
複製代碼
在這種狀況下,咱們使用is符號來聲明咱們的代幣是ERC20Token
。這使得咱們的代幣擴展了ERC20
合約,後者又擴展了StandardToken
,等等......
不管哪一種方式,咱們的代幣如今已準備就緒。但誰獲得了多少代幣以及如何開始?
讓咱們給合約的製造者全部的代幣。不然,代幣將不會發送給任何人。經過在其末尾添加如下行來更新constructor
:
balances[msg.sender] = totalSupply_;
複製代碼
看到咱們打算使用代幣做爲投票權(即你在投票期間鎖定了多少代幣表明你的投票有多強大),咱們須要一種方法來防止用戶在投票後發送它們,不然咱們的DAO將容易受到Sybil攻擊的影響——擁有一百萬個代幣的我的能夠註冊100個地址,並經過將它們發送到不一樣的地址並使用新地址從新投票來得到1億個代幣的投票權。所以,咱們將阻止發送與一我的投票額徹底同樣多的代幣,對每一個提案的每次投票都是累積的。這是咱們在本文開頭提到的扭曲。讓咱們在合約中添加如下事件:
event Locked(address indexed owner, uint256 indexed amount);
複製代碼
而後讓咱們添加鎖定方法:
function increaseLockedAmount(address _owner, uint256 _amount) onlyOwner public returns (uint256) {
uint256 lockingAmount = locked[_owner].add(_amount);
require(balanceOf(_owner) >= lockingAmount, "Locking amount must not exceed balance");
locked[_owner] = lockingAmount;
emit Locked(_owner, lockingAmount);
return lockingAmount;
}
function decreaseLockedAmount(address _owner, uint256 _amount) onlyOwner public returns (uint256) {
uint256 amt = _amount;
require(locked[_owner] > 0, "Cannot go negative. Already at 0 locked tokens.");
if (amt > locked[_owner]) {
amt = locked[_owner];
}
uint256 lockingAmount = locked[_owner].sub(amt);
locked[_owner] = lockingAmount;
emit Locked(_owner, lockingAmount);
return lockingAmount;
}
複製代碼
每種方法都確保不會鎖定或解鎖非法金額,而後在更改給定地址的鎖定金額後發出事件。每一個函數還返回如今爲此用戶鎖定的新金額。但這仍然不能阻止發送。讓咱們修改transfer
和transferFrom
:
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender] - locked[msg.sender]); // <-- THIS LINE IS DIFFERENT
// ...
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from] - locked[_from]);
require(_value <= allowed[_from][msg.sender] - locked[_from]); // <-- THIS LINE IS DIFFERENT
// ...
複製代碼
最後,咱們須要知道爲用戶鎖定或解鎖了多少代幣:
function getLockedAmount(address _owner) view public returns (uint256) {
return locked[_owner];
}
function getUnlockedAmount(address _owner) view public returns (uint256) {
return balances[_owner].sub(locked[_owner]);
}
複製代碼
就是這樣:咱們的代幣如今能夠從外部鎖定,但只能由代幣合約的全部者鎖定(這將是咱們將在即將到來的教程中構建的Story DAO)。讓咱們將代幣合約設爲Ownable
,即容許它擁有一個全部者。使用import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol"
導入;而後更改此行:
contract StoryDao {
複製代碼
......是這樣的:
contract StoryDao is Ownable {
複製代碼
此時帶有自定義函數註釋的代幣的完整代碼見文末所示。
這部分幫助咱們構建了一個基本代幣,咱們將在The Neverending Story
中將其用做參與/共享代幣。雖然代幣具備效用,但它的定義是做爲一種資產來控制更大的體量的安全代幣。注意區別。
在本系列的下一部分中,咱們將學習如何編譯,部署和測試此代幣。
======================================================================
分享一些以太坊、EOS、比特幣等區塊鏈相關的交互式在線編程實戰教程:
- java以太坊開發教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智能合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和交易等。
- EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、帳戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- tendermint區塊鏈開發詳解,本課程適合但願使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操代碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這裏是原文以太坊構建DApps系列教程(二):構建TNS代幣
完整代碼:
pragma solidity ^0.4.24;
import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";
contract TNStoken is Ownable {
using SafeMath for uint256;
mapping(address => uint256) balances;
mapping(address => uint256) locked;
mapping (address => mapping (address => uint256)) internal allowed;
uint256 totalSupply_;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event Locked(address indexed owner, uint256 indexed amount);
string public name;
string public symbol;
uint8 public decimals;
constructor() public {
name = "The Neverending Story Token";
symbol = "TNS";
decimals = 18;
totalSupply_ = 100 * 10**6 * 10**18;
balances[msg.sender] = totalSupply_;
}
/**
@dev _owner will be prevented from sending _amount of tokens. Anything
beyond this amount will be spendable.
*/
function increaseLockedAmount(address _owner, uint256 _amount) public onlyOwner returns (uint256) {
uint256 lockingAmount = locked[_owner].add(_amount);
require(balanceOf(_owner) >= lockingAmount, "Locking amount must not exceed balance");
locked[_owner] = lockingAmount;
emit Locked(_owner, lockingAmount);
return lockingAmount;
}
/**
@dev _owner will be allowed to send _amount of tokens again. Anything
remaining locked will still not be spendable. If the _amount is greater
than the locked amount, the locked amount is zeroed out. Cannot be neg.
*/
function decreaseLockedAmount(address _owner, uint256 _amount) public onlyOwner returns (uint256) {
uint256 amt = _amount;
require(locked[_owner] > 0, "Cannot go negative. Already at 0 locked tokens.");
if (amt > locked[_owner]) {
amt = locked[_owner];
}
uint256 lockingAmount = locked[_owner].sub(amt);
locked[_owner] = lockingAmount;
emit Locked(_owner, lockingAmount);
return lockingAmount;
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender] - locked[msg.sender]);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from] - locked[_from]);
require(_value <= allowed[_from][msg.sender] - locked[_from]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}
function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
allowed[msg.sender][_spender] = (
allowed[msg.sender][_spender].add(_addedValue));
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
uint oldValue = allowed[msg.sender][_spender];
if (_subtractedValue > oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
/**
@dev Returns number of tokens the address is still prevented from using
*/
function getLockedAmount(address _owner) public view returns (uint256) {
return locked[_owner];
}
/**
@dev Returns number of tokens the address is allowed to send
*/
function getUnlockedAmount(address _owner) public view returns (uint256) {
return balances[_owner].sub(locked[_owner]);
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function allowance(address _owner, address _spender) public view returns (uint256) {
return allowed[_owner][_spender];
}
}
複製代碼