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函数修饰符也可以用于receivefallback函数。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不为空

当通过transfersend调用时,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

既然receivefallback都可以接收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)进行许可,使用时请注明出处。

Author: mengbin

blog: mengbin

Github: mengbin92

cnblogs: 恋水无意

腾讯云开发者社区:孟斯特