跟我学 Solidity :函数

如何在Solidity中使用函数

> * 来源:https://medium.com/better-programming/learn-solidity-functions-ddd8ea24c00d 作者:[wissal haji](https://wissal-haji.medium.com) > * 译文出自:[登链翻译计划](https://github.com/lbc-team/Pioneer) > * 译者:[翻译小组](https://learnblockchain.cn/people/412) > * 校对:[Tiny 熊](https://learnblockchain.cn/people/15) > * 本文永久链接:[learnblockchain.cn/article…](https://learnblockchain.cn/article/1817) 上一篇文章[介绍了变量的使用](https://learnblockchain.cn/article/1778),今天,我将介绍函数和修饰符,在本文结尾还提供一个练习:构建多重签名钱包,在练习中可以重温学习的内容。 Solidity中的函数为: ```javascript 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(应用程序二进制接口)规范](https://learnblockchain.cn/docs/solidity/abi-spec.html)来指定要调用的函数并对参数进行编码,这些参数将包含在交易的数据字段中并发送给以太坊网络来执行。 ABI编码也用于事件和返回类型,更多详细信息可以在[文档](https://learnblockchain.cn/docs/solidity/abi-spec.html)中找到。 ABI编码器的第一个版本并非支持前几篇文章中所介绍的所有类型,例如,我们无法从函数返回结构体,如果尝试这样做,则会出现错误,这就是为什么我们需要使用ABI编码器的v2版本`pragma abicoder v2;` 来避免错误提示 (如果你使用的是Solidity版本:0.7.5才可以使用 v2 版本。对于低于0.7.5的版本,我们需要使用实验版本:`pragma experimental ABIEncoderV2;`)。 使用 Solidity 0.7.5 版本的示例。 ```js // SPDX-License-Identifier: GPL-3.0 pragma 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) {} } ``` 可在[文档的此部分](https://docs.soliditylang.org/en/v0.7.5/abi-spec.html#types)中找到受支持的ABI类型的完整列表。。 ## 可见性(Visibility) 函数的可见性有四种: - **Private(私有)**:限制性最强,函数只能在所定义的智能合约内部调用。 - **Internal(内部)**:可以在所定义智能合约内部调用该函数,也可以从继承合约中调用该函数。 - **External(外部)**:只能从智能合约外部调用。 (如果要从智能合约中调用它,则必须使用 `this`。) - **Public(公开)**:可以从任何地方调用。 (最宽松) ## 状态可变性(mutability) - **view**:用`view`声明的函数只能读取状态,而不能修改状态。 - **pure**:用`pure`声明的函数既不能读取也不能修改状态。 - **payable**:用`payable`声明的函数可以接受发送给合约的以太币,如果未指定,该函数将自动拒绝所有发送给它的以太币。 ```js contract SimpleStorage { uint256 private data; function getData() external view returns(uint256) { return data; } function setData(uint256 _data) external { data = _data; } } ``` 你可以在[文档](https://learnblockchain.cn/docs/solidity/contracts.html#view)了解读取状态及写状态的含义。 ### 交易与调用 用`view`和`pure`关键字定义的函数不会改变以太坊区块链的状态,这意味着当你调用这些函数时,你不会向区块链发送任何交易,因为交易被定义为从一个状态到另一个状态的状态栏变换。其仅仅是,你连接的节点通过检查其自己的区块链版本在本地执行函数代码,并将结果返回,而无需将任何交易广播到以太坊网络。 ## 特殊函数 在本节中,我们将看到一些可以使用的特殊函数。 ## Getter 函数 定义为public的状态变量具有getter函数,该函数由编译器自动创建。该函数与变量具有相同的名称,并且具有外部可见性。 ```javascript contract C { uint public data; function x() public returns (uint) { data = 3; // 内部访问 return this.data(); // 外部访问 } } ``` ## 接收以太币函数 合约最多可以具有一个`receive`函数。这个函数不能有参数,不能返回任何参数,并且必须具有`receive`可见性和` payable`状态可变性。 当向合约发送Ether且未指定调用任何函数(calldata 为空)时执行。这是在普通的以太坊转账上执行的函数(例如,通过`.send()`或`.transfer()`转账)。 该函数声明如下: ```javascript receive() external payable { ... } ``` ## Fallback函数 合约最多可以具有一个`fallback`函数(一般翻译为回退函数)。这个函数不能有参数,不能返回任何参数,并且必须具有`external`可见性。如果**其他函数均不匹配给定的函数签名**,或者**根本没有提供任何数据并且没有`receive`函数**,则在调用合约时执行该函数。 你可以这样声明一个函数: ```js fallback() external [payable]{ ... } ``` > [Solidity文档](https://docs.soliditylang.org/en/v0.7.5/abi-spec.html#contract-abi-specification) — 如果对一个没有实现`receive`函数或`payable`回退函数的合约转账,则合约将抛出异常,以太币会退还。 可以在Remix中自行尝试一下,创建一个没有`receive`或`payable fallback`的合约,并向其中发送一些以太币。点击**Transact(交易)**后,你应该会看到一条类似以下的消息。 ![1_JD90_-QFq3Wmmq7nEj7GNQ](https://img.learnblockchain.cn/pics/20201202152026.png) ## 函数修饰器 当你要在执行函数之前检查某些条件时,可以使用[修饰器](https://learnblockchain.cn/docs/solidity/contracts.html#modifier)。例如,如果你要检查发件人是否是合约的所有者,则可以编写以下内容: ```js function selectWinner() external { require(msg.sender == owner, "this function is restricted to the owner); ...} ``` 使用修饰器,我们可以分离该代码,以便我们可以将其与其他函数复用,我们只需要声明修饰器,如下所示: ```js 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 { ...} ``` 通过在用空格分隔的列表中指定多个修饰器,可以将它们应用到一个函数,并按给出的顺序对其进行应用。 ## 练习:多签钱包 在本练习中,我们将为多重签名钱包建立一个智能合约: 多签名钱包是其中需要多个密钥才能授权交易的钱包。关于这种类型的钱包及其用例的更多信息,请参见[比特币文档](https://en.bitcoin.it/wiki/Multisignature). 我们需要的第一件事是批准者列表和批准交易所需的法定人数(所需的最小用户数,例如3方多签钱包中至少2个签名有效,这意味着法定人数为2个)。 你还需要创建一个结构体来记录与转账相关的信息,包括要支付的金额,接收账号,已经批准转账的批准人数以及交易状态(时候已发送或仍在等待批准者确认)。 整个过程如下:一名批准者将创建转账,该转账将保存在智能合约的存储中,等待其他批准者确认,一旦达到所需的确认数量,则将以太转移到接收者。 解决方案可以在[这里](https://gist.github.com/wissalHaji/3bd5e5c0573618f9e284971abe2af1aa)找到在Github上。代码如下: ```js // SPDX-License-Identifier: MIT pragma 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"); _; } } ``` 这就是Solidity中的函数的相关内容,我希望本文对你有用。 在Solidity中,我们仍有很多内容:智能合约,继承,事件和异常处理,部署到公共测试网中等等,将在之后的文章介绍。 ------ 本翻译由 [Cell Network](https://www.cellnetwork.io/?utm_souce=learnblockchain) 赞助支持。

  • 来源:https://medium.com/better-programming/learn-solidity-functions-ddd8ea24c00d 作者:wissal haji
  • 译文出自:登链翻译计划
  • 译者:翻译小组
  • 校对:Tiny 熊
  • 本文永久链接:learnblockchain.cn/article…

