特殊函数
receive()
一个合约只能有一个receive函数,该函数不能有参数和返回值,需设置为external,payable;
当本合约收到ether且未指定调用任何函数(calldata 为空)时执行,receive函数被触发;传进来的ether全部转给合约。
fallback()
一个合约只能有一个receive函数,该函数不能有参数和返回值,需设置为external;
可设置为payable;
当本合约的其他函数不匹配调用,或调用者未提供任何信息,且没有receive函数,fallback函数被触发;
0.5.x之前使用 function() external payable { }
来实现。后面发现有问题才拆分上面两个。比如0.5.x的一个实现。
pragma solidity ^0.5.0;
contract Charity {
mapping(address => uint256) public donations;
function processDonation(address user) external payable {
donations[user] += msg.value;
}
}
contract Receiver {
event ValueReceived(address user, uint256 amount);
function() external payable {
emit ValueReceived(msg.sender, msg.value);
}
}
contract CharitySplitter {
function donate(Charity charity) external payable {
charity.processDonation.value(msg.value)(msg.sender);
}
}
const goodCharity = await Charity.new();
const receiver = await Receiver.new();
const badCharity = await Charity.at(receiver.address);
const charitySplitter = await CharitySplitter.new();
// 成功捐款10wei
await charitySplitter.donate(goodCharity, { value: 10 });
// 这里虽然传入了一个错误的地址,但是触发了Receiver的fallback函数,没有看到任何异常
await charitySplitter.donate(badCharity, { value: 10 });
为了修复这个问题。可以使用下面的实现。
pragma solidity ^0.6.0;
contract Charity {
mapping (address => uint256) public donations;
function processDonation(address user) external payable {
donations[user] += msg.value;
}
}
contract Receiver {
event ValueReceived(address user, uint amount);
receive() external payable {
emit ValueReceived(msg.sender, msg.value);
}
}
contract CharitySplitter {
function donate(Charity charity) external payable {
charity.processDonation{value:msg.value}(msg.sender);
}
}
可见性和 getter 函数
由于 Solidity 有两种函数调用(内部调用不会产生实际的 EVM 调用或称为“消息调用”,而外部调用则会产生一个 EVM 调用), 函数和状态变量有四种可见性类型。 函数可以指定为 external ,public ,internal 或者 private。 对于状态变量,不能设置为 external ,默认是 internal 。
external
外部函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。 一个外部函数 f 不能从内部调用(即 f 不起作用,但 this.f() 可以)。 当收到大量数据的时候,外部函数有时候会更有效率,因为数据不会从calldata复制到内存.
public
public 函数是合约接口的一部分,可以在内部或通过消息调用。对于 public 状态变量, 会自动生成一个 getter 函数(见下面)。
internal
这些函数和状态变量只能是内部访问(即从当前合约内部或从它派生的合约访问),不使用 this 调用。
private
private 函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。
Constant 和 Immutable 状态变量
状态变量声明为 constant (常量)或者 immutable (不可变量),在这两种情况下,合约一旦部署之后,变量将不在修改。
对于 constant 常量, 他的值在编译器确定,而对于 immutable, 它的值在部署时确定。
也可以在文件级别定义 constant 变量(注:0.7.2 之后加入的特性)。
编译器不会为这些变量预留存储位,它们的每次出现都会被替换为相应的常量表达式(它可能被优化器计算为实际的某个值)。
与常规状态变量相比,常量和不可变量的gas成本要低得多。 对于常量,赋值给它的表达式将复制到所有访问该常量的位置,并且每次都会对其进行重新求值。 这样可以进行本地优化。
不可变变量在构造时进行一次求值,并将其值复制到代码中访问它们的所有位置。 对于这些值,将保留32个字节,即使它们适合较少的字节也是如此。 因此,常量有时可能比不可变量更便宜。
状态可变性(mutability)
View 视图函数
可以将函数声明为 view 类型,这种情况下要保证不修改状态。
下面的语句被认为是修改状态:
- 修改状态变量。
- 产生事件。
- 创建其它合约。
- 使用 selfdestruct。
- 通过调用发送以太币。
- 调用任何没有标记为 view 或者 pure 的函数。
- 使用低级调用。
- 使用包含特定操作码的内联汇编。
Pure 纯函数
函数可以声明为 pure ,在这种情况下,承诺不读取也不修改状态。
除了上面解释的状态修改语句列表之外,以下被认为是读取状态:
- 读取状态变量。
- 访问 address(this).balance 或者 <address>.balance。
- 访问 block,tx, msg 中任意成员 (除 msg.sig 和 msg.data 之外)。
- 调用任何未标记为 pure 的函数。
- 使用包含某些操作码的内联汇编。
payable
用payable声明的函数可以接受发送给合约的以太币,如果未指定,该函数将自动拒绝所有发送给它的以太币。