導讀:
本文旨在引導對 DApp 開發感興趣的開發者,構建一個基於以太坊去中心化應用,經過開發一款功能完備的競猜遊戲,邁出 DApp 開發的第一步,經過實例講解 Solidity 語言的經常使用語法,以及前端如何與智能合約進行交互。
若是正在閱讀的你,從未接觸過 DApp 開發也沒關係,能夠先閱讀【 區塊鏈上編程:DApp開發簡介】進行前置知識補充。
隨着加密貓、FOMO3D 等遊戲的火爆,去中心化應用在遊戲領域遍地開花,下面就帶着你們一塊兒開發一款簡單有趣的 DApp 遊戲,幫助你們熟悉 DApp 開發。javascript
本 DApp 實現的合約功能:用戶從 0-6 的數字中,任意組合三位數進行投注,合約計算出 3 位隨機數,根據隨機數的組合規則分別給予用戶不一樣倍數的獎勵,如隨機數爲 AAA ,A 等於 6 則獎勵至少 20 倍投注金額,即獎池全部餘額;A 不等於 6 則獎勵 5 倍投注金額;隨機數爲 AAB,則獎勵 2 倍投注金額;隨機數爲 ABC 則不獎勵,同時用戶可查看獎池餘額和我的投注記錄。前端
能夠看出合約須要實現用戶投注、生成隨機數、發放獎勵、獎池餘額查詢的功能,接下來編寫咱們的合約代碼。java
新建Lottery.sol
合約文件,聲明合約版本,^
表示合約編譯版本爲 0.4.0 至 0.5.0(不含 0.5.0)。git
pragma solidity ^0.4.0;
定義合約基本內容,同時聲明最低投注金額。程序員
contract Lottery { uint public betMoney = 10 finney; }
生成隨機數,經過區塊難度block.difficulty
和內置函數keccak256
生成隨機數,在EVM
中經常使用的數據存儲位置:memory
、storage
,函數的參數、返回值默認存儲在memory
中,狀態變量默認存儲在storage
中,咱們能夠經過聲明memory
、storage
改變默認存儲位置,二者的存儲都須要消耗gas
,但storage
的開銷遠大於memory
。github
contract Lottery { ... function generateRandomNumber() private view returns(uint[]) { uint[] memory dices = new uint[](3); for (uint i = 0; i < 3; i++) { dices[i] = uint(keccak256(abi.encodePacked(block.difficulty, now, i))) % 6 + 1; } return dices; } ... }
獲取合約餘額,address
類型比較重要的成員屬性主要有balance
、transfer
,分別爲獲取地址餘額、轉移eth
至該地址,默認eth
單位是wei
。web
contract Lottery { ... function getBankBalance() public view returns(uint) { return address(this).balance; } ... }
用戶投注,投注方法須要使用payable
關鍵字描述,用來表示能夠接收eth
;經過msg.sender
和msg.value
得到交易發送者地址和當前交易附帶的eth
。一般使用require
來校驗外部輸入參數,當斷定條件爲false
時,則會將剩餘的gas
退回,同時回滾交易;assert
則用來處理合約內部的邏輯錯誤,當錯誤發生時會消耗掉全部gas
,同時回滾交易。編程
contract Lottery { ... function bet() public payable { uint amount = msg.value; require(amount >= betMoney, 'bet money not less than 10 finney'); require(address(this).balance >= amount * 20, 'contract money not enough to pay reward'); uint[] memory dices = generateRandomNumber(); require(dices.length == 3, 'dices illegal'); address addr = msg.sender; bool isReward = false; uint reward = 0; if ((dices[0] == dices[1]) && (dices[1] == dices[2]) && (dices[0] == 6)) { isReward = true; reward = address(this).balance; } else if ((dices[0] == dices[1]) && (dices[1] == dices[2]) && (dices[0] != 6)) { isReward = true; reward = amount * 5; } else if ((dices[0] == dices[1]) || (dices[0] == dices[2]) || (dices[1] == dices[2])) { isReward = true; reward = amount * 2; } if (isReward) { addr.transfer(reward); } } ...
定義事件,經過合約內部觸發事件,web3 監聽到事件回調進行相應邏輯處理,從而進行頁面 UI 更新;同時前端也能夠經過對應事件名稱獲取日誌信息。segmentfault
contract Lottery { ... event BetList( address addr, uint amount, bool isReward, uint reward, uint[] dices ); function bet() public payable { ... emit BetList(addr, amount, isReward, reward, dices); } ...
至此,咱們已經寫完了合約代碼,前端頁面實現就不在此贅述,主要介紹如何使用 web3 與合約交互,這裏使用到的 web3 版本是 1.0,web3 1.0 和 0.2x.x 的 API 調用方式差異較大,1.0 的 API 支持異步調用。瀏覽器
安裝 Metamask 瀏覽器插件後,會在瀏覽器頁面內注入一個 web3 實例。檢測頁面中是否存在 web3 實例,若是不存在則鏈接本身的實例。
import Web3 from 'web3'; if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); } else { web3 = new Web3(new Web3.providers.HttpProvider(NODE_NRL)); }
傳入合約 ABI,合約地址,實例化合約對象。
this.contract = new web3.eth.Contract( CONTRACT_ABI, CONTRACT_ADDR, );
調用合約中的投注方法,經過try catch
能夠捕獲到 Metamask 彈窗取消交易操做。
userBet = async () => { try { await this.contract.methods .bet( ... ) .send({ from: ACCOUNT, value: MONEY, }); } catch (error) { ... } }
查詢記錄的日誌,能夠經過指定事件名稱、區塊高度及過濾條件來進行日誌查詢,值得注意的是,在合約內不能查詢到日誌信息。
queryEvent = async () => { const event = await this.contract.getPastEvents( EVENT_NAME, { filter: {}, fromBlock: 0, toBlock: 'latest', } ) }
好比修改用戶投注金額及充值這類敏感操做,就須要管理員的權限來進行操做。一樣地,咱們也能夠拓展贊助商的功能,經過充值獎池的累計金額排名來展現贊助商的廣告,這裏就不作展開了。
定義修飾器,在構造函數裏設置管理員地址,將建立合約的帳戶設置爲管理員。
contract Lottery { ... address public owner; modifier onlyOwner() { require(owner == msg.sender); _; } constructor() public { owner = msg.sender; } ... }
實現修改投注金額的功能,僅管理員帳戶可觸發。
contract Lottery { ... function setBetMoney(uint _betMoney) public onlyOwner { betMoney = _betMoney; } function deposit() public payable onlyOwner {} ... }
當前隨機數的實現經過鏈上信息生成,這種生成隨機數的方式容易受到不誠實的節點攻擊。下一篇文章將經過多個實例介紹相應的第三方工具庫的使用(Oricalize、SafeMath、OpenZepplin),生成安全的隨機數。
前置文章:
文 / ielapp
一個簡單的程序員編 / 熒聲
本文已由做者受權發佈,版權屬於創宇前端。歡迎註明出處轉載本文。本文連接:https://knownsec-fed.com/2018...
想要訂閱更多來自知道創宇開發一線的分享,請搜索關注咱們的微信公衆號:創宇前端(KnownsecFED)。歡迎留言討論,咱們會盡量回復。
歡迎點贊、收藏、留言評論、轉發分享和打賞支持咱們。打賞將被徹底轉交給文章做者。
感謝您的閱讀。