溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時(shí)間:2020-06-25 06:20:52 來(lái)源:網(wǎng)絡(luò) 閱讀:3047 作者:ZeroOne01 欄目:開發(fā)技術(shù)

上一篇:智能合約編程語(yǔ)言-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)地址和合約

我們?cè)诰帉懼悄芎霞s的時(shí)候就可以通過這些API來(lái)獲取區(qū)塊和交易的屬性(Block And Transaction Properties),簡(jiǎn)單來(lái)說(shuō)這些API主要用來(lái)提供一些區(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)用,或庫(kù)函數(shù)調(diào)用發(fā)生變化(因?yàn)閙sg就是和調(diào)用相關(guān)的全局變量)。

不應(yīng)該依據(jù) block.timestamp, now 和 block.blockhash來(lái)產(chǎn)生一個(gè)隨機(jī)數(shù)(除非你確實(shí)需要這樣做),這幾個(gè)值在一定程度上被礦工影響(比如在×××合約里,不誠(chéng)實(shí)的礦工可能會(huì)重試去選擇一個(gè)對(duì)自己有利的hash)。

對(duì)于同一個(gè)鏈上連續(xù)的區(qū)塊來(lái)說(shuō),當(dāng)前區(qū)塊的時(shí)間戳(timestamp)總是會(huì)大于上一個(gè)區(qū)塊的時(shí)間戳。為了可擴(kuò)展性的原因,你只能查最近256個(gè)塊,所有其它的將返回0.

接下來(lái)使用代碼演示一下常用的全局變量:

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,翻譯過來(lái)就是:應(yīng)用程序二進(jìn)制接口,是調(diào)用智能合約函數(shù)以及合約之間函數(shù)調(diào)用的消息編碼格式定義,也可以理解為智能合約函數(shù)調(diào)用的接口說(shuō)明。類似Webservice里的SOAP協(xié)議一樣;也就是定義操作函數(shù)簽名,參數(shù)編碼,返回結(jié)果編碼等。

簡(jiǎn)單來(lái)說(shuō)從外部施加給以太坊的行為都稱之為向以太坊網(wǎng)絡(luò)提交了一個(gè)交易, 調(diào)用合約函數(shù)其實(shí)是向合約地址(賬戶)提交了一個(gè)交易,這個(gè)交易有一個(gè)附加數(shù)據(jù),這個(gè)附加的數(shù)據(jù)就是ABI的編碼數(shù)據(jù)。因此要想和合約交互,就離不開ABI數(shù)據(jù)。

solidity 提供了以下函數(shù),用來(lái)直接得到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編碼值,下面通過一段代碼來(lái)看看這些方式的使用:

pragma solidity ^0.4.24;

contract testABI {
    uint storedData;

    function set(uint x) public {
        storedData = x;
    }

    function abiEncode() public constant returns (bytes) {
            // 計(jì)算 1 的ABI編碼
        abi.encode(1);  

                //計(jì)算函數(shù)set(uint256) 及參數(shù)1 的ABI 編碼
        return abi.encodeWithSignature("set(uint256)", 1); 
    }
}

solidity錯(cuò)誤處理

在很多編程語(yǔ)言中都具有錯(cuò)誤處理機(jī)制,在solidity中自然也不例外,solidity最開始的錯(cuò)誤處理方式是使用throw以及if … throw,后來(lái)因?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ù)編程語(yǔ)言不一樣,solidity是通過回退狀態(tài)來(lái)進(jìn)行錯(cuò)誤處理的,就像數(shù)據(jù)庫(kù)事務(wù)一樣,也就是說(shuō)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ù)庫(kù)(公鏈)。全球共享意味著參與這個(gè)網(wǎng)絡(luò)的每一個(gè)人都可以讀寫其中的數(shù)據(jù),如果沒有這種事務(wù)一般的錯(cuò)誤處理機(jī)制就會(huì)導(dǎo)致一些操作成功一些操作失敗,所帶來(lái)的結(jié)果就是數(shù)據(jù)的混亂、不一致。所以使用這種事務(wù)一般的錯(cuò)誤處理機(jī)制可以保證一組調(diào)用及其子調(diào)用要么成功要么失敗回滾,就像啥事都沒有發(fā)生一樣,solidity錯(cuò)誤處理就是要保證每次調(diào)用都是具有事務(wù)性的。


