合約編寫(xiě)實(shí)戰(zhàn)實(shí)例
一生年、簡(jiǎn)單代幣合約
pragma solidity > 0.4.22;
contract Coin{
? ? //這里我們定義了一個(gè)address 作為key, uint做為value的hashTable balances; 我們還定義了一個(gè)address的變量minter;
? ? address public minter;
? ? mapping(address=>uint) balances;
? ? event Sent(address from, address to, uint amount);
? ? constructor(){
? ? ? ? //代表創(chuàng)建這個(gè)合約的賬戶地址惰许,被賦值給變量minter.
? ? ? ? minter = msg.sender;
? ? }
? ? //添加一個(gè)挖礦合約
? ? function mint(address receiver, uint amount) public{
? ? ? ? require(msg.sender == minter);
? ? ? ? balances[receiver] += amount;
? ? }
? ? function send(address receiver, uint amount) public{
? ? ? ? require(balances[msg.sender] >= amount);
? ? ? ? balances[msg.sender] -= amount;
? ? ? ? balances[receiver] += amount;
? ? ? ? emit Sent(msg.sender,receiver,amount);
? ? }
}
二至会、水龍頭合約
打開(kāi) faucet.sol,并寫(xiě)入如下代碼
pragma solidity ^0.7.0;
contract faucet {
? ? function withdraw (uint amount) public {
? ? ? ? require (amount <= 1e18);
? ? ? ? msg.sender.transfer (amount);
? ? }
? ? receive () external payable {}
}
通過(guò)這幾行代碼我們就實(shí)現(xiàn)了一個(gè)非常簡(jiǎn)單的水龍頭合約鹉究。首行代碼 pragma solidity ^0.7.0 是一個(gè)雜注瓣距,指定了我們的源文件使用的編譯器版本不能低于 0.7.0慨飘,也不能高于 0.8.0响疚。
contract faucet{...} 聲明了一個(gè)合約對(duì)象兼都,合約對(duì)象類(lèi)似面向?qū)ο笳Z(yǔ)言中的類(lèi),對(duì)象名必須跟文件名相同稽寒。
接下來(lái)通過(guò) function withdraw (uint amount) public {...} 創(chuàng)建了一個(gè)名為 withdraw 的函數(shù)扮碧,該函數(shù)接收一個(gè)無(wú)符號(hào)整數(shù)(uint)作為參數(shù),并且被聲明為 public 函數(shù)杏糙,意為可以被其他合約調(diào)用慎王。
withdraw 函數(shù)體中的 require 是 Solidity 的內(nèi)置函數(shù),用來(lái)檢測(cè)括號(hào)中的條件是否滿足宏侍。條件滿足則繼續(xù)執(zhí)行合約赖淤,條件不滿足則合約停止執(zhí)行,回撤所有執(zhí)行過(guò)的操作谅河,并拋出異常咱旱。在這里我們通過(guò) require (amount <= 1e18) 來(lái)檢測(cè)輸入的以太幣值是否小于等于1個(gè)以太。
接下來(lái)的這一行 msg.sender.transfer (amount) 就是實(shí)際的提款操作了绷耍。msg 是 Solidity 中內(nèi)置的對(duì)象吐限,所有合約都可以訪問(wèn),它代表觸發(fā)此合約的交易褂始。也就是說(shuō)當(dāng)我們調(diào)用 withdraw 函數(shù)的時(shí)候?qū)嶋H上觸發(fā)了一筆交易诸典,并用 msg 來(lái)表示它。sender 是交易 msg 的屬性崎苗,表示了交易的發(fā)件人地址狐粱。函數(shù) transfer 是一個(gè)內(nèi)置函數(shù),它接收一個(gè)參數(shù)作為以太幣的數(shù)量胆数,并將該數(shù)量的以太幣從合約賬戶發(fā)送到調(diào)用合約的用戶的地址中肌蜻。
最后一行是一個(gè)特殊的函數(shù) receive ,這是所謂的 fallback 或 default 函數(shù)必尼。當(dāng)合約中的其他函數(shù)無(wú)法處理發(fā)送到合約中的交易信息時(shí)蒋搜,就會(huì)執(zhí)行該函數(shù)。在這里胰伍,我們將該函數(shù)聲明為 external 和 payable 齿诞,external 意味著該函數(shù)可以接收來(lái)自外部賬戶的調(diào)用,payable 意味著該函數(shù)可以接收來(lái)自外部賬戶發(fā)送的以太幣骂租。
這樣,當(dāng)我們調(diào)用合約中的 withdraw 并提供一個(gè)參數(shù)時(shí)斑司,我們可以從這份合約中提出以太幣渗饮;當(dāng)我們向合約發(fā)送以太幣時(shí)但汞,就會(huì)調(diào)用 receive 函數(shù)往合約中捐贈(zèng)以太幣。
代碼編寫(xiě)完畢后互站,在 Remix 左側(cè)的功能欄中選擇第二項(xiàng)私蕾,并點(diǎn)擊 Compile faucet.sol 來(lái)編譯我們的 sol 文件。
三胡桃、投票合約的實(shí)現(xiàn)
以solidity文件中的contract/_Ballot.sol文件為例:
首先我們定義成員類(lèi)型踩叭,我們?yōu)槊總€(gè)投票者定義權(quán)重、是否已投票翠胰、
struct Voter {
? ? uint weight; // weight is accumulated by delegation
? ? bool voted;? // if true, that person already voted
? ? address delegate; // person delegated to
? ? uint vote;? // index of the voted proposal
}
然后我們定義提案類(lèi)型容贝,包含提案名和投票總數(shù):
struct Proposal {
? ? bytes32 name;? // short name (up to 32 bytes)
? ? uint voteCount; // number of accumulated votes
}
定義三個(gè)變量,主席是一個(gè)公開(kāi)的地址之景,建立投票者與地址的映射斤富,然后定義提案動(dòng)態(tài)數(shù)組:
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
address public chairperson:投票發(fā)起人,類(lèi)型為 address锻狗。
mapping(address => Voter) public voters:所有投票人满力,類(lèi)型為 address 到 Voter 的映射。
Proposal[] public proposals:所有提案轻纪,類(lèi)型為動(dòng)態(tài)大小的 Proposal 數(shù)組油额。
3 個(gè)狀態(tài)變量都使用了 public 關(guān)鍵字,使得變量可以被外部訪問(wèn)(即通過(guò)消息調(diào)用)刻帚。事實(shí)上悔耘,編譯器會(huì)自動(dòng)為 public 的變量創(chuàng)建同名的 getter 函數(shù),供外部直接讀取我擂。
我們還需要為每個(gè)投票賦予初始權(quán)值衬以,并將主席的權(quán)重設(shè)置為1。我們一般使用constructor賦初值校摩,這與C++等語(yǔ)言類(lèi)似:
constructor(bytes32[] memory proposalNames) {
? ? chairperson = msg.sender;
? ? voters[chairperson].weight = 1;
? ? for (uint i = 0; i < proposalNames.length; i++) {
? ? ? ? proposals.push(Proposal({
? ? ? ? ? ? name: proposalNames[i],
? ? ? ? ? ? voteCount: 0
? ? ? ? }));
? ? }
}
所有提案的名稱通過(guò)參數(shù) bytes32[] proposalNames 傳入看峻,逐個(gè)記錄到狀態(tài)變量 proposals 中。同時(shí)用 msg.sender 獲取當(dāng)前調(diào)用消息的發(fā)送者的地址衙吩,記錄為投票發(fā)起人 chairperson互妓,該發(fā)起人投票權(quán)重設(shè)為 1。
接下來(lái)我們需要給每個(gè)投票者賦予權(quán)重:
function giveRightToVote(address voter) public {
? ? require(
? ? ? ? msg.sender == chairperson,
? ? ? ? "Only chairperson can give right to vote."
? ? );
? ? require(
? ? ? ? !voters[voter].voted,
? ? ? ? "The voter already voted."
? ? );
? ? require(voters[voter].weight == 0);
? ? voters[voter].weight = 1;
}
該函數(shù)給 address voter 賦予投票權(quán)坤塞,即將 voter 的投票權(quán)重設(shè)為 1冯勉,存入 voters 狀態(tài)變量。
上面這個(gè)函數(shù)只有投票發(fā)起人 chairperson 可以調(diào)用摹芙。這里用到了 require((msg.sender == chairperson) && !voters[voter].voted) 函數(shù)灼狰。如果 require 中表達(dá)式結(jié)果為 false,這次調(diào)用會(huì)中止浮禾,且回滾所有狀態(tài)和以太幣余額的改變到調(diào)用前交胚。但已消耗的 Gas 不會(huì)返還份汗。
下面一段是整段代碼的重點(diǎn),其作用是委托其他人代理投票蝴簇,基本思路是:
使用require判斷委托人是否已投票(若投過(guò)票再委托則重復(fù)投票)杯活,并判斷被委托對(duì)象是否是自己
當(dāng)判斷被委托人不是0地址(主席)時(shí),被委托人代理委托人的票熬词,【繞口警告】由于被委托人也可能委托了別人旁钧,因此這里需要一直循環(huán)直到找到最后沒(méi)有委托別人的被委托人為止!
委托人找到對(duì)應(yīng)的被委托人互拾,委托人已投票(避免重復(fù)投票)
判斷被委托人是否已投票歪今,若投了票則將被委托人投的提案票數(shù)加上委托人的權(quán)重,若未投票則令被委托人的權(quán)重加上委托人的權(quán)重(以后投票自然相當(dāng)于投兩票)
注:該函數(shù)使用了 while 循環(huán)摩幔,這里合約編寫(xiě)者需要十分謹(jǐn)慎彤委,防止調(diào)用者消耗過(guò)多 Gas,甚至出現(xiàn)死循環(huán)或衡。
function delegate(address to) public {
? ? Voter storage sender = voters[msg.sender];
? ? require(!sender.voted, "You already voted.");
? ? require(to != msg.sender, "Self-delegation is disallowed.");
? ? while (voters[to].delegate != address(0)) {
? ? to = voters[to].delegate;
? ? require(to != msg.sender, "Found loop in delegation.");
? ? }
? ? sender.voted = true;
? ? sender.delegate = to;
? ? Voter storage delegate_ = voters[to];
? ? if (delegate_.voted) {
? ? proposals[delegate_.vote].voteCount += sender.weight;
? ? } else {
? ? delegate_.weight += sender.weight;
? ? }
}
投票部分僅是幾個(gè)簡(jiǎn)單的條件判斷:
function vote(uint proposal) public {
? ? ? ? Voter storage sender = voters[msg.sender];
? ? ? ? require(sender.weight != 0, "Has no right to vote");
? ? ? ? require(!sender.voted, "Already voted.");
? ? ? ? sender.voted = true;
? ? ? ? sender.vote = proposal;
? ? ? ? proposals[proposal].voteCount += sender.weight;
? ? }
用 voters[msg.sender] 獲取投票人焦影,即此次調(diào)用的發(fā)起人。接下來(lái)檢查是否是重復(fù)投票封断,如果不是斯辰,進(jìn)行投票后相關(guān)狀態(tài)變量的更新。
接下來(lái)是計(jì)算獲勝提案:
function winningProposal() public view
? ? ? ? returns (uint winningProposal_)
{
? ? uint winningVoteCount = 0;
? ? for (uint p = 0; p < proposals.length; p++) {
? ? ? ? if (proposals[p].voteCount > winningVoteCount) {
? ? ? ? ? ? winningVoteCount = proposals[p].voteCount;
? ? ? ? ? ? winningProposal_ = p;
? ? ? ? }
? ? }
}
returns (uint winningProposal) 指定了函數(shù)的返回值類(lèi)型坡疼,constant 表示該函數(shù)不會(huì)改變合約狀態(tài)變量的值彬呻。
最后是查詢獲勝者名稱:
function winnerName() public view
? ? ? ? returns (bytes32 winnerName_)
{
? ? winnerName_ = proposals[winningProposal()].name;
}
這里采用內(nèi)部調(diào)用 winningProposal() 函數(shù)的方式獲得獲勝提案。如果需要采用外部調(diào)用柄瑰,則需要寫(xiě)為 this.winningProposal()闸氮。