解读Solidity中的可见性修饰符:从基础知识到优化再到安全性

本文深入探讨了Solidity中的可见性修饰符(public, external, internal, private),分析了它们的基础知识、优化技巧(external函数比public函数更节省gas)以及安全注意事项(private并非真正私有)。同时,文章强调了访问控制和输入验证的重要性,并提供了关于函数布局和接口可见性的实用建议。

在使用 Solidity 语言学习智能合约开发时,至关重要的部分之一是掌握可见性说明符。

Solidity 中的可见性说明符为开发者提供了额外的能力来控制智能合约中函数或状态变量的可访问性(或可见性)。

它们主要有 4 种不同的类型 -> external、public、internal 和 private。

好的!现在我们将快速回顾基础知识,以便更好地理解本文接下来要介绍的部分。

然而,如果你已经了解了基础知识,请随时移至优化部分

基础知识

  1. Public:
  • 具有 public 可见性的函数是所有函数中最容易访问的。它可以从任何地方访问,即合约内部、合约外部,甚至可以通过继承合约访问。
  • 有趣的是,当将 public 说明符分配给状态变量时,将自动生成该特定状态变量的 getter 函数。

2. External

  • 与 Public 可见性不同,external 说明符不能从合约内部调用,只能从合约外部或由第三方调用。
  • 此外,external 可见性只能分配给函数,而不能分配给状态变量。

关于 public & external 函数的 gas 消耗差异的一个非常有趣的事实在本文的优化**部分中进行了解释。

3. Internal

  • 顾名思义,具有 internal 可见性的函数只能从定义该函数的合约内部或从继承该函数的合约内部访问。
  • 具有 internal 可见性说明符的状态变量也是如此。

4. Private

  • Private 可见性是最受限制的一种。一旦分配给一个函数,它只允许从定义它的合约内部访问该函数。
  • Internal 不同,具有 private 可见性的函数甚至无法通过继承合约访问。
  • 当与状态变量一起使用时,private 可见性的类似规则也适用。除了包含它的合约之外,任何其他合约都不能访问或修改 Private 状态变量。

❓快速提问❓

如果 private 可见性 限制了任何第三方合约或用户对状态变量的访问,这是否意味着我们可以将秘密信息存储在智能合约的 private 状态变量中?

🤔 思考一下,我们将在 安全部分 回答它。


优化:External 函数比 Public 函数消耗更少的 gas

虽然具有 external 和 public 可见性的函数行为几乎相似,但两者之间存在一些重要的 gas 优化差异,必须牢记。

例如,快速浏览下面的图片。👇

External 和 Public 可见性函数之间的 Gas 消耗的明显差异

你是否注意到 public 函数比 external 函数消耗更多的 gas

好吧,为什么?

public 函数的情况下,函数的参数被复制到 𝐌𝐄𝐌𝐎𝐑𝐘 中。而另一方面,具有 external 可见性的函数可以直接从 𝐂𝐀𝐋𝐋𝐃𝐀𝐓𝐀 中读取参数。

现在,由于 CALLDATAMEMORY 便宜,因此 external 函数产生的执行成本比 public 函数低。

好吧,这可能会导致另一个问题:

为什么 public 函数将参数复制到 memory 中,而 external 函数不复制?

正如先前讨论的,public 函数可以从合约的外部和内部调用,即内部调用

内部调用通过操作码 JUMP 执行,因为数组参数在内部通过指向内存的指针传递。

因此,当编译器为内部函数生成操作码时,该函数期望其参数位于内存本身中。

但是,这根本不是 external 函数的情况。它们根本不关心内部调用,因此最终节省了一些 gas。


关于可见性说明符的安全提示

  1. PRIVATE 实际上并不是 PRIVATE ⚠️

还记得 基础知识 部分中的快速问题吗?

是时候回答了。⏰

智能合约开发者,尤其是初学者,通常会误以为 private 可见性说明符实际上使状态变量不可读或完全无法访问。

好吧,事实并非如此。💡

具有 private 关键字的状态变量仍然可以在链上读取。

此外,即使你的合约未经过验证,任何第三方参与者都可以查看其交易,以找出存储在合约状态中的值。

你问,具体是怎么做到的? 🤔

查看这个快速精彩视频,它清楚地解释了如何完成它的底层细节。

因此,如果任何未加密的重要数据存储在链上,即使具有 private 可见性,也会成为安全问题。

敏感或关键数据应完全存储在链下,或在链上进行充分加密。

2. 默认可见性可能非常危险 🔴

还在使用低于 0.5 的 solidity 版本?

那么你可能需要小心默认可见性。

在低于 0.5.0 的 solidity 版本中,没有任何显式可见性关键字分配的函数默认被认为是 public。

如果对关键状态进行修改的函数由于错误而根本没有分配任何可见性关键字,这可能会导致潜在的利用场景。

由于默认可见性 public 将分配给这个重要函数,因此任何恶意参与者都可以通过触发该函数来进行意外的状态修改。

此安全问题清楚地说明了 solidity 中可见性类型的重要性,以及为什么应该非常仔细地为特定函数选择它们。

3. 对于可供世界访问的函数,访问控制无疑非常重要 ⚠️

考虑到前面的提示,重要的是要注意,任何具有 external/public 可见性的函数基本上对任何用户都是可见、可读和可访问的。

一个关于 访问控制不足缺乏输入验证200 万美元黑客攻击 的快速示例,发生在 external 或 public 函数 中 (200 万美元黑客攻击) 。🥷

由于访问控制不足导致的 TempleDao 黑客攻击

因此,在开发或审计此类函数时,必须牢记 2 个重要点。

谁可以调用这些函数?

  • 由于 external/public 可见性关键字使第三方参与者可以完全访问函数,因此为这些函数分配足够的 访问控制修饰符 变得至关重要。
  • 访问控制修饰符有助于我们在此类函数上添加一层安全保护,因为仅可见性说明符并不能阻止钱包或合约访问它们。

在此处 阅读有关不同类型的访问控制机制的更多信息。

如何调用这些函数?

  • 虽然访问控制绝对可以帮助提供额外的安全性,但它可能并不总是需要的。
  • 某些 public 或 external 函数旨在供链上的每个用户调用。
  • 因此,在这一点上,不再是谁可以调用这些函数的问题,而是更多关于应该如何调用这些函数的问题(即,在调用函数时传递正确的参数)。
  • 简而言之,对于开放函数,确保包含 适当的输入验证 非常重要。
  • 这是为了确保虽然每个人都可以访问该函数,但传递给该函数的参数在有效范围内,并且符合合约的预期。

关于可见性说明符的补充说明

  1. 接口中函数的可访问性应始终为 External
  2. “Free 函数” 的可见性应始终为 Internal。在此处 了解有关 Free 函数的更多信息:https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/:%7E:text=A%20free%20function%20behaves%20like,and%20in%20this%20case%2C%20msg
  3. solidity 中的 receive() 和 fallback() 函数必须始终分配 External 可见性。
  4. 函数布局顺序: 在合约中包含适当的函数布局可以提高其可读性。在 solidity 中,按照以下顺序排列不同的函数(基于它们的类型和可见性)被认为是更好的做法 👇
  • constructor
  • receive 或 fallback 函数(如果需要)
  • external 函数
  • public 函数
  • internal 函数
  • private 函数
  • view 或 pure 函数(如果有)
  • 原文链接: decipherclub.com/deciphe...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
decipherclub
decipherclub
江湖只有他的大名,没有他的介绍。