上一篇文章介绍了变量的使用,今天,我将介绍函数和修饰符,在本文结尾还提供一个练习:构建多重签名钱包,在练习中可以重温学习的内容。

Solidity中的函数为:

function function_name(&lt;param_type> &lt;param_name>) &lt;visibility> &lt;state mutability> [returns(&lt;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编码器的v2版本pragma abicoder v2; 来避免错误提示 (如果你使用的是Solidity版本:0.7.5才可以使用 v2 版本。对于低于0.7.5的版本,我们需要使用实验版本:pragma experimental ABIEncoderV2;)。

使用 Solidity 0.7.5 版本的示例。

// SPDX-License-Identifier: GPL-3.0
pragma 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类型的完整列表。。

可见性(Visibility)

函数的可见性有四种:

  • Private(私有):限制性最强,函数只能在所定义的智能合约内部调用。
  • Internal(内部):可以在所定义智能合约内部调用该函数,也可以从继承合约中调用该函数。
  • External(外部):只能从智能合约外部调用。 (如果要从智能合约中调用它,则必须使用 this。)
  • Public(公开):可以从任何地方调用。 (最宽松)

状态可变性(mutability)

  • 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;
    }
}

你可以在文档了解读取状态及写状态的含义。

交易与调用

viewpure关键字定义的函数不会改变以太坊区块链的状态,这意味着当你调用这些函数时,你不会向区块链发送任何交易,因为交易被定义为从一个状态到另一个状态的状态栏变换。其仅仅是,你连接的节点通过检查其自己的区块链版本在本地执行函数代码,并将结果返回,而无需将任何交易广播到以太坊网络。

