如何在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类型的完整列表。。
函数的可见性有四种:
this
。)view
声明的函数只能读取状态,而不能修改状态。pure
声明的函数既不能读取也不能修改状态。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; // 内部访问
return this.data(); // 外部访问
}
}
合约最多可以具有一个receive
函数。这个函数不能有参数,不能返回任何参数,并且必须具有receive
可见性和payable
状态可变性。
当向合约发送Ether且未指定调用任何函数(calldata 为空)时执行。这是在普通的以太坊转账上执行的函数(例如,通过.send()
或.transfer()
转账)。
该函数声明如下:
receive() external payable {
...
}
合约最多可以具有一个fallback
函数(一般翻译为回退函数)。这个函数不能有参数,不能返回任何参数,并且必须具有external
可见性。如果其他函数均不匹配给定的函数签名,或者根本没有提供任何数据并且没有receive
函数,则在调用合约时执行该函数。
你可以这样声明一个函数:
fallback() external [payable]{
...
}
Solidity文档 — 如果对一个没有实现
receive
函数或payable
回退函数的合约转账,则合约将抛出异常,以太币会退还。
可以在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 {
...}
通过在用空格分隔的列表中指定多个修饰器,可以将它们应用到一个函数,并按给出的顺序对其进行应用。
在本练习中,我们将为多重签名钱包建立一个智能合约: 多签名钱包是其中需要多个密钥才能授权交易的钱包。关于这种类型的钱包及其用例的更多信息,请参见比特币文档.
我们需要的第一件事是批准者列表和批准交易所需的法定人数(所需的最小用户数,例如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 赞助支持。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!