WEB3-Day7—Solidity基础[part6]

WEB3-Day7—Solidity基础[part6]

变量优化

优化仅设置一次的变量的燃气消耗(gas usage)

变量 ownerminimumUSD 仅设置一次且永不改变:owner 在合约创建时赋值,minimumUSD 在合约初始化时设定。

评估 FundMe 合约

我们可以通过部署合约并在终端中观察交易来评估创建合约所需的燃气量。在原始合约配置中,我们消耗了近 859,000 单位燃气。

常量(Constant)

为减少燃气消耗,我们可以使用 constantimmutable 关键字。这些关键字确保变量值保持不变。更多信息可参考 Solidity 文档

对于仅赋值一次且永不改变的变量,我们可以应用这些关键字。

对于编译时已知的值,使用 constant 关键字。它会避免变量占用存储槽位,从而使读取更便宜、更快速。

使用 constant 关键字可节省约 19,000 单位燃气,这接近在两个账户之间发送 ETH 的成本。

📝 注意
constant 变量的命名约定为全大写并以下划线分隔(例如 MINIMUM_USD)。

⚠️ 警告
将当前 ETH 燃气成本转换为美元时可见,当 ETH 价格为 3000 美元时,将 MINIMUM_USD 定义为常量需花费 9 美元,比其公开变量(public)的等效实现多近 1 美元。

不可变(Immutable)

constant 变量适用于编译时已知的值,而 immutable 适用于部署时设置且不再改变的变量。immutable 变量的命名约定是在变量名前添加前缀 i_(例如 i_owner)。

owner 设为 immutable 变量后,观察到其燃气节省效果与 constant 关键字类似。

💡 提示
在学习的早期阶段,不必过于关注燃气优化。先保证合约能够完整写出来先。

自定义错误

需求检查(Require)

提升燃气效率的一种方式是优化 require 语句。当前,require 语句强制我们存储字符串 'sender is not an owner',该字符串中的每个字符都会单独存储,导致管理逻辑复杂且成本高昂。

自定义错误(Custom Errors)

Solidity 0.8.4 版本引入的自定义错误可用于 revert 语句中。这些错误需在代码顶部声明,并在 if 语句中使用。相较于之前的错误消息字符串,调用更轻量的错误代码可降低燃气成本。

我们可以先创建一个自定义错误:

error NotOwner();

然后用 if 语句替换 require 函数,并通过 revert 调用新创建的错误:

if (msg.sender != i_owner) {
    revert NotOwner();
}

通过实现自定义错误,我们既能降低燃气消耗,又能简化智能合约中的错误处理。

结论

本节课中,我们学习了如何通过使用自定义错误替代传统含字符串的 require 语句,进一步优化 Solidity 合约的燃气效率。

receive和fallback函数

receivefallback是特殊函数,当用户直接向合约发送以太币或调用不存在的函数时会触发。这些函数不返回任何值,且必须声明为external

为了说明这一点,我们创建一个简单的合约:

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

contract FallbackExample {
    uint256 public result;

    receive() external payable {
        result = 1;
    }

    fallback() external payable {
        result = 2;
    }
}

在这个合约中,result初始化为0。当以太币发送到合约时,receive函数会被触发,将result设为1。如果交易包含数据但指定的函数不存在,则会触发fallback函数,将result设为2。

如需全面解释,请参考SolidityByExample

发送以太币的逻辑流程

向合约发送以太币
        |
        ↓
    msg.data为空吗?
      /        \
     是          否
    /            \
  receive()存在?   fallback()
    /      \
   是        否
  /          \
receive()     fallback()

向fundMe合约发送以太币

当用户直接向fundMe合约发送以太币而不调用fund函数时,可以使用receive函数将交易重定向到fund函数:

receive() external payable {
    fund();
}

fallback() external payable {
    fund();
}

要测试此功能,可使用MetaMask向fundMe合约发送一些Sepolia测试网以太币。此时并未直接调用fund函数,但receive函数会触发它。确认交易后,检查funders数组,会发现其已更新,表明receive函数成功调用了fund函数。

这种方法确保所有交易按预期处理。尽管直接调用fund函数消耗的Gas更少,但此方法能确保用户的贡献被正确确认和记录。