7

跟我学 Solidity :函数

 3 years ago
source link: https://learnblockchain.cn/article/1817
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如何在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编码器的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(交易)**后,你应该会看到一条类似以下的消息。

yqYFbyA.png!mobile

函数修饰器

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

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 <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 赞助支持。

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

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编码器的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(交易) 后,你应该会看到一条类似以下的消息。

yqYFbyA.png!mobile

函数修饰器

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

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 <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 赞助支持。

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK