函數(shù)

2022-05-16 10:46 更新

可以在合約內(nèi)部和外部定義函數(shù)。

合約之外的函數(shù),也稱為“自由函數(shù)”,總是具有隱式internal 可見性。它們的代碼包含在調(diào)用它們的所有合約中,類似于內(nèi)部庫函數(shù)。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

function sum(uint[] memory arr) pure returns (uint s) {
    for (uint i = 0; i < arr.length; i++)
        s += arr[i];
}

contract ArrayExample {
    bool found;
    function f(uint[] memory arr) public {
        // This calls the free function internally.
        // The compiler will add its code to the contract.
        uint s = sum(arr);
        require(s >= 10);
        found = true;
    }
}

筆記

在合約之外定義的功能仍然總是在合約的上下文中執(zhí)行。他們?nèi)匀豢梢栽L問變量this,可以調(diào)用其他合約,向它們發(fā)送以太幣并銷毀調(diào)用它們的合約等等。與合約中定義的函數(shù)的主要區(qū)別在于,自由函數(shù)不能直接訪問存儲變量和不在其范圍內(nèi)的函數(shù)。

函數(shù)參數(shù)和返回變量

函數(shù)將類型化參數(shù)作為輸入,并且與許多其他語言不同,它還可以返回任意數(shù)量的值作為輸出。

功能參數(shù)

函數(shù)參數(shù)的聲明方式與變量相同,未使用的參數(shù)名稱可以省略。

例如,如果您希望您的合約接受一種帶有兩個整數(shù)的外部調(diào)用,您可以使用如下內(nèi)容:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract Simple {
    uint sum;
    function taker(uint a, uint b) public {
        sum = a + b;
    }
}

函數(shù)參數(shù)可以用作任何其他局部變量,也可以分配給它們。

筆記

外部函數(shù)不能接受多維數(shù)組作為輸入?yún)?shù)。如果您通過添加到源文件來啟用 ABI coder v2,則此功能是可能的。pragma abicoder v2;

內(nèi)部函數(shù)可以在不啟用該功能的情況下接受多維數(shù)組。

返回變量

函數(shù)返回變量在 returns關(guān)鍵字之后使用相同的語法聲明。

例如,假設(shè)您要返回兩個結(jié)果:作為函數(shù)參數(shù)傳遞的兩個整數(shù)的總和和乘積,那么您可以使用以下內(nèi)容:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract Simple {
    function arithmetic(uint a, uint b)
        public
        pure
        returns (uint sum, uint product)
    {
        sum = a + b;
        product = a * b;
    }
}

返回變量的名稱可以省略。返回變量可以用作任何其他局部變量,并使用其默認值初始化并具有該值,直到它們被(重新)分配。

您可以顯式分配給返回變量,然后像上面一樣保留函數(shù),或者您可以直接使用 語句提供返回值(單個或多個):return

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract Simple {
    function arithmetic(uint a, uint b)
        public
        pure
        returns (uint sum, uint product)
    {
        return (a + b, a * b);
    }
}

如果使用 earlyreturn離開具有返回變量的函數(shù),則必須在 return 語句中提供返回值。

筆記

您不能從非內(nèi)部函數(shù)返回某些類型,尤其是多維動態(tài)數(shù)組和結(jié)構(gòu)。如果您通過添加 到源文件來啟用 ABI coder v2,則可以使用更多類型,但 類型仍僅限于單個合約內(nèi),您無法轉(zhuǎn)移它們。pragma abicoder v2;mapping

返回多個值

當(dāng)一個函數(shù)有多種返回類型時,該語句可用于返回多個值。組件的數(shù)量必須與返回變量的數(shù)量相同,并且它們的類型必須匹配,可能在隱式轉(zhuǎn)換之后。return (v0, v1, ..., vn)

狀態(tài)可變性

查看函數(shù)

可以聲明函數(shù)view,在這種情況下它們承諾不修改狀態(tài)。

筆記

