https://segmentfault.com/a/1190000015295148
流程
合約代碼編寫(Solidity)-> 合約編譯(solc)-> 合約部署(web3)
開發(fā)語言及工具:
- 區(qū)塊鏈節(jié)點(diǎn):ganache-cli
- 基礎(chǔ)環(huán)境:node
- 合約開發(fā)語言:Solidity
- 合約編譯器:solc
- 合約訪問庫:web3.js
基礎(chǔ)環(huán)境安裝
- 1、安裝 node.js
- 2、安裝 ganache-cli
ganache-cli
sudo npm install -g ganache-cli
ganache 默認(rèn)會自動創(chuàng)建 10 個賬戶誊酌,每個賬戶有 100 個以太幣(ETH:Ether)觉啊。 可以把賬戶視為銀行賬戶,以太幣就是以太坊生態(tài)系統(tǒng)中的貨幣棵里。
面輸出的最后一句話过蹂,描述了節(jié)點(diǎn)仿真器的監(jiān)聽地址和端口為localhost:8545族檬,在使用 web3.js 時茬腿,需要傳入這個地址來告訴web3js庫應(yīng)當(dāng)連接到哪一個節(jié)點(diǎn)。
合約設(shè)計
合約中的屬性用來聲明合約的狀態(tài)宜雀,而合約中的方法則提供修改狀態(tài)的訪問接口切平。
重點(diǎn):
- 合約狀態(tài)是持久化到區(qū)塊鏈上的,因此對合約狀態(tài)的修改需要消耗以太幣辐董。
- 只有在合約部署到區(qū)塊鏈的時候悴品,才會調(diào)用構(gòu)造函數(shù),并且只調(diào)用一次。
- 與 web 世界里每次部署代碼都會覆蓋舊代碼不同苔严,在區(qū)塊鏈上部署的合約是不可改變的定枷,也就是說,如果你更新 合約并再次部署届氢,舊的合約仍然會在區(qū)塊鏈上存在欠窒,并且合約的狀態(tài)數(shù)據(jù)也依然存在。新的部署將會創(chuàng)建合約的一 個新的實(shí)例退子。
狀態(tài)變量和整數(shù)
Solidity
顯著特點(diǎn):
- 后綴.sol
- 強(qiáng)類型語言
- 語法和javascript類似
函數(shù)類型
view: 讀取區(qū)塊鏈上的數(shù)據(jù)岖妄,但是不修改區(qū)塊鏈上面的數(shù)據(jù)
pure:不修改也不讀取區(qū)塊鏈上面的數(shù)據(jù)
一筆事務(wù)的控制臺信息
地址類型
合約代碼編寫(Solidity)-> 合約編譯(solc)-> 合約部署(web3)
開發(fā)語言及工具:
- 區(qū)塊鏈節(jié)點(diǎn):ganache-cli
- 基礎(chǔ)環(huán)境:node
- 合約開發(fā)語言:Solidity
- 合約編譯器:solc
- 合約訪問庫:web3.js
版本指令
標(biāo)明 Solidity 編譯器的版本. 以避免將來新的編譯器可能破壞你的代碼。
pragma solidity ^0.4.19;
^0.4.20 0.4.x
~0.4.20 0.x.x
EVM數(shù)據(jù)存儲
storage:狀態(tài)變量(全局變量)會存儲在這里寂祥。永久性存儲數(shù)據(jù)(因?yàn)闀褦?shù)據(jù)寫入?yún)^(qū)塊鏈當(dāng)中)荐虐,
stack:函數(shù)中的本地變量(局部變量)默認(rèn)會存儲在這里,暫時性存儲數(shù)據(jù)丸凭。
memory:暫時性存儲數(shù)據(jù)福扬,實(shí)參(函數(shù)中實(shí)際傳遞的參數(shù))默認(rèn)會存儲在這里。
tips:storage和memory都需要消耗gas惜犀,但是storage更貴铛碑。
結(jié)構(gòu)體作為函數(shù)參數(shù),函數(shù)必須是internal類型
數(shù)組
// 固定長度為2的靜態(tài)數(shù)組:
uint[2] fixedArray;
// 固定長度為5的string類型的靜態(tài)數(shù)組:
string[5] stringArray;
// 動態(tài)數(shù)組向拆,長度不固定亚茬,可以動態(tài)添加元素:
uint[] dynamicArray;
公共數(shù)組
你可以定義 public 數(shù)組, Solidity 會自動創(chuàng)建 getter 方法. 語法如下:
Person[] public people;
其它的合約可以從這個數(shù)組讀取數(shù)據(jù)(但不能寫入數(shù)據(jù)),所以這在合約中是一個有用的保存公共數(shù)據(jù)的模式浓恳。
定義函數(shù)
function eatHamburgers(string _name, uint _amount) {
}
注:: 習(xí)慣上函數(shù)里的變量都是以(_)開頭 (但不是硬性規(guī)定) 以區(qū)別全局變量刹缝。我們整個教程都會沿用這個習(xí)慣。
私有 / 公共函數(shù)
Solidity 定義的函數(shù)的屬性默認(rèn)為公共颈将。 這就意味著任何一方 (或其它合約) 都可以調(diào)用你合約里的函數(shù)梢夯。
顯然,不是什么時候都需要這樣晴圾,而且這樣的合約易于受到攻擊颂砸。
uint[] numbers;
function _addToArray(uint _number) private {
numbers.push(_number);
}
返回值
string greeting = "What's up dog";
function sayHello() public returns (string) {
return greeting;
}
函數(shù)的修飾符****view,returns
把函數(shù)定義為 view, 意味著它只能讀取數(shù)據(jù)不能更改數(shù)據(jù):
function sayHello() public view returns (string) {}
Solidity 還支持 pure 函數(shù), 表明這個函數(shù)甚至都不訪問應(yīng)用里的數(shù)據(jù),例如:
function _multiply(uint a, uint b) private pure returns (uint) {
return a * b;
}
Keccak256 和 類型轉(zhuǎn)換
Ethereum 內(nèi)部有一個散列函數(shù)keccak256死姚,它用了SHA3版本人乓。一個散列函數(shù)基本上就是把一個字符串轉(zhuǎn)換為一個256位的16進(jìn)制數(shù)字。字符串的一個微小變化會引起散列數(shù)據(jù)極大變化都毒。
注: 在區(qū)塊鏈中安全地產(chǎn)生一個隨機(jī)數(shù)是一個很難的問題色罚, 本例的方法不安全,但是在我們的Zombie DNA算法里不是那么重要账劲,已經(jīng)很好地滿足我們的需要了戳护。
事件
事件 是合約和區(qū)塊鏈通訊的一種機(jī)制金抡。你的前端應(yīng)用“監(jiān)聽”某些事件,并做出反應(yīng)腌且。
// 這里建立事件
event IntegersAdded(uint x, uint y, uint result);
function add(uint _x, uint _y) public {
uint result = _x + _y;
//觸發(fā)事件梗肝,通知app
IntegersAdded(_x, _y, result);
return result;
}
YourContract.IntegersAdded(function(error, result) {
// 干些事
}
映射(****Mapping)和地址(Address)
Addresses(地址)
以太坊區(qū)塊鏈由 account (賬戶)組成,你可以把它想象成銀行賬戶铺董。一個帳戶的余額是 以太 (在以太坊區(qū)塊鏈上使用的幣種)巫击,你可以和其他帳戶之間支付和接受以太幣,就像你的銀行帳戶可以電匯資金到其他銀行帳戶一樣柄粹。
每個帳戶都有一個“地址”喘鸟,你可以把它想象成銀行賬號。這是賬戶唯一的標(biāo)識符驻右,它看起來長這樣:
0x0cE446255506E92DF41614C46F1d6df9Cc969183
Mapping(映射)
//對于金融應(yīng)用程序什黑,將用戶的余額保存在一個 uint類型的變量中:
mapping (address => uint) public accountBalance;
//或者可以用來通過userId 存儲/查找的用戶名
mapping (uint => string) userIdToName;
msg.sender
在 Solidity 中,有一些全局變量可以被所有函數(shù)調(diào)用堪夭。 其中一個就是 msg.sender愕把,它指的是當(dāng)前調(diào)用者(或智能合約)的 address。
注意:在 Solidity 中森爽,功能執(zhí)行始終需要從外部調(diào)用者開始恨豁。 一個合約只會在區(qū)塊鏈上什么也不做,除非有人調(diào)用其中的函數(shù)爬迟。所以 msg.sender 總是存在的橘蜜。
注意:在 Solidity 中,功能執(zhí)行始終需要從外部調(diào)用者開始付呕。 一個合約只會在區(qū)塊鏈上什么也不做计福,除非有人調(diào)用其中的函數(shù)。所以 msg.sender 總是存在的徽职。
以下是使用**** msg.sender 來更新 mapping 的例子:
mapping (address => uint) favoriteNumber;
function setMyNumber(uint _myNumber) public {
// 更新我們的 favoriteNumber
映射來將 _myNumber
存儲在 msg.sender
名下
favoriteNumber[msg.sender] = _myNumber;
// 存儲數(shù)據(jù)至映射的方法和將數(shù)據(jù)存儲在數(shù)組相似
}
function whatIsMyNumber() public view returns (uint) {
// 拿到存儲在調(diào)用者地址名下的值
// 若調(diào)用者還沒調(diào)用 setMyNumber象颖,則值為 0
return favoriteNumber[msg.sender];
}
Require
function sayHiToVitalik(string _name) public returns (string) {
// 比較 _name 是否等于 "Vitalik". 如果不成立,拋出異常并終止程序
// (敲黑板: Solidity 并不支持原生的字符串比較, 我們只能通過比較
// 兩字符串的 keccak256 哈希值來進(jìn)行判斷)
require(keccak256(_name) == keccak256("Vitalik"));
// 如果返回 true, 運(yùn)行如下語句
return "Hi!";
}
繼承 Inheritance
有個讓**** Solidity 的代碼易于管理的功能姆钉,就是合約 inheritance (繼承):
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Such Moon BabyDoge";
}
}
由于 BabyDoge 是從 Doge 那里 inherits (繼承)過來的说订。 這意味著當(dāng)你編譯和部署了 BabyDoge,它將可以訪問 catchphrase() 和 anotherCatchphrase()和其他我們在 Doge 中定義的其他公共函數(shù)潮瓶。
引入****Import
在 Solidity 中陶冷,當(dāng)你有多個文件并且想把一個文件導(dǎo)入另一個文件時,可以使用 import 語句:
import "./someothercontract.sol";
contract newContract is SomeOtherContract {
}
Storage與Memory
在 Solidity 中毯辅,有兩個地方可以存儲變量 —— storage 或 memory埃叭。
Storage 變量是指永久存儲在區(qū)塊鏈中的變量。 Memory 變量則是臨時的悉罕,當(dāng)外部函數(shù)對某合約調(diào)用完成時赤屋,內(nèi)存型變量即被移除。 你可以把它想象成存儲在你電腦的硬盤或是RAM中數(shù)據(jù)的關(guān)系壁袄。
大多數(shù)時候你都用不到這些關(guān)鍵字类早,默認(rèn)情況下 Solidity 會自動處理它們。 狀態(tài)變量(在函數(shù)之外聲明的變量)默認(rèn)為“存儲”形式嗜逻,并永久寫入?yún)^(qū)塊鏈涩僻;而在函數(shù)內(nèi)部聲明的變量是“內(nèi)存”型的,它們函數(shù)調(diào)用結(jié)束后消失栈顷。
internal 和 external
internal 和 private 類似逆日,不過, 如果某個合約繼承自其父合約萄凤,這個合約即可以訪問父合約中定義的“內(nèi)部”函數(shù)室抽。(嘿,這聽起來正是我們想要的那樣C遗)坪圾。
external 與public 類似,只不過這些函數(shù)只能在合約之外調(diào)用 - 它們不能被合約內(nèi)的其他函數(shù)調(diào)用惑朦。稍后我們將討論什么時候使用 external 和 public兽泄。
Ownable
下面是一個 Ownable 合約的例子: 來自 OpenZeppelin Solidity 庫的 Ownable 合約。 OpenZeppelin 是主打安保和社區(qū)審查的智能合約庫漾月,您可以在自己的 DApps中引用病梢。等把這一課學(xué)完,您不要催我們發(fā)布下一課梁肿,最好利用這個時間把 OpenZeppelin 的網(wǎng)站看看蜓陌,保管您會學(xué)到很多東西!
- 構(gòu)造函數(shù):function Ownable()是一個 constructor (構(gòu)造函數(shù))栈雳,構(gòu)造函數(shù)不是必須的护奈,它與合約同名,構(gòu)造函數(shù)一生中唯一的一次執(zhí)行哥纫,就是在合約最初被創(chuàng)建的時候霉旗。
- 函數(shù)修飾符:modifier onlyOwner()。 修飾符跟函數(shù)很類似蛀骇,不過是用來修飾其他已有函數(shù)用的厌秒, 在其他語句執(zhí)行前,為它檢查下先驗(yàn)條件擅憔。 在這個例子中鸵闪,我們就可以寫個修飾符 onlyOwner 檢查下調(diào)用者,確保只有合約的主人才能運(yùn)行本函數(shù)暑诸。我們下一章中會詳細(xì)講述修飾符蚌讼,以及那個奇怪的_;辟灰。
- indexed 關(guān)鍵字:別擔(dān)心,我們還用不到它篡石。
- @title Ownable*
- @dev The Ownable contract has an owner address, and provides basic authorization control*
- functions, this simplifies the implementation of "user permissions".*
/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
** @dev The Ownable constructor sets the original owner
of the contract to the sender*
** account.*
*/
function Ownable() public {
owner = msg.sender;
}
/**
** @dev Throws if called by any account other than the owner.*
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
** @dev Allows the current owner to transfer control of the contract to a newOwner.*
** @param newOwner The address to transfer ownership to.*
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
Gas-驅(qū)動以太坊DApps的能源
在 Solidity 中芥喇,你的用戶想要每次執(zhí)行你的 DApp 都需要支付一定的 gas,gas 可以用以太幣購買凰萨,因此继控,用戶每次跑 DApp 都得花費(fèi)以太幣。
一個 DApp 收取多少 gas 取決于功能邏輯的復(fù)雜程度胖眷。每個操作背后武通,都在計算完成這個操作所需要的計算資源,(比如珊搀,存儲數(shù)據(jù)就比做個加法運(yùn)算貴得多)冶忱, 一次操作所需要花費(fèi)的 gas 等于這個操作背后的所有運(yùn)算花銷的總和。
由于運(yùn)行你的程序需要花費(fèi)用戶的真金白銀食棕,在以太坊中代碼的編程語言朗和,比其他任何編程語言都更強(qiáng)調(diào)優(yōu)化。同樣的功能簿晓,使用笨拙的代碼開發(fā)的程序眶拉,比起經(jīng)過精巧優(yōu)化的代碼來,運(yùn)行花費(fèi)更高憔儿,這顯然會給成千上萬的用戶帶來大量不必要的開銷忆植。
為何要****gas來驅(qū)動?
以太坊就像一個巨大谒臼、緩慢朝刊、但非常安全的電腦。當(dāng)你運(yùn)行一個程序的時候蜈缤,網(wǎng)絡(luò)上的每一個節(jié)點(diǎn)都在進(jìn)行相同的運(yùn)算拾氓,以驗(yàn)證它的輸出 —— 這就是所謂的”去中心化“ 由于數(shù)以千計的節(jié)點(diǎn)同時在驗(yàn)證著每個功能的運(yùn)行,這可以確保它的數(shù)據(jù)不會被被監(jiān)控底哥,或者被刻意修改咙鞍。
可能會有用戶用無限循環(huán)堵塞網(wǎng)絡(luò),抑或用密集運(yùn)算來占用大量的網(wǎng)絡(luò)資源趾徽,為了防止這種事情的發(fā)生续滋,以太坊的創(chuàng)建者為以太坊上的資源制定了價格,想要在以太坊上運(yùn)算或者存儲孵奶,你需要先付費(fèi)疲酌。
注意:如果你使用側(cè)鏈,倒是不一定需要付費(fèi),比如咱們在 Loom Network 上構(gòu)建的 CryptoZombies 就免費(fèi)朗恳。你不會想要在以太坊主網(wǎng)上玩兒“魔獸世界”吧湿颅? - 所需要的 gas 可能會買到你破產(chǎn)。但是你可以找個算法理念不同的側(cè)鏈來玩它僻肖。我們將在以后的課程中咱們會討論到肖爵,什么樣的 DApp 應(yīng)該部署在太坊主鏈上,什么又最好放在側(cè)鏈臀脏。
時間單位
readyTime 稍微復(fù)雜點(diǎn)。我們希望增加一個“冷卻周期”冀自,表示僵尸在兩次獵食或攻擊之之間必須等待的時間揉稚。如果沒有它,僵尸每天可能會攻擊和繁殖1,000次熬粗,這樣游戲就太簡單了搀玖。
Solidity 使用自己的本地時間單位。
變量 now 將返回當(dāng)前的unix時間戳(自1970年1月1日以來經(jīng)過的秒數(shù))驻呐。我寫這句話時 unix 時間是 1515527488灌诅。
注意:Unix時間傳統(tǒng)用一個32位的整數(shù)進(jìn)行存儲。這會導(dǎo)致“2038年”問題含末,當(dāng)這個32位的unix時間戳不夠用猜拾,產(chǎn)生溢出,使用這個時間的遺留系統(tǒng)就麻煩了佣盒。所以挎袜,如果我們想讓我們的 DApp 跑夠20年,我們可以使用64位整數(shù)表示時間肥惭,但為此我們的用戶又得支付更多的 gas盯仪。真是個兩難的設(shè)計啊蜜葱!
Solidity 還包含秒(seconds)全景,分鐘(minutes),小時(hours)牵囤,天(days)爸黄,周(weeks) 和 年(years) 等時間單位。它們都會轉(zhuǎn)換成對應(yīng)的秒數(shù)放入 uint 中奔浅。所以 1分鐘 就是 60馆纳,1小時是 3600(60秒×60分鐘),1天是86400(24小時×60分鐘×60秒)汹桦,以此類推
函數(shù)修飾符
- 1鲁驶、我們有決定函數(shù)何時和被誰調(diào)用的可見性修飾符: private 意味著它只能被合約內(nèi)部調(diào)用; internal 就像 private 但是也能被繼承的合約調(diào)用舞骆; external 只能從合約外部調(diào)用钥弯;最后 public 可以在任何地方調(diào)用径荔,不管是內(nèi)部還是外部。
- 2脆霎、我們也有狀態(tài)修飾符总处, 告訴我們函數(shù)如何和區(qū)塊鏈交互: view 告訴我們運(yùn)行這個函數(shù)不會更改和保存任何數(shù)據(jù); pure 告訴我們這個函數(shù)不但不會往區(qū)塊鏈寫數(shù)據(jù)睛蛛,它甚至不從區(qū)塊鏈讀取數(shù)據(jù)鹦马。這兩種在被從合約外部調(diào)用的時候都不花費(fèi)任何gas(但是它們在被內(nèi)部其他函數(shù)調(diào)用的時候?qū)馁M(fèi)gas)。
- 3忆肾、然后我們有了自定義的 modifiers荸频,例如在第三課學(xué)習(xí)的: onlyOwner 和 aboveLevel。 對于這些修飾符我們可以自定義其對函數(shù)的約束邏輯客冈。
payable修飾符
payable 方法是讓 Solidity 和以太坊變得如此酷的一部分 —— 它們是一種可以接收以太的特殊函數(shù)旭从。
先放一下。當(dāng)你在調(diào)用一個普通網(wǎng)站服務(wù)器上的API函數(shù)的時候场仲,你無法用你的函數(shù)傳送美元——你也不能傳送比特幣和悦。
但是在以太坊中, 因?yàn)殄X (以太), 數(shù)據(jù) (事務(wù)負(fù)載)渠缕, 以及合約代碼本身都存在于以太坊鸽素。你可以在同時調(diào)用函數(shù) 并付錢給另外一個合約。
這就允許出現(xiàn)很多有趣的邏輯褐健, 比如向一個合約要求支付一定的錢來運(yùn)行一個函數(shù)付鹿。
示例
contract OnlineStore {
function buySomething() external payable {
// 檢查以確定0.001以太發(fā)送出去來運(yùn)行函數(shù):
require(msg.value == 0.001 ether);
// 如果為真,一些用來向函數(shù)調(diào)用者發(fā)送數(shù)字內(nèi)容的邏輯
transferThing(msg.sender);
}
}
提現(xiàn)
在上一節(jié)蚜迅,我們學(xué)習(xí)了如何向合約發(fā)送以太舵匾,那么在發(fā)送之后會發(fā)生什么呢?
在你發(fā)送以太之后谁不,它將被存儲進(jìn)以合約的以太坊賬戶中坐梯, 并凍結(jié)在哪里 —— 除非你添加一個函數(shù)來從合約中把以太提現(xiàn)。
你可以寫一個函數(shù)來從合約中提現(xiàn)以太刹帕,類似這樣:
contract GetPaid is Ownable {
function withdraw() external onlyOwner {
owner.transfer(this.balance);
}
}
隨機(jī)數(shù)
優(yōu)秀的游戲都需要一些隨機(jī)元素吵血,那么我們在 Solidity 里如何生成隨機(jī)數(shù)呢?
真正的答案是你不能偷溺,或者最起碼蹋辅,你無法安全地做到這一點(diǎn)。
我們來看看為什么
用** keccak256 **來制造隨機(jī)數(shù)
Solidity 中最好的隨機(jī)數(shù)生成器是 keccak256 哈希函數(shù).
我們可以這樣來生成一些隨機(jī)數(shù)
// 生成一個0到100的隨機(jī)數(shù):
uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;
這個方法首先拿到 now 的時間戳挫掏、 msg.sender侦另、 以及一個自增數(shù) nonce (一個僅會被使用一次的數(shù),這樣我們就不會對相同的輸入值調(diào)用一次以上哈希函數(shù)了)。
然后利用 keccak 把輸入的值轉(zhuǎn)變?yōu)橐粋€哈希值, 再將哈希值轉(zhuǎn)換為 uint, 然后利用 % 100 來取最后兩位, 就生成了一個0到100之間隨機(jī)數(shù)了褒傅。
這個方法很容易被不誠實(shí)的節(jié)點(diǎn)攻擊
在以太坊上, 當(dāng)你在和一個合約上調(diào)用函數(shù)的時候, 你會把它廣播給一個節(jié)點(diǎn)或者在網(wǎng)絡(luò)上的 transaction 節(jié)點(diǎn)們弃锐。 網(wǎng)絡(luò)上的節(jié)點(diǎn)將收集很多事務(wù), 試著成為第一個解決計算密集型數(shù)學(xué)問題的人,作為“工作證明”殿托,然后將“工作證明”(Proof of Work, PoW)和事務(wù)一起作為一個 block 發(fā)布在網(wǎng)絡(luò)上霹菊。
一旦一個節(jié)點(diǎn)解決了一個PoW, 其他節(jié)點(diǎn)就會停止嘗試解決這個 PoW, 并驗(yàn)證其他節(jié)點(diǎn)的事務(wù)列表是有效的,然后接受這個節(jié)點(diǎn)轉(zhuǎn)而嘗試解決下一個節(jié)點(diǎn)支竹。
這就讓我們的隨機(jī)數(shù)函數(shù)變得可利用了
我們假設(shè)我們有一個硬幣翻轉(zhuǎn)合約——正面你贏雙倍錢旋廷,反面你輸?shù)羲械腻X。假如它使用上面的方法來決定是正面還是反面 (random >= 50 算正面, random < 50 算反面)礼搁。
如果我正運(yùn)行一個節(jié)點(diǎn)柳洋,我可以 只對我自己的節(jié)點(diǎn) 發(fā)布一個事務(wù),且不分享它叹坦。 我可以運(yùn)行硬幣翻轉(zhuǎn)方法來偷窺我的輸贏 — 如果我輸了,我就不把這個事務(wù)包含進(jìn)我要解決的下一個區(qū)塊中去卑雁。我可以一直運(yùn)行這個方法募书,直到我贏得了硬幣翻轉(zhuǎn)并解決了下一個區(qū)塊,然后獲利测蹲。
所以我們該如何在以太坊上安全地生成隨機(jī)數(shù)呢莹捡?
因?yàn)閰^(qū)塊鏈的全部內(nèi)容對所有參與者來說是透明的, 這就讓這個問題變得很難扣甲,它的解決方法不在本課程討論范圍篮赢,你可以閱讀 這個 StackOverflow 上的討論 來獲得一些主意。 一個方法是利用 oracle 來訪問以太坊區(qū)塊鏈之外的隨機(jī)數(shù)函數(shù)琉挖。
ERC20 ERC721標(biāo)準(zhǔn)(七)
預(yù)防溢出
僵尸轉(zhuǎn)移給0 地址(這被稱作 “燒幣”, 基本上就是把代幣轉(zhuǎn)移到一個誰也沒有私鑰的地址启泣,讓這個代幣永遠(yuǎn)也無法恢復(fù))
假設(shè)我們有一個 uint8, 只能存儲8 bit數(shù)據(jù)。這意味著我們能存儲的最大數(shù)字就是二進(jìn)制 11111111 (或者說十進(jìn)制的 2^8 - 1 = 255).
來看看下面的代碼示辈。最后 number 將會是什么值寥茫?
uint8 number = 255;
number++;
在這個例子中,我們導(dǎo)致了溢出 — 雖然我們加了1矾麻, 但是 number 出乎意料地等于 0了纱耻。 (如果你給二進(jìn)制 11111111 加1, 它將被重置為 00000000,就像鐘表從 23:59 走向 00:00)险耀。
下溢(underflow)也類似弄喘,如果你從一個等于 0 的 uint8 減去 1, 它將變成 255 (因?yàn)?uint 是無符號的,其不能等于負(fù)數(shù))甩牺。
使用 SafeMath
為了防止這些情況蘑志,OpenZeppelin 建立了一個叫做 SafeMath 的 庫(library),默認(rèn)情況下可以防止這些問題。
不過在我們使用之前…… 什么叫做庫?
一個庫是 Solidity 中一種特殊的合約卖漫。其中一個有用的功能是給原始數(shù)據(jù)類型增加一些方法费尽。
比如,使用 SafeMath 庫的時候羊始,我們將使用 using SafeMath for uint256 這樣的語法旱幼。 SafeMath 庫有四個方法 — add, sub突委, mul柏卤, 以及 div。現(xiàn)在我們可以這樣來讓 uint256 調(diào)用這些方法:
using SafeMath for uint256;
uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10
assert和require區(qū)別
assert 和 require 相似匀油,若結(jié)果為否它就會拋出錯誤缘缚。 assert 和 require 區(qū)別在于,require 若失敗則會返還給用戶剩下的 gas敌蚜, assert 則不會桥滨。所以大部分情況下,你寫代碼的時候會比較喜歡 require弛车,assert 只在代碼可能出現(xiàn)嚴(yán)重錯誤的時候使用齐媒,比如 uint 溢出。
Solidity 社區(qū)所使用的一個標(biāo)準(zhǔn)是使用一種被稱作 natspec 的格式纷跛,看起來像這樣:
/// @title 一個簡單的基礎(chǔ)運(yùn)算合約
/// @author H4XF13LD MORRIS
/// @notice ???ú£??a??o?????ìí?óò???3?·¨