手把手教你使用Solidity开发智能合约(五)
欢迎来到Learn Solidity系列的另一篇文章,在上一篇文章中,我们以变量作了总结,今天,我将向您介绍函数和修饰符,它们将在本文结尾为您提供构建多重签名钱包的所有步骤。将在“练习”部分中看到。
Solidity中的函数具有以下形式,它们可以在外部(自由功能)或合同内部编写:
function function_name()[returns()]{ ... }
返回变量
函数可以返回任意数量的值作为输出。有两种从函数返回变量的方法:
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"); _; }}
Scan QR code with WeChat