如果編譯器的 EVM 目標是 Byzantium 或更新的(默認), 則在調(diào)用函數(shù)STATICCALL時使用操作碼view,這會強制狀態(tài)保持不變,作為 EVM 執(zhí)行的一部分。使用庫view函數(shù) DELEGATECALL,因為沒有組合DELEGATECALLand STATICCALL。這意味著庫view函數(shù)沒有阻止狀態(tài)修改的運行時檢查。這不應(yīng)該對安全性產(chǎn)生負面影響,因為庫代碼通常在編譯時是已知的,并且靜態(tài)檢查器執(zhí)行編譯時檢查。

以下語句被視為修改狀態(tài):

  1. 寫入狀態(tài)變量。

  2. 發(fā)射事件。

  3. 創(chuàng)建其他合同。

  4. 使用selfdestruct.

  5. 通過調(diào)用發(fā)送以太幣。

  6. 調(diào)用任何未標記view或的函數(shù)pure。

  7. 使用低級調(diào)用。

  8. 使用包含某些操作碼的內(nèi)聯(lián)匯編。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;

contract C {
    function f(uint a, uint b) public view returns (uint) {
        return a * (b + 42) + block.timestamp;
    }
}

筆記

constanton 函數(shù)曾經(jīng)是 的別名view,但在 0.5.0 版中已刪除。

筆記

Getter 方法是自動標記的view

筆記

在 0.5.0 版本之前,編譯器不使用函數(shù)的STATICCALL操作碼view。view這通過使用無效的顯式類型轉(zhuǎn)換啟用了函數(shù)中的狀態(tài)修改。通過使用 STATICCALLforview函數(shù),可以防止在 EVM 級別上修改狀態(tài)。

純函數(shù)

可以聲明函數(shù)pure,在這種情況下它們承諾不讀取或修改狀態(tài)。特別是,應(yīng)該可以pure在編譯時評估一個函數(shù),只給它的輸入 和msg.data,但不知道當(dāng)前的區(qū)塊鏈狀態(tài)。這意味著從immutable變量中讀取可能是非純操作。

筆記

如果編譯器的 EVM 目標是 Byzantium 或更新的(默認),STATICCALL則使用操作碼,這不保證不讀取狀態(tài),但至少不修改狀態(tài)。

