在本教程的第一篇文章中,我們介紹瞭如何搭建簡單的投票dapp,並將其部署在本地機器的測試環境中。在第二篇文章中,我們將這個dapp遷移到truffle框架上,並將其部署到公開測試網絡Ropsten上,然後分別通過truffle控制檯和網頁與其交互。接下來我們要再添加一些新功能,以便學習幾個新概念。下面是本文要介紹的內容:
- 學習如何用新的數據類型,比如
struct
,在區塊鏈上組織和存儲數據。 - 學習代幣(token)的概念及其用法。
- 學習如何使用以太幣,即以太坊區塊鏈平臺上的貨幣,進行支付。
本文所涉及的代碼全部放在代碼庫 https://github.com/maheshmurthy/ethereum_voting_dapp 的chapter3目錄下。
通常在選舉時,每位選民可以投一票給自己喜歡的候選人。然而在選舉公司的董事會主席時,是根據股東所持有的股份來計算票數的。也就是說股東持有的股數越多,可以投的票數也越多。
接下來我們要讓這個投票dapp支持這種選舉。因此要添加購買公司股份的功能,然後根據自己所持有的股數投票給候選人。另外還要添加一個功能,查看投票者的信息。在以太坊區塊鏈上,這些股份一般被稱作代幣,因此後面我們也用代幣這種叫法。
如果你想直接去看看合約文件,可以訪問:https://github.com/maheshmurthy/ethereum_voting_dapp/blob/master/chapter3/contracts/Voting.sol。
第一步是聲明變量,我們要用這些變量存放所有我們感興趣的信息。下面就是合約中的變量及其含義。
// 我們用struct存放投票者的信息
struct voter {
address voterAddress; // 投票者的地址
uint tokensBought; // 投票者持有的代幣總數
uint[] tokensUsedPerCandidate; // 這個數組中存放給每個候選人的代幣數
/* 下面有個名爲candidateList的數組。投票者每次用代幣
給候選人投票,對應位置的值就會增加。比如說,如果
candidateList數組聲明爲["Rama", "Nick", "Jose"],並且這位
投票者給Nick投了10代幣,則
tokensUsedPerCandidate[1] 的值會加10。
*/
}
/* 映射表就相當於關聯數組或哈希表。
映射表的鍵是候選人的名稱,用bytes32類型記錄;值是無符號整型,用來存放票數。
*/
mapping (bytes32 => uint) public votesReceived;
mapping (address => voter) public voterInfo;
/* Solidity中還不能直接返回字符串數組。所以我們會用一個bytes32數組存放候選人。
*/
bytes32[] public candidateList;
uint public totalTokens; // 這次選舉可用的代幣總數。
uint public balanceTokens; // 還可以購買的代幣總數
uint public tokenPrice; // 代幣的價格
在前兩篇文章中,我們在構造器中初始化了一個包含候選人的列表。可構造器只有在合約部署到區塊鏈上時纔會運行一次。我們在這裏也要初始化可銷售的代幣總數和每個代幣的價值。所以構造器需要調整一下:
/* 在合約部署到區塊鏈上時初始化可銷售的代幣總數和每個代幣的價值,以及所有的候選人。
*/
function Voting(uint tokens, uint pricePerToken, bytes32[] candidateNames) public {
candidateList = candidateNames;
totalTokens = tokens;
balanceTokens = tokens;
tokenPrice = pricePerToken;
}
在truffle中,是用遷移將代碼將合約部署到區塊鏈上。你可以看一下這裏的遷移文件。truffle遷移文件中的部署代碼看起來是這樣的:
deployer.deploy(Voting, 1000, web3.toWei('0.1', 'ether'), ['Rama', 'Nick', 'Jose']);
// 1000是可銷售的代幣總數,每個代幣的價格是0.1以太幣
// 稍後我們還會看到這段代碼。
在初始化好代幣及其價格之後,我們來看看如何用以太幣購買這些代幣。下面這個是購買代幣的函數。
/* 這是購買代幣的函數,注意下面的關鍵字'payable'。只要在函數聲明中加入這個關鍵字,合約就可以接受調用該函數者提供的以太幣。收錢不可能比這更容易了。
*/
function buy() payable public returns (uint) {
uint tokensToBuy = msg.value / tokenPrice;
if (tokensToBuy > balanceTokens) throw;
voterInfo[msg.sender].voterAddress = msg.sender;
voterInfo[msg.sender].tokensBought += tokensToBuy;
balanceTokens -= tokensToBuy;
return tokensToBuy;
}
調用這個購買函數的示例如下所示:
truffle(development)> Voting.deployed().then(function(contract) {contract.buy({value: web3.toWei('1', 'ether'), from: web3.eth.accounts[1]})})
在函數buy()
中,可以用msg.value
訪問參數 value: web3.toWei(‘1’, ‘ether’)
,而msg.sender
會給我們web3.eth.accounts[1]
的賬號地址。也就是說按照上面的代碼,假如每個代幣的價格是0.1以太幣,則web3.eth.accounts[1]
將會收到1 /0.1 = 10個代幣。
我們先把代碼放一放,看一下這幅圖,看看賬戶(投票者)和合約之間是如何交互的。
正如github上的合約文件所示,其他新方法大多是取值方法,應該很容易看懂。
index.html文件也有些更新:
- 要投票給候選人,必須設定投票用的代幣數。
- 還多了一個購買代幣的區域。
- 現在你可以查看投票者的信息,他們持有多少代幣以及用多少代幣給每個候選人投票。
- 候選人不再是硬編碼在文件中的了,而是從區塊鏈中讀取出來的。
app.js文件中也有變化,以實現UI中的新增功能。
更新部署文件2_deploy_contracts.js,傳入代幣總數,代幣價格以及候選人的名字。
var Voting = artifacts.require("./Voting.sol");
module.exports = function(deployer) {
deployer.deploy(Voting, 1000, web3.toWei('0.1', 'ether'), ['Rama', 'Nick', 'Jose']);
};
所以這裏總共更新了四個文件,分別是Voting.sol、index.html、app.js和 2_deploy_contracts.js。 所以只要你更新了這四個文件,就可以將合約部署到區塊鏈上了。部署過程跟我們之前介紹的一樣。
只需要用truffle命令就可以完成編譯和遷移。
maheshmurthy|~/dev/ethereum_voting_dapp/chapter3$ truffle migrate
Using network ‘development’.
Compiling Migrations.sol…
Compiling Voting.sol…
Writing artifacts to ./build/contracts
Running migration: 1_initial_migration.js
Deploying Migrations…
Migrations: 0xc9249947010675b8a3b1defb12334148f7f59010
Saving successful migration to network…
Saving artifacts…
Running migration: 2_deploy_contracts.js
Deploying Voting…
Voting: 0x795d6d1f7cf467f27e48181da5f1ebd5bbd0a8df
Saving successful migration to network…
Saving artifacts…
在合約部署成功後,啓動web服務器,你就能看到如下所示的頁面:
正如你在上面的界面中看到的那樣,你可以購買代幣,用代幣投票給候選人,通過地址查看投票者的信息。如果這些功能都沒問題,恭喜你!
我希望你能通過這個教程認識到什麼是以太坊,它能做什麼,以及如何搭建去中心化應用。