特殊函数

在本节中,我们将看到一些可以使用的特殊函数。

Getter 函数

定义为public的状态变量具有getter函数,该函数由编译器自动创建。该函数与变量具有相同的名称,并且具有外部可见性。

contract C {
    uint public data;
    function x() public returns (uint) {
        data = 3; //  内部访问
        return this.data(); // 外部访问
    }
}

接收以太币函数

合约最多可以具有一个receive函数。这个函数不能有参数,不能返回任何参数,并且必须具有receive可见性和payable状态可变性。

当向合约发送Ether且未指定调用任何函数(calldata 为空)时执行。这是在普通的以太坊转账上执行的函数(例如,通过.send().transfer()转账)。

该函数声明如下:

receive() external payable {
   ...
}

Fallback函数

合约最多可以具有一个fallback函数(一般翻译为回退函数)。这个函数不能有参数,不能返回任何参数,并且必须具有external可见性。如果其他函数均不匹配给定的函数签名,或者根本没有提供任何数据并且没有receive函数,则在调用合约时执行该函数。

你可以这样声明一个函数:

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

Solidity文档 — 如果对一个没有实现receive函数或payable回退函数的合约转账,则合约将抛出异常,以太币会退还。

可以在Remix中自行尝试一下,创建一个没有receivepayable fallback的合约,并向其中发送一些以太币。点击Transact(交易)后,你应该会看到一条类似以下的消息。

跟我学 Solidity :函数插图

函数修饰器

当你要在执行函数之前检查某些条件时,可以使用修饰器。例如,如果你要检查发件人是否是合约的所有者,则可以编写以下内容:

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 {

    ...}

通过在用空格分隔的列表中指定多个修饰器,可以将它们应用到一个函数,并按给出的顺序对其进行应用。

练习:多签钱包

在本练习中,我们将为多重签名钱包建立一个智能合约: 多签名钱包是其中需要多个密钥才能授权交易的钱包。关于这种类型的钱包及其用例的更多信息,请参见比特币文档.

我们需要的第一件事是批准者列表和批准交易所需的法定人数(所需的最小用户数,例如3方多签钱包中至少2个签名有效,这意味着法定人数为2个)。

你还需要创建一个结构体来记录与转账相关的信息,包括要支付的金额,接收账号,已经批准转账的批准人数以及交易状态(时候已发送或仍在等待批准者确认)。

整个过程如下:一名批准者将创建转账,该转账将保存在智能合约的存储中,等待其他批准者确认,一旦达到所需的确认数量,则将以太转移到接收者。

解决方案可以在这里找到在Github上。代码如下:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 &lt;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&lt; approvers.length ; i++){
      if(approvers[i] == msg.sender){
        isApprover = true;
        break;
      }
    }
    require(isApprover, "access restricted only to an approver");
    _;
  }

}

这就是Solidity中的函数的相关内容,我希望本文对你有用。

在Solidity中,我们仍有很多内容:智能合约,继承,事件和异常处理,部署到公共测试网中等等,将在之后的文章介绍。

本翻译由 Cell Network 赞助支持。

区块链技术网。

  • 发表于 2020-12-02 15:50
  • 阅读 ( 1849 )
  • 学分 ( 115 )
  • 分类:Solidity
  • 专栏:全面掌握Solidity智能合约开发

评论