如何审计Solana智能合约 第四部分:Anchor框架

  • Sec3dev
  • 发布于 2021-12-17 12:41
  • 阅读 21

本文深入探讨了Solana中Anchor框架的内部机制,介绍了其编程模型、代码生成和常用宏的工作原理,以及在智能合约审核过程中需要注意的陷阱。通过实例,详细阐述了Anchor如何简化智能合约的开发,但同时指出在使用Anchor时可能出现的潜在问题。

第 3 部分:渗透测试 后,本文介绍了Anchor的内部实现,这是一种流行的用于编写和测试 Solana 智能合约的框架。

与以太坊的Truffle类似,Anchor 提供了一系列用于在 Solana 上开发完整应用程序的功能,特别是:

我们将从审计师的角度详细探讨 Anchor 的内部原理一些注意事项

Anchor 编程模型和代码生成

Anchor 提供了 声明式编程模型,用户可以使用 注解方法或数据类型(结构体及其字段),这些宏会 自动生成代码包装器,以便在 Solana 上执行。

生成的代码包装器可以完成多种操作,例如解码输入数据、创建和初始化账户,更重要的是,确保输入数据的附加约束,例如,确保输入账户是签名者以及多个输入账户之间的关系。

有三种常用的 Anchor 宏:

  • #[program] — 在 #[program] 内声明的全局指令
  • #[derive(Accounts)] — 反序列化为输入账户向量的结构体
  • #[account(…)] — 与每个结构体字段相关的约束,即每个输入账户

接下来,我们将使用一个示例(当中的 basic-2)详细说明上述三种宏及其生成的代码。

还有许多其他的 Anchor 宏,例如 ` #[state] ` (状态方法)、 ` #[interface] ` (接口方法)等。可以在Anchor CHANGELOG中找到它们的列表。

图 1. #[program] 宏在 basic-2 中的应用

1.#[program] — 全局指令

在没有 Anchor 的标准 Solana 程序中,将会有一个入口点(由 entrypoint! 宏定义),入口点需要三个参数:program_idaccountsinstruction_data。要调用相应的指令,程序必须解析 instruction_data。

然而,使用 Anchor,就不需要指定入口点或解析 instructiondata。所有操作都由 ` **#[program]_** ` 宏处理。

图 1 显示了两个函数 ( 第 9 行create第 16 行increment):这些是将通过交易调用的合约指令。借助 ` #[program] ` 宏 (第 5 行),Anchor 将生成以下代码来调用这些指令(使用 cargo expand 显示宏扩展结果):

图 2. Anchor 生成的入口点

入口点函数将使用 solana_program::entrypoint::deserializ 解码输入为一个元组 ( program_id, accounts, instruction_data),并调用另一个函数 “ entry”,再调用函数“dispatch”,将元组作为参数传入(类似于 process_instruction):

图 3. Anchor 生成的入口函数

图 4. Anchor 生成的 dispatch 函数

在图 4 中,dispatch 函数使用 指令数据的前 8 个字节(称为“ sighash”)来识别调用的指令。如果 sighash 与用户定义的指令匹配,则将调用相应的方法处理程序包装器。例如,[24, 30, 200, 40, 5, 28, 7, 119] 对应于 “ __global::create ,而 [11, 18, 104, 9, 104, 174, 59, 33] 对应于 “ __global::increment”。

我们示例中的方法处理程序包装器定义如下:

图 5. Anchor 生成的方法处理程序包装器

这些包装器( __global::create 和 __global::increment) 包装相应的指令( basic_2:createbasic_2:increment),执行账户的反序列化、构建上下文、调用用户代码,最后执行退出例程,这通常会持久化账户更改。

图 6. #[derive(Accounts)] 和 #[account(…)] 宏在 basic-2 中的应用

2. #[derive(Accounts)] — 账户反序列化

对于每个标记为 #[derive(Accounts)] 的结构体 T,Anchor 将生成一个相应的函数 T::try_accounts,用于反序列化输入账户并添加验证检查。

例如,在图 6 中,#[derive(Accounts)] 在结构体 CreateIncrement 的顶部声明。这告诉 Anchor 生成两个 try_accounts 函数( Create::try_accounts 和 Increment::try_accounts),这些函数将输入账户反序列化为 ctx.accounts,如图 5 中所示。

请注意,合约指令的第一个参数都是 ` ctx `,它是参数类型 ` Context<T> `。在图 1(basic_2 示例)中,` Context<Create>` 和 ` Context<Increment> 分别。

图 7 显示了 ` Context ` 的定义,它是一个封装了三个字段的结构体: program_id、accounts 和 remaining_accounts注意:虽然 accounts 是根据宏反序列化和验证的,remaining_accounts 帮助则不会,因此使用时必须非常小心。

图 7. Anchor 中定义的 “Context” 结构体

3. #[account(…)] — 反序列化逻辑和约束

结构体字段的反序列化逻辑由 #[account(…)] 指定,其中 表示一个属性列表,例如 mut、init、owner=…、has_one=…、payer=… 等。

每个属性代表相应账户的一定约束,约束检查会在 try_accounts 中自动添加。例如:

  • mut 添加对 is_writable 的检查
  • init 创建一个账户并初始化它
  • payer=user 将用户账户设置为初始化账户的付款人

图 8. Anchor 生成的 Increment::try_accounts 函数

图 8 显示了 Increment.try_accounts 函数。考虑 # [account(mut, has_one = authority)] 宏在计数器账户上的声明:

对于属性 mut,它生成检查:

对于属性 has_one=authority,它生成检查:

  • has_one=authority:强制约束 Increment.counter.authority == Increment.authority.key

此外,还有一些内置的账户类型,如 Signer(检查 is_signer 是否为账户)和 Programsystem_program)。

此外,Anchor 默认还会为所有标记为 #[account(init)] 的账户生成 租户豁免 检查(除非 r ent_exempt = skip):

Anchor 注意事项

总体而言,通过声明式编程模型,Anchor 使编写 Solana 智能合约比本机 Rust 程序更容易。但是,有几点注意事项:

  1. Anchor 仍在积极开发中,因此某些宏的特性和语义 可能会发生变化
  2. Anchor 尚未经过审计,Anchor 代码生成中的任何缺陷都可能导致潜在的漏洞未被察觉。
  3. #[account(...)] 中的声明性约束 必须特别小心 以确保每个合约指令的充足验证和正确的访问控制。
  4. 在直接使用 ctx.remaining_accounts 时要非常小心。Context 结构体中的其余账户没有被反序列化或验证。

sec3 审计

sec3 是由区块链安全和软件验证领域的领先专家创立的。

我们很高兴为 Solana 上的重要 Dapps 提供审计服务。请访问 sec3.dev 或发送电子邮件至 contact@sec3.dev

之前的文章

如何审核Solana智能合约系列?

有关sec3的所有博客,请访问https://www.sec3.dev/blog

  • 原文链接: sec3.dev/blog/how-to-aud...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Sec3dev
Sec3dev
https://www.sec3.dev/