大概了解了solidity的錯(cuò)誤處理機(jī)制后,我們來(lái)看看如何在solidity中進(jìn)行錯(cuò)誤處理。從上表中可以看到solidity提供了兩個(gè)函數(shù)assert和require來(lái)進(jìn)行條件檢查,如果條件不滿足則拋出異常。assert函數(shù)通常用來(lái)檢查(測(cè)試)內(nèi)部錯(cuò)誤,而require函數(shù)來(lái)檢查輸入變量或合同狀態(tài)變量是否滿足條件以及驗(yàn)證調(diào)用外部合約返回值。

另外,如果我們正確使用assert,使用一些solidity分析工具就可以幫我們分析出智能合約中的錯(cuò)誤,幫助我們發(fā)現(xiàn)合約中有邏輯錯(cuò)誤的bug。

assert和require兩個(gè)函數(shù)實(shí)際上也就對(duì)應(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è)無(wú)效操作(指令0xfe)。

在上述的兩種情況下,EVM都會(huì)撤回所有的狀態(tài)改變。是因?yàn)槠谕慕Y(jié)果沒有發(fā)生,就沒法繼續(xù)安全執(zhí)行。必須保證交易的原子性(一致性,要么全部執(zhí)行,要么一點(diǎn)改變都沒有,不能只改變一部分),所以需要撤銷所有操作,讓整個(gè)交易沒有任何影響。

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

  1. 如果越界,或負(fù)的序號(hào)值訪問數(shù)組,如i >= x.length 或 i < 0時(shí)訪問x[i]
  2. 如果序號(hào)越界,或負(fù)的序號(hào)值時(shí)訪問一個(gè)定長(zhǎng)的bytesN。
  3. 被除數(shù)為0, 如5/0 或 23 % 0。
  4. 對(duì)一個(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類型異常的場(chǎng)景:

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

除了可以兩個(gè)函數(shù)assert和require來(lái)進(jìn)行條件檢查,另外還有兩種方式來(lái)觸發(fā)異常:

  • revert函數(shù)可以用來(lái)標(biāo)記錯(cuò)誤并回退當(dāng)前調(diào)用
  • 使用throw關(guān)鍵字拋出異常(從0.4.13版本,throw關(guān)鍵字已被棄用,將來(lái)會(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ì)返回成功,所以我們?cè)谶M(jìn)行調(diào)用時(shí),應(yīng)該總是優(yōu)先進(jìn)行函數(shù)存在性檢查。

在下面通過一個(gè)示例來(lái)說(shuō)明如何使用require來(lái)檢查輸入條件,代碼中使用了require函數(shù)檢查msg.value的值是否為偶數(shù),此時(shí)我們?cè)O(shè)置value值為2,可以正常的運(yùn)行sendHalf函數(shù):
智能合約編程語(yǔ)言-solidity快速入門(下)

詳細(xì)的日志如下:
智能合約編程語(yǔ)言-solidity快速入門(下)

接著我們測(cè)試異常的情況,將value改成1,即不能被2整除的數(shù),執(zhí)行sendHalf函數(shù)后,控制臺(tái)輸出的錯(cuò)誤日志如下,從錯(cuò)誤日志中我們可以看到此次交易被reverted到一個(gè)初始的狀態(tài):
智能合約編程語(yǔ)言-solidity快速入門(下)

然后我們?cè)賮?lá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é)我們來(lái)介紹一下solidity中的函數(shù)參數(shù),與其他編程語(yǔ)言一樣,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語(yǔ)句指定輸出值,return可以返回多個(gè)值。(當(dāng)返回一個(gè)沒有賦值的參數(shù)時(shí),默認(rèn)為0)

