news 2026/9/12 4:35:13

Sway 合约所有权与访问控制:基于 msg_sender() 实现 Owner 权限管理的完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sway 合约所有权与访问控制:基于 msg_sender() 实现 Owner 权限管理的完整实战

Sway 合约所有权与访问控制:基于 msg_sender() 实现 Owner 权限管理的完整实战

【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway

导读

在去中心化应用中,大量合约功能需要限制为只有特定账户(如合约部署者、DAO 管理员)才能调用,例如提现、升级、修改配置等敏感操作。本文基于 Sway 语言官方文档中的contract-ownership示例,从零实现一个标准的"合约所有权(Ownership)"访问控制模式:通过Option<Identity>存储记录合约所有者,并借助标准库msg_sender()校验调用者身份,最终实现"仅所有者可执行"的权限门禁。读完本文,你将掌握 Sway 合约中Identity的使用、msg_sender()的底层原理、assert防御式校验的写法,以及完整的可运行合约代码。

一、核心设计思想:合约所有权模式

合约所有权(Contract Ownership)是最基础、最常用的访问控制模式之一。其思路非常直观:

  1. 在合约存储中记录一个"所有者"(owner)标识;
  2. 在需要保护的函数入口处,将当前调用者(msg_sender()的返回值)与存储中的所有者进行比较;
  3. 身份不匹配时通过assert直接让交易回滚(revert),阻止未授权调用继续执行。

该示例对应的完整可运行源码位于 docs/reference/src/code/examples/access-control/ownership/src/main.sw,其工程清单见 docs/reference/src/code/examples/access-control/ownership/Forc.toml。

Sway 合约由"ABI(接口)"与"ABI 实现"两部分构成,详见 Smart Contracts:ABI 定义了合约对外暴露的端点,impl <ABI名> for Contract则负责给出具体实现。下面我们严格按此结构展开。

二、定义 ABI:暴露"设置所有者"与"受限动作"两个端点

ABI 是合约对外调用的接口层。在本例中,合约暴露两个函数:

  • set_owner:设置(或转移)所有者,需要读写存储;
  • action:只有所有者才能调用的受保护动作,仅读取存储。
abi Ownership { #[storage(read, write)] fn set_owner(owner: Option<Identity>); #[storage(read)] fn action(); }

代码要点:

  • #[storage(read, write)]#[storage(read)]是存储访问注解,告知编译器该函数对存储的访问模式。set_owner因为要写入新的所有者,所以声明为read, writeaction只需要比对存储中的所有者,声明为read即可。存储注解不匹配时编译器会报错,这是 Sway 强制显式声明存储行为的体现。
  • set_owner的参数类型是Option<Identity>而非Identity。之所以允许"空",是为了支持"清除所有者"或"初始尚无所有者"的场景,这与下文存储初始值的设置保持一致。

三、Identity 与存储:记录所有者

所有者是谁?Sway 用Identity统一表达"外部地址(Address)"与"合约(ContractId)"两类调用者身份,从而让访问控制同时适用于"EOA 账户"和"合约账户"。

我们必须在存储中持久化记录所有者,并在每次调用时与调用者对比。由于合约刚部署时并不存在所有者,初始值被设置为None

storage { owner: Option<Identity> = None, }

代码要点:

  • storage块声明了链上存储变量owner,类型为Option<Identity>,初始值为None
  • 在实现函数中,通过storage.owner.read()读取、storage.owner.write(...)写入;
  • Option在 Sway 标准库中定义(sway-lib-std/src/option.sw),None表示"尚无所有者",这正是"首次设置所有者"这一特殊分支能被正确处理的根基。

四、实现访问控制:set_owner 与 action

ABI 只是接口声明,真正的权限逻辑在impl Ownership for Contract中实现。与 Rust 中 trait 的实现语法一致,所有 ABI 中声明的函数都必须在实现中给出(见 Smart Contracts):

impl Ownership for Contract { #[storage(read, write)] fn set_owner(owner: Option<Identity>) { assert(storage.owner.read().is_none() || storage.owner.read().unwrap() == msg_sender().unwrap()); storage.owner.write(owner); } #[storage(read)] fn action() { assert(storage.owner.read().unwrap() == msg_sender().unwrap()); // code } }

4.1 set_owner:满足任一条件才允许设置所有者

设置所有者时,必须满足以下两个条件之一,否则assert触发回滚:

  • 当前没有所有者storage.owner.read().is_none()):合约刚部署、所有者尚未初始化时的首次授权路径;
  • 当前调用者就是所有者storage.owner.read().unwrap() == msg_sender().unwrap()):所有者本人调用,用于后续"转移所有权"或"重新授权"。

条件通过后,将新值写入存储:storage.owner.write(owner)。注意这里写入的就是调用者传入的Option<Identity>,因此调用方也可以传None来"清除"所有者(具体业务是否允许清除由开发者自行约定)。

4.2 action:仅所有者可调用

action()代表任何需要权限保护的合约功能。它的校验只有一条:

assert(storage.owner.read().unwrap() == msg_sender().unwrap());
  • 若存储中还没有所有者,unwrap()会在断言之前直接使调用失败(None不可unwrap),保证"无主合约"无法执行受保护操作;
  • 若调用者不是所有者,assert中的相等比较为false,交易立即回滚,// code处的业务逻辑不会执行;
  • 只有所有者本人调用时,校验通过,后续代码才继续运行。

在实际项目中,action()内的业务代码可以是提现、暂停、参数更新等任意敏感操作。这套模式即通用"Ownable"风格的权限门禁。

五、深入底层:msg_sender() 是如何判定调用者身份的

示例中反复出现的msg_sender()由标准库自动导入(见 prelude),其实现位于 sway-lib-std/src/auth.sw。理解它的判定逻辑,才能明白为什么所有权校验是可信的。

msg_sender()的返回类型是Result<Identity, AuthError>,其核心逻辑分两条路径:

pub fn msg_sender() -> Result<Identity, AuthError> { if caller_is_external() { match caller_address() { Err(err) => Err(err), Ok(owner) => Ok(Identity::Address(owner)), } } else { // Get caller's `ContractId`. Ok(Identity::ContractId(caller_contract_id())) } }

关键机制逐层拆解(依据 sway-lib-std/src/auth.sw):

  1. 判断调用是否来自外部caller_is_external()通过内联汇编读取虚拟机元数据指令gm r1 i1,返回true表示调用者是外部脚本(EOA 地址),false表示调用者是另一个合约;
  2. 外部调用者 → 解析地址:调用caller_address(),它遍历交易的全部输入(Input::CoinInput::Message),要求所有输入属于同一个所有者,否则返回AuthError::InputsNotAllOwnedBySameAddress。这正是防止"一个交易里混入多个签名者"从而混淆身份的防护;
  3. 合约调用者 → 解析合约 IDcaller_contract_id()通过gm r1 i2指令获取调用方合约的ContractId
  4. 最终统一包装为Identity::Address(...)Identity::ContractId(...),使外部账户与合约在访问控制上等价对待。

错误类型AuthError同样定义在 sway-lib-std/src/auth.sw:InputsNotAllOwnedBySameAddress表示外部输入并非同属一个地址;CallerIsInternal表示在内部上下文错误地调用了caller_address。由于msg_sender()返回的是Result,示例中使用.unwrap()直接取Identity,一旦出错整个调用回滚——在权限校验场景中这是可接受的"fail-closed(默认拒绝)"策略。

六、工程配置与运行方式

示例合约以独立 Forc 工程组织,其清单 docs/reference/src/code/examples/access-control/ownership/Forc.toml 内容如下:

[project] authors = ["Fuel Labs <contact@fuel.sh>"] entry = "main.sw" license = "Apache-2.0" name = "ownership" [dependencies] std = { path = "../../../../../../../sway-lib-std" }

配置说明:

  • name = "ownership":工程名,也是生成 ABI 文件的基础名;
  • entry = "main.sw":入口源文件;
  • std依赖通过相对路径指向仓库根目录下的 sway-lib-std(真实工程中一般替换为已发布的std版本号)。

在已安装forc(Fuel Orchestrator)的前提下,可在此目录执行以下命令进行构建与测试:

forc build # 编译合约,生成 ABI 与字节码 forc test # 运行合约测试(如配置了测试目标)

构建产物包括 JSON 格式的 ABI 文件与二进制字节码,可供 SDK(如 fuels-rs / fuels-ts)加载后部署与调用。

七、与本主题相关的进一步阅读

围绕"访问控制"这一主题,仓库内还有以下资料可深入:

  • Message Sender 官方文档:单独讲解msg_sender()的用途与访问控制应用,并给出将调用者与OWNER常量比较的最小示例;
  • Smart Contracts 文档:ABI 与impl ... for Contract的完整语法约定;
  • 标准库实现 sway-lib-std/src/auth.sw:msg_sendercaller_addresscaller_contract_idcaller_is_external等全套调用者身份 API;
  • 类型定义 sway-lib-std/src/identity.sw 与 sway-lib-std/src/address.sw:IdentityAddress的构造与比较方式。

八、总结

本文完整还原了 Sway 官方contract-ownership示例:通过Option<Identity>存储所有者、以msg_sender()获取调用者身份、用assert强制权限校验,实现了set_owner(设置/转移所有权)与action(仅所有者可执行)两个核心函数。结合 sway-lib-std/src/auth.sw 的源码可以看到,msg_sender()在底层依赖 FuelVM 的gm元数据指令与输入所有者一致性检查,为访问控制提供了可靠的运行时依据。这套"所有权 + 调用者校验"的模式是 Sway 合约实现管理员功能、DAO 治理与合约自托管的基础设施,可直接复用到生产合约中。

【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 4:34:51

Java路径操作:从基础Path到跨平台实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:34:48

Python Flask+Django混合架构开发药品仓库管理系统

1. 项目背景与核心需求药品仓库管理是医疗机构运营中至关重要的环节。传统的手工记录方式效率低下且容易出错&#xff0c;而市面上的通用仓库管理系统往往无法满足诊所特有的药品管理需求。这正是我们选择用Python开发轻量级药品仓库管理系统的原因。Flask和Django作为Python两…

作者头像 李华
网站建设 2026/9/12 4:34:47

Skynet 游戏服务器:3 步跑通第一个服务节点

Skynet 游戏服务器:3 步跑通第一个服务节点 【免费下载链接】skynet A lightweight online game framework 项目地址: https://gitcode.com/GitHub_Trending/sk/skynet 一个 3 人小团队想在两天内上线战斗服,自己写 TCP 重连、消息分帧、跨节点路由,时间肯定不够。Skyne…

作者头像 李华
网站建设 2026/9/12 4:33:22

YOLOv5安全帽检测系统:从模型训练到RTSP实时视频流部署

简介&#xff1a;基于Python与YOLOv5算法打造的安全帽佩戴及危险区域实时检测毕业设计项目&#xff0c;面向计算机、通信、人工智能、自动化等专业学生与从业者&#xff0c;可服务于毕业设计、课程设计或工程入门。项目已接通海康摄像头实现实时视频流检测&#xff0c;覆盖模型…

作者头像 李华
网站建设 2026/9/12 4:33:16

基于Flask与Vue的大学生阅读行为分析系统设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华