payable
在Solidity中,payable
是一个函数修饰符,它允许函数接收Ether(以太币)。如果一个函数被标记为payable
,那么你可以在调用该函数时附带一定数量的Ether。如果一个函数没有被标记为payable
,那么你不能在调用该函数时发送Ether,否则交易将被拒绝。
以下是一个使用payable
函数修饰符的示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
contract PayableExample {
address payable public owner;
// 合约初始化时可以接收ETH
constructor() payable {
owner = payable(msg.sender);
}
// 查询合约所有者账户下的余额
function getBalance() public view returns (uint256) {
return owner.balance;
}
// 函数用于向此合约存入Ether。
// 调用此函数并附带一些Ether。
// 此合约的余额将自动更新。
function deposit1() public payable {}
// 调用此函数并附带一些Ether。
// 由于此函数不是可支付的,函数将抛出错误。
// transact to PayableExample.deposit2 errored: Error occurred: revert.
// revert
// The transaction has been reverted to the initial state.
// Note: The called function should be payable if you send value and the value you send should be less than your current balance.
// You may want to cautiously increase the gas limit if the transaction went out of gas.
function deposit2() public {}
// 将余额全部发送给合约所有者
function withdraw() public {
// 获取存储在此合约中的Ether数量
uint256 amount = address(this).balance;
// 将所有Ether发送给所有者
(bool success, ) = owner.call{value: amount}("");
require(success, "Failed to send Ether");
}
}
在上面的合约中展示了如何在Solidity中使用payable
关键字来接收和发送Ether。
1.owner
:这是一个公开的可支付地址,它被设置为合约的创建者(也就是部署合约的地址)。
2.constructor
:这是一个构造函数,它在合约部署时运行。这个构造函数是可支付的,这意味着你可以在部署合约时向它发送Ether。构造函数将合约的创建者设置为所有者。
3.getBalance
:这个函数返回合约所有者的余额。
4.deposit1
:这是一个可支付函数,这意味着你可以在调用这个函数时向它发送Ether。发送的Ether将被添加到合约的余额中。
5.deposit2
:这个函数不是可支付的,这意味着你不能在调用这个函数时发送Ether。如果你试图这样做,将会抛出错误。
6.withdraw
:这个函数将合约的全部余额发送给所有者。如果发送失败,它将抛出一个错误。
注意,payable
函数修饰符只影响函数是否可以接收Ether,它不影响函数的其他行为。也就是说,一个payable
函数可以做任何其他函数可以做的事情,包括修改合约的状态。
另外,payable
函数修饰符也可以用于receive
和fallback
函数。receive
函数在合约接收Ether时被调用,fallback
函数在调用了不存在的函数时被调用。这两个函数都必须被标记为payable
,否则合约不能接收Ether。
receive
在Solidity中,receive
函数是一种特殊的函数,用于处理发送到合约的Ether转账。这个函数在合约收到普通Ether转账时被调用,它不能有参数,也不能返回任何值。
receive
函数必须被声明为external payable
,并且一个合约只能有一个receive
函数。如果合约没有定义receive
函数,但是定义了fallback
函数,那么在收到Ether转账时,fallback
函数会被调用。
以下是一个receive
函数的例子:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
contract MyContract {
// Event to emit when Ether is received
event Received(address sender, uint amount);
// Function to receive Ether. msg.data must be empty
receive() external payable {
// Emit the Received event when Ether is received
emit Received(msg.sender, msg.value);
}
}
在这个例子中,当向这个合约发送Ether时,receive
函数会被调用,并且触发一个Received
事件,事件中包含了发送者的地址和发送的Ether的数量。注意,receive
函数被声明为external payable
,并且没有任何参数或返回值。
fallback
fallback
是一种特殊的函数,当以下情况发生时会被执行:
•调用了不存在的函数,或者•直接向合约发送了Ether,但是没有receive()
函数,或者msg.data
不为空
当通过
transfer
或send
调用时,fallback
函数的Gas限制为2300。这个限制是为了防止被调用的合约执行复杂的操作,可能会耗费更多的gas。
此外,fallback
函数可以接受一个bytes calldata
参数,并且可以返回bytes memory
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
// 函数调用关系:TestFallbackInputOutput -> FallbackInputOutput -> Counter
contract FallbackInputOutput {
address immutable target;
constructor(address _target) {
target = _target;
}
fallback(bytes calldata data) external payable returns (bytes memory) {
(bool ok, bytes memory res) = target.call{value: msg.value}(data);
require(ok, "call failed");
return res;
}
}
contract Counter {
uint256 public count;
function get() external view returns (uint256) {
return count;
}
function inc() external returns (uint256) {
count = 1;
return count;
}
}
contract TestFallbackInputOutput {
event Log(bytes res);
function test(address _fallback, bytes calldata data) external {
(bool ok, bytes memory res) = _fallback.call(data);
require(ok, "call failed");
emit Log(res);
}
function getTestData() external pure returns (bytes memory, bytes memory) {
return
(abi.encodeCall(Counter.get, ()), abi.encodeCall(Counter.inc, ()));
}
}
receive or fallback?
既然receive
和fallback
都可以接收ETH,那什么时候调用receive
?什么时候调用fallback
?
在Solidity中,当你发送Ether时,会根据msg.data
是否为空以及receive()
函数是否存在来决定是调用receive()
函数还是fallback()
函数。
以下是详细的判断流程:
1.首先,检查msg.data
是否为空。2.如果msg.data
为空,那么就会检查receive()
函数是否存在。•如果receive()
函数存在,那么就会调用receive()
函数。•如果receive()
函数不存在,那么就会调用fallback()
函数。3.如果msg.data
不为空,那么就会直接调用fallback()
函数。
这种设计是为了在不同的情况下提供更大的灵活性。例如,你可能希望在没有任何数据的情况下(即msg.data
为空)执行一种操作(通过receive()
函数),而在有数据的情况下执行另一种操作(通过fallback()
函数)。
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)[1]进行许可,使用时请注明出处。 Author: mengbin[2] blog: mengbin[3] Github: mengbin92[4] cnblogs: 恋水无意[5] 腾讯云开发者社区:孟斯特[6]
References
[1]
署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0): https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh
[2]
mengbin: mengbin1992@outlook.com
[3]
mengbin: https://mengbin.top
[4]
mengbin92: https://mengbin92.github.io/
[5]
恋水无意: https://www.cnblogs.com/lianshuiwuyi/
[6]
孟斯特: https://cloud.tencent.com/developer/user/6649301