 实现 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访问控制模式通过OptionIdentity存储记录合约所有者并借助标准库msg_sender()校验调用者身份最终实现仅所有者可执行的权限门禁。读完本文你将掌握 Sway 合约中Identity的使用、msg_sender()的底层原理、assert防御式校验的写法以及完整的可运行合约代码。一、核心设计思想合约所有权模式合约所有权Contract Ownership是最基础、最常用的访问控制模式之一。其思路非常直观在合约存储中记录一个所有者owner标识在需要保护的函数入口处将当前调用者msg_sender()的返回值与存储中的所有者进行比较身份不匹配时通过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 ContractsABI 定义了合约对外暴露的端点impl ABI名 for Contract则负责给出具体实现。下面我们严格按此结构展开。二、定义 ABI暴露设置所有者与受限动作两个端点ABI 是合约对外调用的接口层。在本例中合约暴露两个函数set_owner设置或转移所有者需要读写存储action只有所有者才能调用的受保护动作仅读取存储。abi Ownership { #[storage(read, write)] fn set_owner(owner: OptionIdentity); #[storage(read)] fn action(); }代码要点#[storage(read, write)]与#[storage(read)]是存储访问注解告知编译器该函数对存储的访问模式。set_owner因为要写入新的所有者所以声明为read, writeaction只需要比对存储中的所有者声明为read即可。存储注解不匹配时编译器会报错这是 Sway 强制显式声明存储行为的体现。set_owner的参数类型是OptionIdentity而非Identity。之所以允许空是为了支持清除所有者或初始尚无所有者的场景这与下文存储初始值的设置保持一致。三、Identity 与存储记录所有者所有者是谁Sway 用Identity统一表达外部地址Address与合约ContractId两类调用者身份从而让访问控制同时适用于EOA 账户和合约账户。我们必须在存储中持久化记录所有者并在每次调用时与调用者对比。由于合约刚部署时并不存在所有者初始值被设置为Nonestorage { owner: OptionIdentity None, }代码要点storage块声明了链上存储变量owner类型为OptionIdentity初始值为None在实现函数中通过storage.owner.read()读取、storage.owner.write(...)写入Option在 Sway 标准库中定义sway-lib-std/src/option.swNone表示尚无所有者这正是首次设置所有者这一特殊分支能被正确处理的根基。四、实现访问控制set_owner 与 actionABI 只是接口声明真正的权限逻辑在impl Ownership for Contract中实现。与 Rust 中 trait 的实现语法一致所有 ABI 中声明的函数都必须在实现中给出见 Smart Contractsimpl Ownership for Contract { #[storage(read, write)] fn set_owner(owner: OptionIdentity) { 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)。注意这里写入的就是调用者传入的OptionIdentity因此调用方也可以传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()的返回类型是ResultIdentity, AuthError其核心逻辑分两条路径pub fn msg_sender() - ResultIdentity, AuthError { if caller_is_external() { match caller_address() { Err(err) Err(err), Ok(owner) Ok(Identity::Address(owner)), } } else { // Get callers ContractId. Ok(Identity::ContractId(caller_contract_id())) } }关键机制逐层拆解依据 sway-lib-std/src/auth.sw判断调用是否来自外部caller_is_external()通过内联汇编读取虚拟机元数据指令gm r1 i1返回true表示调用者是外部脚本EOA 地址false表示调用者是另一个合约外部调用者 → 解析地址调用caller_address()它遍历交易的全部输入Input::Coin与Input::Message要求所有输入属于同一个所有者否则返回AuthError::InputsNotAllOwnedBySameAddress。这正是防止一个交易里混入多个签名者从而混淆身份的防护合约调用者 → 解析合约 IDcaller_contract_id()通过gm r1 i2指令获取调用方合约的ContractId最终统一包装为Identity::Address(...)或Identity::ContractId(...)使外部账户与合约在访问控制上等价对待。错误类型AuthError同样定义在 sway-lib-std/src/auth.swInputsNotAllOwnedBySameAddress表示外部输入并非同属一个地址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 contactfuel.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版本号。在已安装forcFuel 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.swmsg_sender、caller_address、caller_contract_id、caller_is_external等全套调用者身份 API类型定义 sway-lib-std/src/identity.sw 与 sway-lib-std/src/address.swIdentity、Address的构造与比较方式。八、总结本文完整还原了 Sway 官方contract-ownership示例通过OptionIdentity存储所有者、以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),仅供参考