輸入?yún)?shù)和輸出參數(shù)可以在函數(shù)內(nèi)表達(dá)式中使用,也可以作為被賦值的對(duì)象, 如下示例:

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){}包起來(lái),參數(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)來(lái)返回多個(gè)值。元組(tuple)是一個(gè)數(shù)量固定,類型可以不同的元素組成的一個(gè)列表(用小括號(hào)表示),使用return (v0, v1, …, vn) 語(yǔ)句,就可以返回多個(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 流程控制語(yǔ)句

solidity 的流程控制語(yǔ)句與其他大多數(shù)語(yǔ)言一致,擁有if、else、while、do、for、break、continue、return以及三元表達(dá)式 ? :等流程控制語(yǔ)句,這些語(yǔ)句在solidity中的含義與其他語(yǔ)言是一致的這里就不再詳細(xì)贅述了,不過要注意的是solidity中沒有switch和goto語(yǔ)句。

以下使用一個(gè)簡(jiǎn)單的例子演示一下這些流程控制語(yǔ)句的使用方式,代碼如下:

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ù)的語(yǔ)言都會(huì)有權(quán)限修飾符,盡管它們都不盡相同,在 solidity 中有public、private、external以及internal四種權(quán)限修飾符,接下來(lái)我們看看四種權(quán)限修飾符的作用。

1.public

public所修飾的函數(shù)稱為公開函數(shù),是合約接口的一部分,可以通過內(nèi)部,或者消息來(lái)進(jìn)行調(diào)用。對(duì)于public類型的狀態(tài)變量,會(huì)自動(dòng)創(chuàng)建一個(gè)訪問器,這個(gè)訪問器其實(shí)是一個(gè)函數(shù)。solidity 中的函數(shù)默認(rèn)是public的

我們來(lái)看一個(gè)公開函數(shù)的例子,在remix上我們可以看到并執(zhí)行公開的函數(shù):
智能合約編程語(yǔ)言-solidity快速入門(下)


2.private

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

例如我們來(lái)寫一個(gè)私有函數(shù),并且進(jìn)行部署,此時(shí)會(huì)發(fā)現(xiàn)在外部是看不到這個(gè)函數(shù)的:
智能合約編程語(yǔ)言-solidity快速入門(下)


3.external

表示外部函數(shù),與public修飾的函數(shù)有些類似,也是合約接口的一部分,但只能使用消息調(diào)用,不可以直接通過內(nèi)部調(diào)用,值得注意的是external函數(shù)消耗的gas比public函數(shù)要少,所以當(dāng)我們一個(gè)函數(shù)只能被外部調(diào)用時(shí)盡量使用external修飾

同樣的,我們來(lái)看一個(gè)簡(jiǎn)單的例子,代碼如下:
智能合約編程語(yǔ)言-solidity快速入門(下)


4.internal

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

如下示例:
智能合約編程語(yǔ)言-solidity快速入門(下)


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)部的簡(jiǎn)單指令跳轉(zhuǎn)(jumps)。 這樣帶來(lái)的一個(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)用。對(duì)于一個(gè)外部調(diào)用,所有函數(shù)的參數(shù)必須要拷貝到內(nèi)存中。當(dāng)調(diào)用其它合約的函數(shù)時(shí),可以通過選項(xiàng).value(),和.gas()來(lái)分別指定要發(fā)送的以太幣(以wei為單位)和gas值。如下示例:

pragma solidity ^0.4.20;

contract InfoFeed {
    // 必須使用`payable`關(guān)鍵字修飾,否則不能通過`value()`函數(shù)來(lái)接收以太幣
    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來(lái)調(diào)用info,注意這里僅僅是對(duì)發(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)用的合約,對(duì)方幾乎可以利用它做任何事。此外, 被調(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ù)的

如下示例:
智能合約編程語(yǔ)言-solidity快速入門(下)


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í)行后可以直接看到返回值:
智能合約編程語(yǔ)言-solidity快速入門(下)

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

如下示例:
智能合約編程語(yǔ)言-solidity快速入門(下)

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

如下示例:
智能合約編程語(yǔ)言-solidity快速入門(下)

如果沒有實(shí)現(xiàn)回退函數(shù),其他合約是無(wú)法往該合約發(fā)送以太幣的:
智能合約編程語(yǔ)言-solidity快速入門(下)

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

  • 發(fā)送以太幣
  • 被外部調(diào)用了一個(gè)不存在的函數(shù)
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI