區塊鏈研究實驗室 | 手把手教你使用Solidity開發智慧合約(五)

買賣虛擬貨幣

歡迎來到Learn Solidity系列的另一篇文章,在上一篇文章中,我們以變數作了總結,今天,我將向您介紹函式和修飾符,它們將在本文結尾為您提供構建多重簽名錢包的所有步驟。將在“練習”部分中看到。

Solidity中的函式具有以下形式,它們可以在外部(自由功能)或合同內部編寫:

function function_name(<param_type> <param_name>) <visibility> <state mutability> [returns(<return_type>)]{ ... }

返回變數

函式可以返回任意數量的值作為輸出。有兩種從函式返回變數的方法:

1.使用返回變數的名稱:

function arithmetic(uint _a, uint _b) public pure returns (uint o_sum, uint o_product) { o_sum = _a + _b; o_product = _a * _b; }

2.直接在return語句中提供返回值:

function arithmetic(uint _a, uint _b) public pure returns (uint o_sum, uint o_product) { return (_a + _b, _a * _b); }

使用第二種方法,您可以省略返回變數的名稱,而僅指定其型別。

支援的引數和返回型別

為了呼叫智慧合約功能,我們需要使用ABI(應用程式二進位制介面)規範來指定要呼叫的功能並對引數進行編碼,這些引數將包含在交易的資料欄位中併傳送給要執行的以太坊網路(ABI編碼也用於事件和返回型別)。

ABI編碼器的第一個版本不支援我們在前幾篇文章中看到的所有型別,例如,我們無法從函式返回結構,如果嘗試這樣做,則會出現錯誤,這就是為什麼我們需要使用ABI編碼器的版本2,以便透過在檔案中包含以下行來使錯誤消失:pragma abicoder v2;如果您使用的是Solidity版本:0.7.5。對於低於0.7.5的版本,我們需要使用實驗版本:pragma experimental ABIEncoderV2;

這是來自Solidity文件版本0.7.5的示例:

// SPDX-License-Identifier: GPL-3.0pragma solidity >0.7.4;pragma abicoder v2;
contract Test { struct S { uint a; uint[] b; T[] c; } struct T { uint x; uint y; } function f(S memory, T memory, uint) public pure {} function g() public pure returns (S memory, T memory, uint) {}}

在文件的此部分中可以找到受支援的ABI型別的完整列表。

能見度

功能的可見性有四種型別:

  • 私有:限制性最強的函式只能在定義智慧合約的地方呼叫。

  • 內部:可以在定義智慧合約的位置呼叫該函式,也可以從該函式繼承的所有智慧合約中呼叫該函式。

  • 外部:只能從智慧合約外部呼叫。(如果要從智慧合約中呼叫它,則必須使用它。)

  • 公開:可以在任何地方撥打。(最寬容的一個)

狀態變異

  • view:宣告的函式view只能讀取狀態,而不能修改狀態。

  • pure:用宣告的函式pure既不能讀取也不能修改狀態。

  • payable:用宣告的函式payable可以接受傳送給合約的以太幣,如果未指定,該函式將自動拒絕所有傳送給合約的以太幣。

contract SimpleStorage { uint256 private data; function getData() external view returns(uint256) { return data; } function setData(uint256 _data) external { data = _data; }}

交易與通話

用view和pure關鍵字定義的功能不會改變以太坊區塊鏈的狀態,這意味著當您呼叫這些功能時,您不會向區塊鏈傳送任何交易,因為交易被定義為將區塊鏈從一種狀態轉移到另一種狀態的狀態轉換功能。相反,發生的事情是,您要連線的節點透過檢查其自己的區塊鏈版本在本地執行功能程式碼,並在不向以太坊網路廣播任何交易的情況下將結果返回。

在本節中,我們將看到一些可以使用的特殊功能。

訪問功能

定義為public的狀態變數具有getter函式,該函式由編譯器自動建立。該函式與變數具有相同的名稱,並具有外部可見性。

contract C { uint public data; function x() public returns (uint) { data = 3; // internal access return this.data(); // external access }}

接收以太功能

合同最多隻能具有一項receive功能。該函式不能有引數,不能返回任何東西,並且必須具有external可見性和payable狀態可變性。

它在傳送Ether且未指定任何功能(空呼叫資料)的合同的呼叫上執行。這是在普通以太坊傳輸(例如,透過.send()或.transfer())上執行的功能。
該函式宣告如下:

receive() external payable { ...}

Fallback功能

合同最多隻能具有一項fallback功能。此函式不能有引數,不能返回任何東西,並且必須具有external可見性。如果沒有其他函式與給定的函式簽名匹配,或者根本沒有提供任何資料並且沒有接收Ether函式,則在呼叫合同時執行該命令。
您可以這樣宣告一個函式,如下所示:

fallback() external [payable]{ ...}

“在沒有函式呼叫的情況下直接接收以太幣的合同,send或者transfer沒有定義receive函式或應付款回退功能的合同,將丟擲一個例外,將以太幣送回。” —實體文件

在Remix上自行嘗試,建立不帶receive或的合約,payable fallback然後向其中傳送一些以太幣。單擊Transact之後,您應該會看到一條類似以下的訊息。

訊息示例


功能修飾符

當您要在執行函式之前檢查某些條件時,需要使用修飾符。例如,如果您要檢查發件人是否是合同的所有者,則可以編寫以下內容:

function selectWinner() external { require(msg.sender == owner, "this function is restricted to the owner); ...}

使用修飾符,我們可以隔離此程式碼,以便我們可以將其與其他功能複用,我們只需要宣告修飾符,如下所示:

modifier onlyOwner(){ require(msg.sender == owner, "this function is restricted to the owner); _; // will be replaced by the code of the function}

然後將修飾符名稱新增到函式中:

function selectWinner() external onlyOwner {  ...}

透過在以空格分隔的列表中指定多個修飾符,可以將它們應用到一個函式,並按給出的順序對其進行評估。

練習:Multisig錢包

在本練習中,我們將為多簽名錢包構建一個智慧合約,簽名錢包是需要多個金鑰才能授權交易的錢包。

我們需要的第一件事是批准者列表和授權交易所需的法定人數(所需的最小使用者數,如果我們有3個multisig錢包中的2個,這意味著法定人數為2個)。
您還需要建立一個結構來記錄與轉賬相關的資訊,包括要支付的金額,收件人,已經批准轉賬的批准人數量及其狀態(如果已傳送或仍在等待確認)批准者)。

過程如下:一名批准者將建立轉移,該轉移將儲存在智慧合約的儲存中,等待其他批准者確認,一旦達到所需的確認數量,則將以太轉移到接收者。

練習答案

// SPDX-License-Identifier: MITpragma solidity >=0.4.22 <0.8.0;pragma experimental ABIEncoderV2;

contract Wallet { address[] public approvers; uint8 public quorum;
struct Transfer { uint id; uint amount; address payable to; uint approvers; bool sent; }
Transfer[] public transfers; mapping(address => mapping(uint => bool )) public approvals;
constructor(address[] memory _approvers, uint8 _quorum){ approvers = _approvers; quorum = _quorum; }
function getApprovers() external view returns(address[] memory){ return approvers; }
function createTransfer(uint amount, address payable to) external onlyApprover { transfers.push(Transfer( transfers.length, amount, to, 0, false )); }
function getTransfers() external view returns(Transfer[] memory) { return transfers; }
function approveTransfer(uint id) external onlyApprover { require(transfers[id].sent == false, "transfer has already been sent"); require(approvals[msg.sender][id] == false, "cannot approve transfer twice");
approvals[msg.sender][id] = true; transfers[id].approvers++; if(transfers[id].approvers >= quorum ) { transfers[id].sent = true; address payable to = transfers[id].to; uint amount = transfers[id].amount; to.transfer(amount); } }
receive() external payable {}
modifier onlyApprover() { bool isApprover = false; for(uint8 i=0; i< approvers.length ; i++){ if(approvers[i] == msg.sender){ isApprover = true; break; } } require(isApprover, "access restricted only to an approver"); _; }
}

作者:鏈三豐,來源:區塊鏈研究實驗室

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读