本文深入探讨了Solana中Anchor框架的内部机制,介绍了其编程模型、代码生成和常用宏的工作原理,以及在智能合约审核过程中需要注意的陷阱。通过实例,详细阐述了Anchor如何简化智能合约的开发,但同时指出在使用Anchor时可能出现的潜在问题。
继第 3 部分:渗透测试 后,本文介绍了Anchor的内部实现,这是一种流行的用于编写和测试 Solana 智能合约的框架。
与以太坊的Truffle类似,Anchor 提供了一系列用于在 Solana 上开发完整应用程序的功能,特别是:
我们将从审计师的角度详细探讨 Anchor 的内部原理 和 一些注意事项。
Anchor 提供了 声明式编程模型,用户可以使用 宏 注解方法或数据类型(结构体及其字段),这些宏会 自动生成代码包装器,以便在 Solana 上执行。
生成的代码包装器可以完成多种操作,例如解码输入数据、创建和初始化账户,更重要的是,确保输入数据的附加约束,例如,确保输入账户是签名者以及多个输入账户之间的关系。
有三种常用的 Anchor 宏:
接下来,我们将使用一个示例(当中的 basic-2)详细说明上述三种宏及其生成的代码。
还有许多其他的 Anchor 宏,例如 ` #[state] ` (状态方法)、 ` #[interface] ` (接口方法)等。可以在Anchor CHANGELOG中找到它们的列表。
图 1. #[program] 宏在 basic-2 中的应用
在没有 Anchor 的标准 Solana 程序中,将会有一个入口点(由 entrypoint! 宏定义),入口点需要三个参数:program_id、accounts 和 instruction_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:create 和 basic_2:increment),执行账户的反序列化、构建上下文、调用用户代码,最后执行退出例程,这通常会持久化账户更改。
图 6. #[derive(Accounts)] 和 #[account(…)] 宏在 basic-2 中的应用
对于每个标记为 #[derive(Accounts)] 的结构体 T,Anchor 将生成一个相应的函数 T::try_accounts,用于反序列化输入账户并添加验证检查。
例如,在图 6 中,#[derive(Accounts)] 在结构体 Create 和 Increment 的顶部声明。这告诉 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” 结构体
结构体字段的反序列化逻辑由 #[account(…)] 指定,其中 … 表示一个属性列表,例如 mut、init、owner=…、has_one=…、payer=… 等。
每个属性代表相应账户的一定约束,约束检查会在 try_accounts 中自动添加。例如:
图 8. Anchor 生成的 Increment::try_accounts 函数
图 8 显示了 Increment.try_accounts 函数。考虑 # [account(mut, has_one = authority)] 宏在计数器账户上的声明:
对于属性 mut,它生成检查:
对于属性 has_one=authority,它生成检查:
此外,还有一些内置的账户类型,如 Signer(检查 is_signer 是否为账户)和 Program(system_program)。
此外,Anchor 默认还会为所有标记为 #[account(init)] 的账户生成 租户豁免 检查(除非 r ent_exempt = skip):
总体而言,通过声明式编程模型,Anchor 使编写 Solana 智能合约比本机 Rust 程序更容易。但是,有几点注意事项:
sec3 是由区块链安全和软件验证领域的领先专家创立的。
我们很高兴为 Solana 上的重要 Dapps 提供审计服务。请访问 sec3.dev 或发送电子邮件至 contact@sec3.dev
如何审核Solana智能合约系列?
有关sec3的所有博客,请访问https://www.sec3.dev/blog
- 原文链接: sec3.dev/blog/how-to-aud...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!