上一篇:智能合約編程語言 - solidity快速入門(上)
solidity區(qū)塊及交易屬性
在介紹區(qū)塊及交易屬性之前痘拆,我們需要先知道solidity中自帶了一些全局變量和函數(shù)脏榆,這些變量和函數(shù)可以認(rèn)為是solidity提供的API,這些 API 主要表現(xiàn)為Solidity 內(nèi)置的特殊的變量及函數(shù)肛著,它們存在于全局命名空間里,主要分為以下幾類:
- 有關(guān)區(qū)塊和交易的屬性
- ABI編碼函數(shù)
- 有關(guān)錯(cuò)誤處理
- 有關(guān)數(shù)學(xué)及加密功能
- 有關(guān)地址和合約
我們在編寫智能合約的時(shí)候就可以通過這些API來獲取區(qū)塊和交易的屬性(Block And Transaction Properties),簡單來說這些API主要用來提供一些區(qū)塊鏈當(dāng)前的信息鞭缭,下表列出常用的一些API:
API | 描述 |
---|---|
blockhash(uint blockNumber) returns (bytes32) | 返回給定區(qū)塊號(hào)的哈希值受神,只支持最近256個(gè)區(qū)塊抛猖,且不包含當(dāng)前區(qū)塊 |
block.coinbase (address) | 獲取當(dāng)前塊礦工的地址 |
block.difficulty (uint) | 獲取當(dāng)前塊的難度 |
block.gaslimit (uint) | 獲取當(dāng)前塊的gaslimit |
block.number (uint) | 獲取當(dāng)前區(qū)塊的塊號(hào) |
block.timestamp (uint) | 獲取當(dāng)前塊的Unix時(shí)間戳(從1970/1/1 00:00:00 UTC開始所經(jīng)過的秒數(shù)) |
gasleft() (uint256) | 獲取剩余gas |
msg.data (bytes) | 獲取完整的調(diào)用數(shù)據(jù)(calldata) |
msg.gas (uint) | 獲取當(dāng)前還剩的gas(已棄用) |
msg.sender (address) | 獲取當(dāng)前調(diào)用發(fā)起人的地址 |
msg.sig (bytes4) | 獲取調(diào)用數(shù)據(jù)(calldata)的前四個(gè)字節(jié)(例如為:函數(shù)標(biāo)識(shí)符) |
msg.value (uint) | 獲取這個(gè)消息所附帶的以太幣,單位為wei |
now (uint) | 獲取當(dāng)前塊的時(shí)間戳(實(shí)際上是block.timestamp的別名) |
tx.gasprice (uint) | 獲取交易的gas價(jià)格 |
tx.origin (address) | 獲取交易的發(fā)送者(全調(diào)用鏈) |
注意:
msg的所有成員值,如msg.sender,msg.value的值可以因?yàn)槊恳淮瓮獠亢瘮?shù)調(diào)用财著,或庫函數(shù)調(diào)用發(fā)生變化(因?yàn)閙sg就是和調(diào)用相關(guān)的全局變量)联四。
不應(yīng)該依據(jù) block.timestamp, now 和 block.blockhash來產(chǎn)生一個(gè)隨機(jī)數(shù)(除非你確實(shí)需要這樣做),這幾個(gè)值在一定程度上被礦工影響(比如在賭博合約里,不誠實(shí)的礦工可能會(huì)重試去選擇一個(gè)對自己有利的hash)。
對于同一個(gè)鏈上連續(xù)的區(qū)塊來說嫂伞,當(dāng)前區(qū)塊的時(shí)間戳(timestamp)總是會(huì)大于上一個(gè)區(qū)塊的時(shí)間戳氓仲。為了可擴(kuò)展性的原因,你只能查最近256個(gè)塊琅捏,所有其它的將返回0.
接下來使用代碼演示一下常用的全局變量:
pragma solidity ^0.4.17;
contract SolidityAPI {
function getSender() public constant returns(address) {
// 獲取當(dāng)前調(diào)用發(fā)起人的地址
return msg.sender;
}
function getValue() public constant returns(uint) {
// 獲取這個(gè)消息所附帶的以太幣,單位為wei
return msg.value;
}
function getBlockCoinbase() public constant returns(address) {
// 獲取當(dāng)前塊礦工的地址
return block.coinbase;
}
function getBlockDifficulty() public constant returns(uint) {
// 獲取當(dāng)前塊的難度
return block.difficulty;
}
function getBlockNumber() public constant returns(uint) {
// 獲取當(dāng)前區(qū)塊的塊號(hào)
return block.number;
}
function getBlockTimestamp() public constant returns(uint) {
// 獲取當(dāng)前塊的Unix時(shí)間戳
return block.timestamp;
}
function getNow() public constant returns(uint) {
// 獲取當(dāng)前塊的時(shí)間戳
return now;
}
function getGasprice() public constant returns(uint) {
// 獲取交易的gas價(jià)格
return tx.gasprice;
}
}
ABI編碼函數(shù)
ABI全稱Application Binary Interface,是調(diào)用智能合約函數(shù)以及合約之間函數(shù)調(diào)用的消息編碼格式定義鹿霸,也可以理解為智能合約函數(shù)調(diào)用的接口說明。類似Webservice里的SOAP協(xié)議一樣秆乳;也就是定義操作函數(shù)簽名懦鼠,參數(shù)編碼,返回結(jié)果編碼等屹堰。
solidity 提供了以下函數(shù)肛冶,用來直接得到ABI編碼信息,如下表:
函數(shù) | 描述 |
---|---|
abi.encode(...) returns (bytes) | 計(jì)算參數(shù)的ABI編碼 |
abi.encodePacked(...) returns (bytes) | 計(jì)算參數(shù)的緊密打包編碼 |
abi. encodeWithSelector(bytes4 selector, ...) returns (bytes) | 計(jì)算函數(shù)選擇器和參數(shù)的ABI編碼 |
abi.encodeWithSignature(string signature, ...) returns (bytes) | 等價(jià)于 abi.encodeWithSelector(bytes4(keccak256(signature), ...) |
通過ABI編碼函數(shù)可以在不用調(diào)用函數(shù)的情況下扯键,獲得ABI編碼值睦袖,下面通過一段代碼來看看這些方式的使用:
pragma solidity ^0.4.24;
contract testABI {
function abiEncode() public constant returns (bytes) {
abi.encode(1); // 計(jì)算 1 的ABI編碼
return abi.encodeWithSignature("set(uint256)", 1); //計(jì)算函數(shù)set(uint256) 及參數(shù)1 的ABI 編碼
}
}
solidity錯(cuò)誤處理
在很多編程語言中都具有錯(cuò)誤處理機(jī)制,在solidity中自然也不例外忧陪,solidity最開始的錯(cuò)誤處理方式是使用throw以及if … throw扣泊,后來因?yàn)檫@種方式會(huì)消耗掉所有剩余的gas,所以目前throw的方式已經(jīng)被棄用嘶摊,改為使用以下函數(shù)進(jìn)行錯(cuò)誤處理:
函數(shù) | 描述 |
---|---|
assert(bool condition) | 用于判斷內(nèi)部錯(cuò)誤延蟹,條件不滿足時(shí)拋出異常 |
require(bool condition) | 用于判斷輸入或外部組件錯(cuò)誤,條件不滿足時(shí)拋出異常 |
require(bool condition, string message) | 同上叶堆,多了一個(gè)錯(cuò)誤信息 |
revert() | 終止執(zhí)行并還原改變的狀態(tài) |
revert(string reason) | 同上阱飘,提供一個(gè)錯(cuò)誤信息 |
solidity中的錯(cuò)誤處理機(jī)制和其他大多數(shù)編程語言不一樣,solidity是通過回退狀態(tài)來進(jìn)行錯(cuò)誤處理的虱颗,就像數(shù)據(jù)庫事務(wù)一樣沥匈,也就是說solidity沒有try-catch這種捕獲異常的方式。在發(fā)生異常時(shí)solidity會(huì)撤銷當(dāng)前調(diào)用(及其所有子調(diào)用)所改變的狀態(tài)忘渔,同時(shí)給調(diào)用者返回一個(gè)錯(cuò)誤標(biāo)識(shí)高帖。但是消耗的gas不會(huì)回退,會(huì)正常消耗掉畦粮。
solidity之所以使用這種方式處理錯(cuò)誤散址,是因?yàn)閰^(qū)塊鏈就類似于全球共享的分布式事務(wù)性數(shù)據(jù)庫(公鏈)乖阵。全球共享意味著參與這個(gè)網(wǎng)絡(luò)的每一個(gè)人都可以讀寫其中的數(shù)據(jù),如果沒有這種事務(wù)一般的錯(cuò)誤處理機(jī)制就會(huì)導(dǎo)致一些操作成功一些操作失敗预麸,所帶來的結(jié)果就是數(shù)據(jù)的混亂瞪浸、不一致。所以使用這種事務(wù)一般的錯(cuò)誤處理機(jī)制可以保證一組調(diào)用及其子調(diào)用要么成功要么失敗回滾吏祸,就像啥事都沒有發(fā)生一樣对蒲,solidity錯(cuò)誤處理就是要保證每次調(diào)用都是具有事務(wù)性的。
大概了解了solidity的錯(cuò)誤處理機(jī)制后贡翘,我們來看看如何在solidity中進(jìn)行錯(cuò)誤處理蹈矮。從上表中可以看到solidity提供了兩個(gè)函數(shù)assert和require來進(jìn)行條件檢查,如果條件不滿足則拋出異常鸣驱。assert函數(shù)通常用來檢查(測試)內(nèi)部錯(cuò)誤含滴,而require函數(shù)來檢查輸入變量或合同狀態(tài)變量是否滿足條件以及驗(yàn)證調(diào)用外部合約返回值。
另外丐巫,如果我們正確使用assert,使用一些solidity分析工具就可以幫我們分析出智能合約中的錯(cuò)誤勺美,幫助我們發(fā)現(xiàn)合約中有邏輯錯(cuò)誤的bug递胧。
assert和require兩個(gè)函數(shù)實(shí)際上也就對應(yīng)著兩種類型的異常 ,即assert類型異常及require類型異常赡茸。當(dāng)發(fā)生assert類型異常時(shí)缎脾,會(huì)消耗掉所有提供的gas,而require類型異常則不會(huì)消耗占卧。當(dāng)發(fā)生require類型的異常時(shí)遗菠,Solidity會(huì)執(zhí)行一個(gè)回退操作(指令0xfd)。當(dāng)發(fā)生assert類型的異常時(shí)华蜒,Solidity會(huì)執(zhí)行一個(gè)無效操作(指令0xfe)辙纬。
在上述的兩種情況下,EVM都會(huì)撤回所有的狀態(tài)改變叭喜。是因?yàn)槠谕慕Y(jié)果沒有發(fā)生贺拣,就沒法繼續(xù)安全執(zhí)行。必須保證交易的原子性(一致性捂蕴,要么全部執(zhí)行譬涡,要么一點(diǎn)改變都沒有,不能只改變一部分)啥辨,所以需要撤銷所有操作涡匀,讓整個(gè)交易沒有任何影響。
自動(dòng)產(chǎn)生assert類型異常的場景:
- 如果越界溉知,或負(fù)的序號(hào)值訪問數(shù)組陨瘩,如i >= x.length 或 i < 0時(shí)訪問x[i]
- 如果序號(hào)越界腕够,或負(fù)的序號(hào)值時(shí)訪問一個(gè)定長的bytesN。
- 被除數(shù)為0拾酝, 如5/0 或 23 % 0燕少。
- 對一個(gè)二進(jìn)制移動(dòng)一個(gè)負(fù)的值。如:5<<i; i為-1時(shí)蒿囤。
- 整數(shù)進(jìn)行可以顯式轉(zhuǎn)換為枚舉時(shí)客们,如果將過大值,負(fù)值轉(zhuǎn)為枚舉類型則拋出異常
- 如果調(diào)用未初始化內(nèi)部函數(shù)類型的變量材诽。
- 如果調(diào)用assert的參數(shù)為false
自動(dòng)產(chǎn)生require類型異常的場景:
- 調(diào)用throw
- 如果調(diào)用require的參數(shù)為false
- 如果你通過消息調(diào)用一個(gè)函數(shù)底挫,但在調(diào)用的過程中,并沒有正確結(jié)束(gas不足脸侥,沒有匹配到對應(yīng)的函數(shù)建邓,或被調(diào)用的函數(shù)出現(xiàn)異常)。底層操作如call,send,delegatecall或callcode除外睁枕,它們不會(huì)拋出異常官边,但它們會(huì)通過返回false來表示失敗。
- 如果在使用new創(chuàng)建一個(gè)新合約時(shí)出現(xiàn)第3條的原因沒有正常完成外遇。
- 如果調(diào)用外部函數(shù)調(diào)用時(shí)注簿,被調(diào)用的對象不包含代碼。
- 如果合約沒有payable修飾符的public的函數(shù)在接收以太幣時(shí)(包括構(gòu)造函數(shù)跳仿,和回退函數(shù))诡渴。
- 如果合約通過一個(gè)public的getter函數(shù)(public getter funciton)接收以太幣。
- 如果.transfer()執(zhí)行失敗
除了可以兩個(gè)函數(shù)assert和require來進(jìn)行條件檢查菲语,另外還有兩種方式來觸發(fā)異常:
- revert函數(shù)可以用來標(biāo)記錯(cuò)誤并回退當(dāng)前調(diào)用
- 使用throw關(guān)鍵字拋出異常(從0.4.13版本妄辩,throw關(guān)鍵字已被棄用,將來會(huì)被淘汰山上。)
當(dāng)子調(diào)用中發(fā)生異常時(shí)眼耀,異常會(huì)自動(dòng)向上“冒泡”。 不過也有一些例外:send佩憾,和底層的函數(shù)調(diào)用call, delegatecall畔塔,callcode,當(dāng)發(fā)生異常時(shí)鸯屿,這些函數(shù)返回false澈吨。
注意:在一個(gè)不存在的地址上調(diào)用底層的函數(shù)call,delegatecall寄摆,callcode 也會(huì)返回成功谅辣,所以我們在進(jìn)行調(diào)用時(shí),應(yīng)該總是優(yōu)先進(jìn)行函數(shù)存在性檢查婶恼。
在下面通過一個(gè)示例來說明如何使用require來檢查輸入條件桑阶,代碼中使用了require函數(shù)檢查msg.value的值是否為偶數(shù)柏副,此時(shí)我們設(shè)置value值為2,可以正常的運(yùn)行sendHalf函數(shù):
詳細(xì)的日志如下:
接著我們測試異常的情況蚣录,將value改成1割择,即不能被2整除的數(shù),執(zhí)行sendHalf函數(shù)后萎河,控制臺(tái)輸出的錯(cuò)誤日志如下荔泳,從錯(cuò)誤日志中我們可以看到此次交易被reverted到一個(gè)初始的狀態(tài):
然后我們再來看一個(gè)示例,使用assert函數(shù)檢查內(nèi)部錯(cuò)誤:
pragma solidity ^0.4.20;
contract Sharer {
function sendHalf(address addr) public payable returns(uint balance){
// 僅允許偶數(shù)
require(msg.value % 2 == 0);
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2);
// 檢查當(dāng)前的balance是否為轉(zhuǎn)移之前的一半虐杯,不符合條件則會(huì)拋出異常
assert(this.balance == balanceBeforeTransfer - msg.value / 2);
return this.balance;
}
}
solidity 函數(shù)參數(shù)
本小節(jié)我們來介紹一下solidity中的函數(shù)參數(shù)玛歌,與其他編程語言一樣,solidity 函數(shù)可以提供參數(shù)作為輸入并且函數(shù)類型本身也可以作為參數(shù)擎椰,與JavaScript和C不同的是支子,solidity還可以返回任意數(shù)量的返回值作為輸出。
1.輸入?yún)?shù)达舒,輸入?yún)?shù)的聲明方式與變量相同值朋, 未使用的參數(shù)可以省略變量名稱。假設(shè)我們希望合約中的某個(gè)函數(shù)被外部調(diào)用時(shí)巩搏,傳入兩個(gè)整型參數(shù)吞歼,那么就可以這樣寫:
pragma solidity ^0.4.16;
contract Test {
function inputParam(uint a, uint b) public {
// ...
}
}
2.輸出參數(shù),輸出參數(shù)的聲明和輸入?yún)?shù)一樣塔猾,只不過它接在returns之后,也就是函數(shù)的返回值稽坤,只不過在solidity中函數(shù)的返回值可以像輸入?yún)?shù)一樣被處理丈甸。假設(shè)我們希望返回兩個(gè)結(jié)果,兩個(gè)給定整數(shù)的和以及積尿褪,可以這樣寫:
pragma solidity ^0.4.16;
contract Test {
function testOutput(uint a, uint b) public returns (uint sum, uint mul) {
sum = a + b;
mul = a * b;
}
}
可以省略輸出參數(shù)的名稱睦擂,也可以使用return語句指定輸出值,return可以返回多個(gè)值杖玲。(當(dāng)返回一個(gè)沒有賦值的參數(shù)時(shí)顿仇,默認(rèn)為0)
輸入?yún)?shù)和輸出參數(shù)可以在函數(shù)內(nèi)表達(dá)式中使用,也可以作為被賦值的對象摆马, 如下示例:
contract Test {
function testOutput(uint a, uint b) public returns (uint c) {
a = 1;
b = 2;
c = 3;
}
}
3.命名參數(shù)臼闻,調(diào)用某個(gè)函數(shù)時(shí)傳遞的參數(shù),可以通過指定名稱的方式傳遞囤采,使用花括號(hào){}包起來述呐,參數(shù)順序任意,但參數(shù)的類型和數(shù)量要與定義一致蕉毯,這與Python中的關(guān)鍵字參數(shù)一樣的乓搬。如:
pragma solidity ^0.4.0;
contract Test {
function a(uint key, uint value) public {
// ...
}
function b() public {
// 命名參數(shù)
a({value: 2, key: 3});
}
}
4.參數(shù)解構(gòu)思犁,當(dāng)一個(gè)函數(shù)有多個(gè)輸出參數(shù)時(shí),可以使用元組(tuple)來返回多個(gè)值进肯。元組(tuple)是一個(gè)數(shù)量固定激蹲,類型可以不同的元素組成的一個(gè)列表(用小括號(hào)表示),使用return (v0, v1, …, vn) 語句江掩,就可以返回多個(gè)值学辱,返回值的數(shù)量需要和輸出參數(shù)聲明的數(shù)量一致。當(dāng)函數(shù)返回多個(gè)值時(shí)频敛,可以使用多個(gè)變量去接收项郊,此時(shí)元組內(nèi)的元素就會(huì)同時(shí)賦值給多個(gè)變量,這個(gè)過程就稱之為參數(shù)解構(gòu)斟赚。如下示例:
function a() public pure returns (uint, bool, uint) {
// 使用元組返回多個(gè)值
return (7, true, 2);
}
function b() public {
uint x;
bool y;
uint z;
// 使用元組給多個(gè)變量賦值
(x, y , z) = a();
}
solidity 流程控制語句
solidity 的流程控制語句與其他大多數(shù)語言一致着降,擁有if、else拗军、while任洞、do、for发侵、break交掏、continue、return以及三元表達(dá)式 ? :等流程控制語句刃鳄,這些語句在solidity中的含義與其他語言是一致的這里就不再詳細(xì)贅述了盅弛,不過要注意的是solidity中沒有switch和goto語句。
以下使用一個(gè)簡單的例子演示一下這些流程控制語句的使用方式叔锐,代碼如下:
pragma solidity ^0.4.20;
contract Test {
function testWhile() public constant returns(uint){
uint i = 0;
uint sumOfAdd = 0;
while(true) {
i++;
if (i > 10){
break;
}
if (i % 2 == 0) {
continue;
} else {
sumOfAdd += i;
}
}
sumOfAdd = sumOfAdd > 20 ? sumOfAdd + 10 : sumOfAdd;
return sumOfAdd;
}
function testForLoop() public constant returns(uint) {
uint sum = 0;
for (uint i = 0; i < 10; i++) {
sum +=i;
}
return sum;
}
}
solidity 權(quán)限修飾符
大多數(shù)的語言都會(huì)有權(quán)限修飾符挪鹏,盡管它們都不盡相同,在 solidity 中有public愉烙、private讨盒、external以及internal四種權(quán)限修飾符,接下來我們看看四種權(quán)限修飾符的作用步责。
1.public
public所修飾的函數(shù)稱為公開函數(shù)返顺,是合約接口的一部分,可以通過內(nèi)部蔓肯,或者消息來進(jìn)行調(diào)用遂鹊。對于public類型的狀態(tài)變量,會(huì)自動(dòng)創(chuàng)建一個(gè)訪問器蔗包,這個(gè)訪問器其實(shí)是一個(gè)函數(shù)稿辙。solidity 中的函數(shù)默認(rèn)是public的
我們來看一個(gè)公開函數(shù)的例子,在remix上我們可以看到并執(zhí)行公開的函數(shù):
2.private
表示私有的函數(shù)和狀態(tài)變量气忠,僅在當(dāng)前合約中可以訪問邻储,在繼承的合約內(nèi)不可以訪問赋咽,也不可以被外部訪問
例如我們來寫一個(gè)私有函數(shù),并且進(jìn)行部署吨娜,此時(shí)會(huì)發(fā)現(xiàn)在外部是看不到這個(gè)函數(shù)的:
3.external
表示外部函數(shù)脓匿,與public修飾的函數(shù)有些類似,也是合約接口的一部分宦赠,但只能使用消息調(diào)用陪毡,不可以直接通過內(nèi)部調(diào)用,值得注意的是external函數(shù)消耗的gas比public函數(shù)要少勾扭,所以當(dāng)我們一個(gè)函數(shù)只能被外部調(diào)用時(shí)盡量使用external修飾
同樣的毡琉,我們來看一個(gè)簡單的例子,代碼如下:
4.internal
使用此修飾符修飾的函數(shù)和狀態(tài)變量只能通過內(nèi)部訪問妙色,例如在當(dāng)前合約中調(diào)用桅滋,或繼承的合約中調(diào)用。solidity 中的狀態(tài)變量默認(rèn)是internal的
如下示例:
solidity 函數(shù)調(diào)用
在上一小節(jié)中身辨,我們介紹了 solidity 中的權(quán)限修飾符丐谋,其中涉及到了內(nèi)部函數(shù)調(diào)用和外部函數(shù)調(diào)用的概念,所以這一節(jié)我們進(jìn)一步介紹這兩個(gè)概念煌珊。
1.內(nèi)部函數(shù)調(diào)用(Internal Function Calls)
內(nèi)部調(diào)用号俐,不會(huì)創(chuàng)建一個(gè)EVM消息調(diào)用。而是直接調(diào)用當(dāng)前合約的函數(shù)定庵,也可以遞歸調(diào)用吏饿。
如下面這個(gè)的例子:
pragma solidity ^0.4.20;
contract Test {
function a(uint a) public pure returns (uint ret) {
// 直接調(diào)用
return b();
}
function b() internal pure returns (uint ret) {
// 直接調(diào)用及遞歸調(diào)用
return a(7) + b();
}
}
這些函數(shù)調(diào)用被轉(zhuǎn)換為EVM內(nèi)部的簡單指令跳轉(zhuǎn)(jumps)。 這樣帶來的一個(gè)好處是蔬浙,當(dāng)前的內(nèi)存不會(huì)被回收猪落。在一個(gè)內(nèi)部調(diào)用時(shí)傳遞一個(gè)內(nèi)存型引用效率將非常高的。當(dāng)然敛滋,僅僅是同一個(gè)合約的函數(shù)之間才可通過內(nèi)部的方式進(jìn)行調(diào)用。
2.外部函數(shù)調(diào)用(External Function Calls)
外部調(diào)用兴革,會(huì)創(chuàng)建EVM消息調(diào)用绎晃。表達(dá)式
this.sum(8);
和number.add(2);
(這里的number是一個(gè)合約實(shí)例)是外部調(diào)用函數(shù)的方式,它會(huì)發(fā)起一個(gè)消息調(diào)用杂曲,而不是EVM的指令跳轉(zhuǎn)庶艾。需要注意的是,在合約的構(gòu)造器中擎勘,不能使用this調(diào)用函數(shù)咱揍,因?yàn)楫?dāng)前合約還沒有創(chuàng)建完成
其它合約的函數(shù)必須通過外部的方式調(diào)用。對于一個(gè)外部調(diào)用棚饵,所有函數(shù)的參數(shù)必須要拷貝到內(nèi)存中煤裙。當(dāng)調(diào)用其它合約的函數(shù)時(shí)掩完,可以通過選項(xiàng).value()
,和.gas()
來分別指定要發(fā)送的以太幣(以wei為單位)和gas值硼砰。如下示例:
pragma solidity ^0.4.20;
contract InfoFeed {
// 必須使用`payable`關(guān)鍵字修飾且蓬,否則不能通過`value()`函數(shù)來接收以太幣
function info() public payable returns (uint ret) {
return 42;
}
}
contract Consumer {
InfoFeed feed;
function setFeed(address addr) public {
// 這句代碼進(jìn)行了一個(gè)顯示的類型轉(zhuǎn)換,表示給定的地址是合約`InfoFeed`類型题翰,這里并不會(huì)執(zhí)行構(gòu)造器的初始化恶阴。
// 在進(jìn)行顯式的類型強(qiáng)制轉(zhuǎn)換時(shí)需要非常小心,不要調(diào)用一個(gè)未知類型的合約函數(shù)
feed = InfoFeed(addr);
}
function callFeed() public {
// 附加以太幣及gas來調(diào)用info豹障,注意這里僅僅是對發(fā)送的以太幣和gas值進(jìn)行了設(shè)置冯事,真正的調(diào)用是后面的括號(hào)()
feed.info.value(10).gas(800)();
}
}
注:調(diào)用callFeed
時(shí),需要預(yù)先存入一定量的以太幣血公,不然可能會(huì)因余額不足報(bào)錯(cuò)昵仅。
在與外部合約交互時(shí)需要注意的事項(xiàng):
如果我們不知道被調(diào)用的合約源代碼,那么和這些合約的交互就會(huì)有潛在的風(fēng)險(xiǎn)坞笙,即便被調(diào)用的合約繼承自一個(gè)已知的父合約(因?yàn)槔^承僅僅要求正確實(shí)現(xiàn)接口岩饼,而不關(guān)注實(shí)現(xiàn)的內(nèi)容)。因?yàn)楹瓦@些合約交互時(shí)薛夜,就相當(dāng)于把自己控制權(quán)交給被調(diào)用的合約籍茧,對方幾乎可以利用它做任何事。此外, 被調(diào)用的合約可以改變調(diào)用合約的狀態(tài)變量梯澜,所以在編寫函數(shù)時(shí)需要注意可重入性漏洞問題
solidity 函數(shù)
solidity 有以下四種函數(shù):
- 構(gòu)造函數(shù)
- 視圖函數(shù)(constant / view)
- 純函數(shù)(pure)
- 回退函數(shù)
1.構(gòu)造函數(shù):
構(gòu)造函數(shù)在合約創(chuàng)建的時(shí)候運(yùn)行寞冯,我們通常會(huì)在構(gòu)造函數(shù)做一些初始化的操作,構(gòu)造函數(shù)也是可以有參數(shù)的
如下示例:
2.視圖函數(shù)(constant / view):
使用 constant 或者 view 關(guān)鍵字修飾的函數(shù)就是視圖函數(shù)晚伙,視圖函數(shù)不會(huì)修改合約的狀態(tài)變量吮龄。constant 與 view 是等價(jià)的,constant 是view 的別名咆疗,constant在計(jì)劃Solidity 0.5.0版本之后會(huì)棄用(constant這個(gè)詞有歧義漓帚,view 也更能表達(dá)返回值可視),所以在新版的solidity中推薦優(yōu)先使用view
視圖函數(shù)有個(gè)特點(diǎn)就是在remix執(zhí)行后可以直接看到返回值:
一個(gè)函數(shù)如果它不修改狀態(tài)變量午磁,應(yīng)該聲明為視圖函數(shù)尝抖,以下幾種情況被認(rèn)為修改了狀態(tài)變量:
- 寫狀態(tài)變量
- 觸發(fā)事件(events)
- 創(chuàng)建其他的合約
- call調(diào)用附加了以太幣
- 調(diào)用了任何沒有view或pure修飾的函數(shù)
- 使用了低級(jí)別的調(diào)用(low-level calls)
- 使用了包含特定操作符的內(nèi)聯(lián)匯編
3.純函數(shù)(pure):
純函數(shù)是使用 pure 關(guān)鍵字修飾的函數(shù),純函數(shù)不會(huì)讀取狀態(tài)變量迅皇,也不會(huì)修改狀態(tài)變量
如下示例:
以下幾種情況被認(rèn)為是讀取了狀態(tài):
- 讀狀態(tài)變量
- 訪問了 this.balance
- 訪問了block昧辽、tx、msg 的成員 (msg.sig 和 msg.data除外)
- 調(diào)用了任何沒有pure修飾的函數(shù)
- 使用了包含特定操作符的內(nèi)聯(lián)匯編
4.回退函數(shù):
回退函數(shù)實(shí)際上是一個(gè)匿名函數(shù)登颓,并且是一個(gè)只能被動(dòng)調(diào)用的函數(shù)搅荞,一個(gè)合約中只能有一個(gè)回退函數(shù)。通常當(dāng)我們的一個(gè)智能合約需要接收以太幣的時(shí),就需要實(shí)現(xiàn)回退函數(shù)咕痛,而且回退函數(shù)的實(shí)現(xiàn)應(yīng)該盡量的簡單
如下示例:
如果沒有實(shí)現(xiàn)回退函數(shù)痢甘,其他合約是無法往該合約發(fā)送以太幣的:
回退函數(shù)會(huì)在以下情況被調(diào)用:
- 發(fā)送以太幣
- 被外部調(diào)用了一個(gè)不存在的函數(shù)