除了上面解釋的狀態(tài)修改語句列表之外,以下內(nèi)容被認為是從狀態(tài)中讀取的:

  1. 從狀態(tài)變量中讀取。

  2. 訪問address(this).balance<address>.balance。

  3. block訪問, tx,的任何成員msg(和 除外msg.sigmsg.data。

  4. 調(diào)用任何未標記的函數(shù)pure。

  5. 使用包含某些操作碼的內(nèi)聯(lián)匯編。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;

contract C {
    function f(uint a, uint b) public pure returns (uint) {
        return a * (b + 42);
    }
}

純函數(shù)能夠使用revert()和函數(shù)在發(fā)生錯誤時require()恢復(fù)潛在的狀態(tài)變化。

恢復(fù)狀態(tài)更改不被視為“狀態(tài)修改”,因為只有先前在沒有vieworpure限制的代碼中所做的狀態(tài)更改才會被恢復(fù),并且該代碼可以選擇捕獲revert而不傳遞它。

這種行為也符合STATICCALL操作碼。

警告

不可能在 EVM 級別阻止函數(shù)讀取狀態(tài),只能阻止它們寫入狀態(tài)(即只能view在 EVM 級別強制執(zhí)行,pure不能)。

筆記

在 0.5.0 版本之前,編譯器不使用函數(shù)的STATICCALL操作碼pure。pure這通過使用無效的顯式類型轉(zhuǎn)換啟用了函數(shù)中的狀態(tài)修改。通過使用 STATICCALLforpure函數(shù),可以防止在 EVM 級別上修改狀態(tài)。

筆記

在 0.4.17 版本之前,編譯器沒有強制執(zhí)行pure不讀取狀態(tài)。它是一種編譯時類型檢查,可以規(guī)避在合約類型之間進行無效的顯式轉(zhuǎn)換,因為編譯器可以驗證合約的類型不做狀態(tài)改變操作,但不能檢查將要被在運行時調(diào)用實際上就是那種類型。

特殊功能

接收以太功能

一個合約最多可以有一個receive函數(shù),使用聲明 (不帶關(guān)鍵字)。此函數(shù)不能有參數(shù),不能返回任何內(nèi)容,并且必須具有 可見性和狀態(tài)可變性。它可以是虛擬的,可以覆蓋并且可以具有修飾符。receive() external payable { ... }functionexternalpayable

接收函數(shù)在調(diào)用帶有空 calldata 的合約時執(zhí)行。.send()這是在普通 Ether 傳輸(例如 via或.transfer())上執(zhí)行的函數(shù)。如果不存在這樣的功能,但存在應(yīng)付回退功能 ,則回退功能將在普通的以太幣轉(zhuǎn)賬中被調(diào)用。如果既不存在接收 Ether 也不存在應(yīng)付回退功能,則合約無法通過常規(guī)交易接收 Ether 并引發(fā)異常。

在最壞的情況下,該receive功能只能依賴 2300 gas 可用(例如何時使用sendtransfer使用),幾乎沒有空間執(zhí)行除基本日志記錄之外的其他操作。以下操作將消耗比 2300 氣體津貼更多的氣體:

  • 寫入存儲

  • 創(chuàng)建合同

  • 調(diào)用消耗大量gas的外部函數(shù)

  • 發(fā)送以太幣

警告

當(dāng) Ether 直接發(fā)送到合約(沒有函數(shù)調(diào)用,即發(fā)送方使用sendor transfer)但接收合約沒有定義接收 Ether 函數(shù)或應(yīng)付回退函數(shù)時,將拋出異常,發(fā)送回 Ether(這是不同的在 Solidity v0.4.0 之前)。如果你希望你的合約接收 Ether,你必須實現(xiàn)一個接收 Ether 函數(shù)(不推薦使用支付回退函數(shù)來接收 Ether,因為回退被調(diào)用并且不會因發(fā)送方的接口混淆而失?。?。

警告

沒有接收 Ether 功能的合約可以接收 Ether 作為coinbase 交易的接收者(又名礦工塊獎勵)或作為selfdestruct.

合約無法對此類以太幣轉(zhuǎn)賬做出反應(yīng),因此也無法拒絕它們。這是 EVM 的設(shè)計選擇,Solidity 無法解決它。

這也意味著它address(this).balance可能高于在合約中實施的一些人工會計的總和(即在接收以太函數(shù)中更新了一個計數(shù)器)。

下面你可以看到一個使用 function 的 Sink 合約示例receive。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
    event Received(address, uint);
    receive() external payable {
        emit Received(msg.sender, msg.value);
    }
}

后備功能

一個合約最多可以有一個fallback函數(shù),使用 or聲明 (都沒有關(guān)鍵字)。此功能必須具有可見性?;赝撕瘮?shù)可以是虛擬的,可以覆蓋并且可以具有修飾符。fallback () external [payable]fallback (bytes calldata input) external [payable] returns (bytes memory output)functionexternal

如果沒有其他函數(shù)與給定的函數(shù)簽名匹配,或者根本沒有提供數(shù)據(jù)并且沒有接收 Ether 函數(shù),則在調(diào)用合約時執(zhí)行回退函數(shù)。fallback 函數(shù)總是接收數(shù)據(jù),但為了也接收 Ether,它必須被標記payable。

如果使用帶參數(shù)的版本,input將包含發(fā)送到合約的完整數(shù)據(jù)(等于msg.data),并且可以在 中返回數(shù)據(jù)output。返回的數(shù)據(jù)不會經(jīng)過 ABI 編碼。相反,它將在沒有修改的情況下返回(甚至沒有填充)。

在最壞的情況下,如果還使用支付回退函數(shù)代替接收函數(shù),它只能依賴 2300 gas 可用( 有關(guān)此含義的簡要描述,請參閱接收以太函數(shù))。

與任何函數(shù)一樣,只要有足夠的 gas 傳遞給它,fallback 函數(shù)就可以執(zhí)行復(fù)雜的操作。

警告

如果不存在接收 Ether 功能payable,則還為普通 Ether 傳輸執(zhí)行回退功能 。如果您定義了一個應(yīng)付回退函數(shù)來區(qū)分 Ether 傳輸和接口混淆,那么建議始終定義一個接收 Ether 函數(shù)。

