智能合約編程語言 - solidity快速入門(下)

上一篇:智能合約編程語言 - solidity快速入門(上)


solidity區(qū)塊及交易屬性

在介紹區(qū)塊及交易屬性之前痘拆,我們需要先知道solidity中自帶了一些全局變量和函數(shù)脏榆,這些變量和函數(shù)可以認(rèn)為是solidity提供的API,這些 API 主要表現(xiàn)為Solidity 內(nèi)置的特殊的變量及函數(shù)肛著,它們存在于全局命名空間里,主要分為以下幾類:

  1. 有關(guān)區(qū)塊和交易的屬性
  2. ABI編碼函數(shù)
  3. 有關(guān)錯(cuò)誤處理
  4. 有關(guān)數(shù)學(xué)及加密功能
  5. 有關(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類型異常的場景:

  1. 如果越界溉知,或負(fù)的序號(hào)值訪問數(shù)組陨瘩,如i >= x.length 或 i < 0時(shí)訪問x[i]
  2. 如果序號(hào)越界腕够,或負(fù)的序號(hào)值時(shí)訪問一個(gè)定長的bytesN。
  3. 被除數(shù)為0拾酝, 如5/0 或 23 % 0燕少。
  4. 對一個(gè)二進(jìn)制移動(dòng)一個(gè)負(fù)的值。如:5<<i; i為-1時(shí)蒿囤。
  5. 整數(shù)進(jìn)行可以顯式轉(zhuǎn)換為枚舉時(shí)客们,如果將過大值,負(fù)值轉(zhuǎn)為枚舉類型則拋出異常
  6. 如果調(diào)用未初始化內(nèi)部函數(shù)類型的變量材诽。
  7. 如果調(diào)用assert的參數(shù)為false

自動(dòng)產(chǎn)生require類型異常的場景:

  1. 調(diào)用throw
  2. 如果調(diào)用require的參數(shù)為false
  3. 如果你通過消息調(diào)用一個(gè)函數(shù)底挫,但在調(diào)用的過程中,并沒有正確結(jié)束(gas不足脸侥,沒有匹配到對應(yīng)的函數(shù)建邓,或被調(diào)用的函數(shù)出現(xiàn)異常)。底層操作如call,send,delegatecall或callcode除外睁枕,它們不會(huì)拋出異常官边,但它們會(huì)通過返回false來表示失敗。
  4. 如果在使用new創(chuàng)建一個(gè)新合約時(shí)出現(xiàn)第3條的原因沒有正常完成外遇。
  5. 如果調(diào)用外部函數(shù)調(diào)用時(shí)注簿,被調(diào)用的對象不包含代碼。
  6. 如果合約沒有payable修飾符的public的函數(shù)在接收以太幣時(shí)(包括構(gòu)造函數(shù)跳仿,和回退函數(shù))诡渴。
  7. 如果合約通過一個(gè)public的getter函數(shù)(public getter funciton)接收以太幣。
  8. 如果.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ù):


image.png

詳細(xì)的日志如下:


image.png

接著我們測試異常的情況蚣录,將value改成1割择,即不能被2整除的數(shù),執(zhí)行sendHalf函數(shù)后萎河,控制臺(tái)輸出的錯(cuò)誤日志如下荔泳,從錯(cuò)誤日志中我們可以看到此次交易被reverted到一個(gè)初始的狀態(tài):


image.png

然后我們再來看一個(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ù):


image.png

2.private

表示私有的函數(shù)和狀態(tài)變量气忠,僅在當(dāng)前合約中可以訪問邻储,在繼承的合約內(nèi)不可以訪問赋咽,也不可以被外部訪問

例如我們來寫一個(gè)私有函數(shù),并且進(jìn)行部署吨娜,此時(shí)會(huì)發(fā)現(xiàn)在外部是看不到這個(gè)函數(shù)的:


image.png

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è)簡單的例子,代碼如下:


image.png

4.internal

使用此修飾符修飾的函數(shù)和狀態(tài)變量只能通過內(nèi)部訪問妙色,例如在當(dāng)前合約中調(diào)用桅滋,或繼承的合約中調(diào)用。solidity 中的狀態(tài)變量默認(rèn)是internal的

如下示例:


image.png

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ù)的

如下示例:


image.png

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í)行后可以直接看到返回值:


image.png

一個(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)變量

如下示例:


image.png

以下幾種情況被認(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)該盡量的簡單

如下示例:


image.png

如果沒有實(shí)現(xiàn)回退函數(shù)痢甘,其他合約是無法往該合約發(fā)送以太幣的:


image.png

回退函數(shù)會(huì)在以下情況被調(diào)用:

  • 發(fā)送以太幣
  • 被外部調(diào)用了一個(gè)不存在的函數(shù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市暇检,隨后出現(xiàn)的幾起案子产阱,更是在濱河造成了極大的恐慌,老刑警劉巖块仆,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件构蹬,死亡現(xiàn)場離奇詭異,居然都是意外死亡悔据,警方通過查閱死者的電腦和手機(jī)庄敛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來科汗,“玉大人藻烤,你說我怎么就攤上這事⊥诽希” “怎么了怖亭?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長坤检。 經(jīng)常有香客問我兴猩,道長,這世上最難降的妖魔是什么早歇? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任倾芝,我火速辦了婚禮,結(jié)果婚禮上箭跳,老公的妹妹穿的比我還像新娘晨另。我一直安慰自己,他們只是感情好谱姓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布借尿。 她就那樣靜靜地躺著,像睡著了一般屉来。 火紅的嫁衣襯著肌膚如雪路翻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天奶躯,我揣著相機(jī)與錄音帚桩,去河邊找鬼亿驾。 笑死嘹黔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播儡蔓,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼郭蕉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了喂江?” 一聲冷哼從身側(cè)響起召锈,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎获询,沒想到半個(gè)月后涨岁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吉嚣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年梢薪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尝哆。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秉撇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秋泄,到底是詐尸還是另有隱情琐馆,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布恒序,位于F島的核電站瘦麸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏奸焙。R本人自食惡果不足惜瞎暑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望与帆。 院中可真熱鬧了赌,春花似錦、人聲如沸玄糟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阵翎。三九已至逢并,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間郭卫,已是汗流浹背砍聊。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贰军,地道東北人玻蝌。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俯树。 傳聞我的和親對象是個(gè)殘疾皇子帘腹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容