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。
- owner:这是一个公开的可支付地址,它被设置为合约的创建者(也就是部署合约的地址)。
- constructor:这是一个构造函数,它在合约部署时运行。这个构造函数是可支付的,这意味着你可以在部署合约时向它发送Ether。构造函数将合约的创建者设置为所有者。
- getBalance:这个函数返回合约所有者的余额。
- deposit1:这是一个可支付函数,这意味着你可以在调用这个函数时向它发送Ether。发送的Ether将被添加到合约的余额中。
- deposit2:这个函数不是可支付的,这意味着你不能在调用这个函数时发送Ether。如果你试图这样做,将会抛出错误。
- 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()函数。
以下是详细的判断流程:
- 首先,检查msg.data是否为空。
- 如果msg.data为空,那么就会检查receive()函数是否存在。- 如果receive()函数存在,那么就会调用receive()函数。
- 如果receive()函数不存在,那么就会调用fallback()函数。
 
- 如果
- 如果msg.data不为空,那么就会直接调用fallback()函数。
这种设计是为了在不同的情况下提供更大的灵活性。例如,你可能希望在没有任何数据的情况下(即msg.data为空)执行一种操作(通过receive()函数),而在有数据的情况下执行另一种操作(通过fallback()函数)。
 
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意
腾讯云开发者社区:孟斯特