筆記

如果要解碼輸入數(shù)據(jù),可以檢查函數(shù)選擇器的前四個字節(jié),然后可以abi.decode與數(shù)組切片語法一起使用來解碼 ABI 編碼的數(shù)據(jù): 請注意,這只能作為最后的手段使用,并且應(yīng)該使用適當(dāng)?shù)墓δ堋?code>(c, d) = abi.decode(input[4:], (uint256, uint256));

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.9.0;

contract Test {
    uint x;
    // This function is called for all messages sent to
    // this contract (there is no other function).
    // Sending Ether to this contract will cause an exception,
    // because the fallback function does not have the `payable`
    // modifier.
    fallback() external { x = 1; }
}

contract TestPayable {
    uint x;
    uint y;
    // This function is called for all messages sent to
    // this contract, except plain Ether transfers
    // (there is no other function except the receive function).
    // Any call with non-empty calldata to this contract will execute
    // the fallback function (even if Ether is sent along with the call).
    fallback() external payable { x = 1; y = msg.value; }

    // This function is called for plain Ether transfers, i.e.
    // for every call with empty calldata.
    receive() external payable { x = 2; y = msg.value; }
}

contract Caller {
    function callTest(Test test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // results in test.x becoming == 1.

        // address(test) will not allow to call ``send`` directly, since ``test`` has no payable
        // fallback function.
        // It has to be converted to the ``address payable`` type to even allow calling ``send`` on it.
        address payable testPayable = payable(address(test));

        // If someone sends Ether to that contract,
        // the transfer will fail, i.e. this returns false here.
        return testPayable.send(2 ether);
    }

    function callTestPayable(TestPayable test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // results in test.x becoming == 1 and test.y becoming 0.
        (success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // results in test.x becoming == 1 and test.y becoming 1.

        // If someone sends Ether to that contract, the receive function in TestPayable will be called.
        // Since that function writes to storage, it takes more gas than is available with a
        // simple ``send`` or ``transfer``. Because of that, we have to use a low-level call.
        (success,) = address(test).call{value: 2 ether}("");
        require(success);
        // results in test.x becoming == 2 and test.y becoming 2 ether.

        return true;
    }
}

函數(shù)重載

一個合約可以有多個同名但參數(shù)類型不同的函數(shù)。這個過程稱為“重載”,也適用于繼承的函數(shù)。下面的例子展示 f了合約范圍內(nèi)函數(shù)的重載A。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract A {
    function f(uint value) public pure returns (uint out) {
        out = value;
    }

    function f(uint value, bool really) public pure returns (uint out) {
        if (really)
            out = value;
    }
}

外部接口中也存在重載函數(shù)。如果兩個外部可見函數(shù)的不同在于它們的 Solidity 類型而不是它們的外部類型,這是一個錯誤。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

// This will not compile
contract A {
    function f(B value) public pure returns (B out) {
        out = value;
    }

    function f(address value) public pure returns (address out) {
        out = value;
    }
}

contract B {
}

上面的兩個f函數(shù)重載最終都接受了 ABI 的地址類型,盡管它們在 Solidity 中被認為是不同的。

重載解析和參數(shù)匹配

通過將當(dāng)前作用域中的函數(shù)聲明與函數(shù)調(diào)用中提供的參數(shù)匹配來選擇重載函數(shù)。如果所有參數(shù)都可以隱式轉(zhuǎn)換為預(yù)期類型,則選擇函數(shù)作為重載候選者。如果不完全是一個候選人,則決議失敗。

筆記

重載解析不考慮返回參數(shù)。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract A {
    function f(uint8 val) public pure returns (uint8 out) {
        out = val;
    }

    function f(uint256 val) public pure returns (uint256 out) {
        out = val;
    }
}

調(diào)用f(50)會產(chǎn)生類型錯誤,因為50可以隱式轉(zhuǎn)換為uint8 和uint256類型。另一方面f(256)將解決f(uint256)重載,因為256不能隱式轉(zhuǎn)換為uint8.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號