关于接收eth和发送eth的基本函数

接收eth回调函数receive和fallback函数都是合约中用来接收以太币(ETH)并且在没有匹配到其他函数时执行的特殊函数receive()只用于处理接收eth。当合约接收到一个纯ETH转账(没有任何数据或调用信息)时,且合约中没有调用其他函数,receive函数会被调用一个

接收eth

回调函数

receivefallback函数都是合约中用来接收以太币(ETH)并且在没有匹配到其他函数时执行的特殊函数

receive()

  1. 只用于处理接收eth。当合约接收到一个纯ETH转账(没有任何数据或调用信息)时,且合约中没有调用其他函数,receive函数会被调用

  2. 一个合约最多一个receive()函数

  3. 不需要函数声明,声明必须包含external,payable

  4. receive不要执行太多的逻辑,防止超出gas的限制

    比如别人用send 和 transfer,来发送eth,gas不能超过2300

    否则会报错

  5. 没有名字、没有参数、不能返回任何值的特殊函数。

    // 定义事件
    event Received(address Sender, uint Value);
    // 接收ETH时释放Received事件
    receive() external payable {
        emit Received(msg.sender, msg.value);
    }

fallback()

  1. 没有名字的函数,但可以接收数据并返回数据
  2. 使用external和payable
  3. 释放事件时,输出msg.sendermsg.valuemsg.data**

只是发送ETH而不包含任何数据,且合约中定义了receive函数,那么将会调用receive函数。如果发送ETH时调用了一个不存在的函数,或者发送ETH同时包含了数据,那么将会调用fallback函数。如果合约中既没有receive也没有fallback函数,合约将拒绝纯ETH转账。

    // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyContract {
    // 事件声明,用于记录fallback函数被调用的信息
    event FallbackCalled(address sender, uint value, bytes data);

    // fallback函数定义
    fallback() external payable {
        // 触发事件,记录函数调用的详细信息
        emit FallbackCalled(msg.sender, msg.value, msg.data);
    }

}

两者显著的区别

接收eth如果msg.data为空,则调用receive,如果此时无receive,则调用fallback。

有数据则直接调用fallback。

其他注意

msg.value:发送的以太币数量。

msg.sender:调用者的地址。

msg.data:调用数据

对msg.data详解

msg.data包含调用数据(call data),即调用合约时传递的完整数据(包括函数签名和参数)。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MsgDataExample {
    event LogData(bytes data);

    // 一个简单的函数,带有一个参数
    function exampleFunction(uint256 x) public {
        // 触发事件,记录msg.data
        emit LogData(msg.data);
    }
}

调用过程

  1. 函数签名exampleFunction(uint256) 的函数签名是 exampleFunction(uint256)

  2. 函数选择器: 这个函数签名的前4个字节是通过对签名进行 keccak256 哈希并取前4个字节得到的:

    bytes4 selector = bytes4(keccak256("exampleFunction(uint256)"));

    对于 exampleFunction(uint256),假设计算得出的选择器是 0x7c600ae6

  3. 调用数据: 当调用 exampleFunction 时,假设传递的参数为 12345,完整的调用数据为:

    0x7c600ae6 0000000000000000000000000000000000000000000000000000000000003039

    其中:

    • 0x7c600ae6 是函数选择器。
    • 0000000000000000000000000000000000000000000000000000000000003039 是参数 12345 的编码(以32字节对齐)。

事件日志

调用 exampleFunction(12345) 时,事件 LogData 会记录 msg.data 的值:

emit LogData(msg.data);

此时,记录的 msg.data 将是:

0x7c600ae60000000000000000000000000000000000000000000000000000000000003039

这个数据包括:

  • 函数选择器 0x7c600ae6
  • 参数 12345 的编码。

发送eth

transfer

function transfereth(address payable _to,uint256 _value) external payable{
    _to.transfer(_value);
}

特点:

  1. gas限制在2300
  2. 转账失败,会自动revert(回滚交易)

send

error failed_send();
function senteth(address payable _to,uint256 _value) external payable{
    bool success=_to.send(_value);
    if(!success){
        revert failed_send();
    }
}

特点:

  1. gas限制2300
  2. 转账失败不会revert,只会返回bool值,回滚需要自己设置

call

error call_failed();

function calleth(address payable _to,uint256 _value) external payable{
    (bool success,)=_to.calleth{value:_value}("");
if(!success){
    revert call_failed();
}
}

用法:发送地址 . call{value:发送eth的数量}

特点:

  1. 没有gas限制,可以支持对方合约fallback()receive()函数实现复杂逻辑
  2. 转账失败,不会revert
  3. 返回值是(bool,data)
    1. 布尔值 (bool):这个返回值表示调用是否成功。如果调用成功执行,它会返回 true;如果调用失败,比如因为调用的函数不存在或者调用用尽了gas,它会返回 false
    2. 字节数据 (bytes memory data):这个返回值包含了被调用函数的返回数据。如果调用的是一个合约函数,这个数据就是函数执行后返回的任何输出值。例如,如果你使用 call 来调用另一个合约中的一个函数,该函数返回一个 uint256 和一个 addressdata 将包含这两个值的紧密打包编码

payable修饰符

在Solidity中,payable 是一个修饰符,它可以用于函数和地址。它的用途取决于它修饰的是函数还是地址:

  1. 修饰函数:当 payable 修饰一个函数时,它指示这个函数可以接收以太币(ETH)。如果调用一个非 payable 函数并发送ETH,交易会被拒绝。

  2. 修饰地址:当 payable 修饰一个地址时,它表示这个地址可以接收和发送ETH。在Solidity中,地址分为两种类型:addressaddress payable。只有 address payable 类型的地址才能接收ETH,并且可以使用 transfersend 方法发送ETH。

例如,想让 exampleFunction 接收ETH,需要这样写:

function exampleFunction(uint256 x) public payable {
    // 函数现在可以接收ETH
    emit LogData(msg.data);
}

同样,想让合约的某个地址类型变量能够接收ETH,需要声明它为 address payable

address payable public recipient;

这样,recipient 就可以接收ETH,并且合约内的函数可以使用 recipient.transfer(amount)recipient.send(amount) 来发送ETH。如果没有将地址声明为 payable 类型,那么将无法向该地址发送ETH。

gas限制2300是啥意思?

在以太坊中,每个交易都需要消耗一定量的 gas,以支付网络进行计算和存储的成本。Gas 是一个衡量以太坊网络计算工作量的单位,每个操作都有一个固定的 gas 消耗量,例如加法消耗3 gas,存储操作消耗更多。

当提到“gas 限制 2300”时,通常是在谈论向合约地址发送以太币时默认分配的 gas 量。在以太坊中,当你发送以太币(但不附带任何数据或调用任何函数)时,交易会附带一个 2300 gas 的限制。这个数值是为了足够支付日志事件(例如,Transfer 事件)的记录成本,但通常不足以执行任何更复杂的操作。

这个限制是为了防止在简单的 ETH 转账中意外执行复杂的合约代码,这可能会消耗更多的 gas。如果合约需要在接收以太币时执行更复杂的逻辑,那么发送者需要手动提高 gas 限制,以确保交易有足够的 gas 完成。

这个 2300 gas 限制是一个“最小单位”或者说是一个安全的最低限度,确保了在发送ETH时至少能完成最简单的操作,如记录一个事件。然而,这个数值并不是说每个交易都必须消耗 2300 gas,实际的消耗量取决于执行的具体操作。如果交易中没有执行任何代码,实际消耗的 gas 可能会更少。如果需要执行更多操作,则必须指定更高的 gas 限制。

error的使用

在错误的处理的时候,我们可能会使用 require, assert ,revert检查语句的条件,当条件未满足的时候中断合约的执行/

error配合着revert使用,用法如下

// Solidity 0.7 or earlier
function restrictedAction() public {
    require(msg.sender == owner, "Not the owner");
    // ...执行操作
}

// Solidity 0.8.4 or later with custom error
error NotOwner();

function restrictedAction() public {
    if (msg.sender != owner) {
        revert NotOwner();
    }
    // ...执行操作
}

当使用 error 并通过 revert 触发时,合约执行会立即停止,并且回滚所有状态变更和未花费的gas(除了用于触发错误的gas)。这与传统的 require 语句相似,但 error 允许你定义一个无需字符串描述的错误类型。

完整功能的代码

接收合约完整的基本功能

  1. 接收到的转账时,看看我们账户余额是否增加
  2. 谁转的,剩余多少gas费
  3. 这里只是接收eth,使用receive就行了
pragma solidity ^0.8.0;

contract re_eth{
    event receive_eth(uint256 value,uint256 gas);

    function getbalance() view public returns (uint256){
        return address(this).balance;
    }

    receive() external payable { 
        emit receive_eth(msg.value,gasleft());
    }
}

关于gasleft()

  1. 交易开始:当你发起一笔交易时,你会指定一个gas limit,这是你愿意为这笔交易支付的最大gas量,同时你也会指定一个gas price,这是你愿意为每单位gas支付的价格。
  2. 执行交易:交易开始执行时,EVM会从交易的gas限额中扣除执行操作所需的gas量。每执行一条指令都会消耗一定的gas。
  3. 剩余gas:在执行过程中,随时可以调用gasleft()函数来获取当前交易中剩余的gas量。这意味着你可以知道到目前为止已经消耗了多少gas,还剩下多少gas可以使用。
  4. 交易结束:交易执行完毕后,如果还有剩余的gas,这些gas会退还给交易发起者。如果交易在执行过程中消耗的gas超过了gas limit,交易会被回退(revert),状态还原为交易之前的状态,但gas仍然会被消耗完。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
浪迹陨灭
浪迹陨灭
0x0c37...a92b
专注于solidity智能合约的开发