什么是 public、external、internal 和 private?合约函数可见性怎么选

Solidity 四种函数可见性决定调用边界,却不等于访问控制或链上保密。本文讲清外部调用、继承、自调用、ABI 与代理风险。

什么是 public、external、internal 和 private?合约函数可见性怎么选

一份合约把某个函数标成 private,有人便说“外部看不到,很安全”;另一个函数写着 public,又有人理解成“所有人都能随便操作”。这两种说法都混淆了两个问题:代码从哪里能调用,以及调用者是否满足业务权限。

Solidity 函数有四种可见性:external、public、internal、private。它们定义调用边界和继承范围,却不会自动完成管理员鉴权,也不会把链上数据变成秘密。

可见性回答“调用入口在哪里”,访问控制回答“谁被允许执行”。

external:面向合约外部的入口

external 函数属于合约外部接口,可以被交易或其他合约调用。当前合约不能直接用 f() 方式内部调用它;写 this.f() 会产生一次真正的外部消息调用,改变调用上下文并增加开销与风险。

public:外部和内部都能调用

public 函数也出现在外部接口中,同时允许合约内部直接调用。它并不表示任何地址都能成功完成操作,函数体仍可通过角色、签名、余额和状态条件拒绝调用。

Solidity 四种函数可见性定义外部内部和继承调用边界
先看调用来自合约外、当前合约还是派生合约,再判断可见性。

internal:当前合约与派生合约可用

internal 函数不会成为普通外部 ABI 入口,只能由当前合约或继承它的合约内部调用。因为不暴露给外部 ABI,它还能接收 mapping 或 storage 引用等内部类型。internal 常用于复用核心逻辑,再由不同 public 或 external 入口做权限和参数检查。

private:只限定义它的合约

private 函数不能被派生合约直接调用,但这不是安全边界的全部。部署后的字节码公开可得,交易和状态变化也能观察。private 主要帮助代码封装,不能用于隐藏算法、密钥或商业秘密。

可见性外部调用当前合约直接调用派生合约调用
external可以不能直接调用作为外部调用处理
public可以可以可以
internal不可以可以可以
private不可以可以不可以

this.f() 不是普通内部调用

通过 this 调用 public 或 external 函数,会从当前合约地址向自己发起外部调用。新的调用帧里 msg.sender 可能变成合约自身,还会经过 ABI 编解码。若权限逻辑没有考虑这种差异,行为可能与直接调用完全不同。

可见性不等于访问控制

提款函数即使 external,也可以要求 msg.sender 是管理员;内部函数即使 internal,仍可能被一个没有鉴权的 public 入口间接触发。审计必须沿所有外部入口追踪到敏感状态变化,不能看到 private 或 internal 就停止。

Private 状态变量同样不保密

状态变量可见性略有不同:public 会由编译器生成 getter,internal 可供派生合约访问,private 不供派生合约直接访问。但链上 storage 对观察者并非秘密,知道布局的人仍可能从节点数据解析内容。

Solidity private 变量仍存储在公开区块链状态中
private 限制 Solidity 代码访问,不会为链上数据提供加密。

Public 变量只自动生成读取器

public 状态变量会获得外部 getter,但不会自动生成 setter。外部可以读取不等于外部可以直接改写。对于数组和复杂结构,自动 getter 也可能只支持按索引或键查询,不一定返回完整对象。

ABI 会暴露哪些函数

public 和 external 函数属于合约外部接口,通常出现在 ABI;internal 和 private 不作为外部入口出现。不过通过 public 函数触发的内部逻辑仍属于交易行为。ABI 缺少某个函数名,不代表相关逻辑永远无法执行。

继承会改变可调用范围

派生合约能直接使用 internal 成员,却不能直接使用父合约 private 成员。函数重写还受 virtual、override 和可见性规则影响。阅读最终部署合约时,要把继承层次展开,确认真正执行的是哪一版实现。

代理合约让入口更间接

代理可以通过 fallback 和 delegatecall 把外部调用交给实现合约。用户面对的是代理地址,可见性与权限逻辑却来自当前实现。因此既要检查 ABI 和实现版本,也要检查升级权与初始化状态。

开发者如何选择

  • 只打算供外部调用的接口优先考虑 external。
  • 既需外部也需内部复用时使用 public,或拆成 external 入口加 internal 核心。
  • 供继承体系复用的实现细节使用 internal。
  • 仅当前合约需要的辅助逻辑可用 private,但不要把它当保密工具。
  • 所有敏感外部路径都要独立做访问控制与状态检查。

审计检查清单

  1. 列出全部 public、external、receive 和 fallback 入口。
  2. 标出每个入口能修改的资金、权限和关键状态。
  3. 沿内部调用追踪到 internal 与 private 函数。
  4. 验证角色检查是否能被代理、自调用或回调绕过。
  5. 检查 public getter 是否意外暴露敏感业务信息。
  6. 在最终继承和代理实现上复核,而非只看单个文件。

官方资料

本文依据 Solidity:Function Visibility 与同页的 State Variable Visibility、Getter Functions。实际开发请以项目锁定编译器版本对应文档为准。

四种可见性是代码结构工具,不是完整安全模型。先找所有外部入口,再追踪它们能触达的内部逻辑和权限条件,才能判断一个操作究竟对谁开放、会改变什么。


本文为链上指南原创科普内容,不构成任何投资建议。智能合约可见性、权限和代理结构均可能影响资产安全,交互前请核对验证源码与独立审计。