本文对 Rust 中 Anchor 智能合约的账户大小计算进行了比较,分析了使用 _std::mem::sizeof 与 Anchor 官方空间参考之间的异同。尽管 _std::mem::sizeof 在大多数情况下准确,但在处理某些数据类型,如 Vec 和 Enum 时可能导致不一致的结果,因此建议开发人员根据 Anchor 文档手动计算账户大小。
使用 Anchor 的智能合约要求开发者为新账户分配空间并指定账户大小。Anchor 提供了基于账户结构计算大小的指导,但许多开发者选择使用 std::mem::size_of,因为在更改账户结构时,他们不必手动更新大小。它们是否等价?在本文中,我们对 std::mem::size_of 产生的结果与 Anchor 空间参考 进行了系统比较。
虽然 std::mem::size_of 通常工作良好,并分配的空间甚至超出所需,但在某些情况下它可能返回一个较小的大小。因此,开发者在选择适当的方法之前应理解这两种方法之间的差异。
在 Anchor 中,开发者普遍使用 std::mem::size_of<T>() 来确定 Account 结构的大小。然而,该函数产生的大小与经过 BorshSerialize 序列化后存储在 Account 中的数据大小可能并不完全匹配。
下表总结了使用两种方法计算不同数据类型的大小。虽然大多数类型给出的结果相同,但某些数据类型,如 Vector 和 Enum 可能会出现差异,此时 std::mem::size_of<T>() 可能返回一个较小的值。
注意:本文中的所有实验都在 64 位机器上进行。
类型 | std::mem::size_of<T>() | Anchor 空间参考 |
---|---|---|
bool | 1 | 1 |
u8/i8 | 1 | 1 |
u16/i16 | 2 | 2 |
u32/i32 | 4 | 4 |
u64/i64 | 8 | 8 |
u128/i128 | 16 | 16 |
[T;amount] | space(T) * amount | space(T) * amount |
Pubkey | 32 | 32 |
Vec<T> | 24 | 4 + space(T) * amount |
String | 24 | 4 + string length |
Option<T> | pad(space(T)),当 T 是 String、Vec<...>、Box<...><br>pad(1 + space(T)),否则 | 1 + space(T) |
Enum | 0,零变体枚举<br>space(variant),单变体枚举<br>pad(space(max discriminant) + space(max variant)),否则 | 1 + largest variant size |
Rust 中的 size_of 函数返回固定值,基于类型的大小信息,可能无法准确计算某些对象占用的内存。
指针 容量 长度
+----------+----------+----------+
| 0x0123 | 4 | 2 |
+----------+----------+----------+
|
v
堆 +--------+--------+--------+--------+
| 'a' | 'b' | uninit | uninit |
+--------+--------+--------+--------+
pub struct Example {
pub example: Vec<T>,
}
当计算包含类型为 T 的向量的 Example 账户的大小时,一些开发者可能使用公式 std::mem::size_of::<Example>() + (std::mem::size_of::<T>() * example.len())。
这种方法是有效的,甚至可能导致账户大小超过原来的 20 字节,因为经过 BorshSerialize 序列化后,向量的内容变为长度(4 字节)加上实际内容的大小。
请注意,在计算总账户大小时,需要考虑存储向量长度所需的额外字节。
Option<T> 有两个变体:None 或 Some。在内存中,None 变体不存储任何值,仅存储一个“标签” 0,而 Some 变体存储值以及 “标签” 1。
然而,当 T 是 Box 或其他智能指针类型时,不需要 0/1 标签。由于 Rust 中的智能指针不能为 0,因此 None 可以用 0 表示,而 Some 则可以通过指针本身的值直接表示。
因此,当使用 size_of 函数计算 Option<T> 的大小时,结果将取决于类型 T:
相比之下,经过序列化后,实际占用的空间为 1 + space(T),因为 Option 使用 1 字节来表示 Some 或 None。
println!("u8 = {}", std::mem::size_of::<u8>()); // 1
println!("Option<u8> = {}", std::mem::size_of::<Option<u8>>()); // 2
println!("u16 = {}", std::mem::size_of::<u16>()); // 2
println!("Option<u16> = {}", std::mem::size_of::<Option<u16>>()); // 4
println!("u32 = {}", std::mem::size_of::<u32>()); // 4
println!("Option<u32> = {}", std::mem::size_of::<Option<u32>>()); // 8
println!("u64 = {}", std::mem::size_of::<u64>()); // 8
println!("Option<u64> = {}", std::mem::size_of::<Option<u64>>()); // 16
println!("u128 = {}", std::mem::size_of::<u128>()); // 16
println!("Option<u128> = {}", std::mem::size_of::<Option<u128>>()); // 24
println!("Box<u8> = {}", std::mem::size_of::<Box<u8>>()); // 8
println!("Option<Box<u8>> = {}", std::mem::size_of::<Option<Box<u8>>>()); // 8
println!("Box<u16> = {}", std::mem::size_of::<Box<u16>>()); // 8
println!("Option<Box<u16>> = {}", std::mem::size_of::<Option<Box<u16>>>()); // 8
println!("Box<u32> = {}", std::mem::size_of::<Box<u32>>()); // 8
println!("Option<Box<u32>> = {}", std::mem::size_of::<Option<Box<u32>>>()); // 8
println!("Box<u64> = {}", std::mem::size_of::<Box<u64>>()); // 8
println!("Option<Box<u64>> = {}", std::mem::size_of::<Option<Box<u64>>>()); // 8
println!("Box<u128> = {}", std::mem::size_of::<Box<u128>>()); // 8
println!("Option<Box<u128>> = {}", std::mem::size_of::<Option<Box<u128>>>()); // 8
println!("Vec<u8> = {}", std::mem::size_of::<Vec<u8>>()); // 24
println!("Option<Vec<u8>> = {}", std::mem::size_of::<Option<Vec<u8>>>()); // 24
println!("Vec<u16> = {}", std::mem::size_of::<Vec<u16>>()); // 24
println!("Option<Vec<u16>> = {}", std::mem::size_of::<Option<Vec<u16>>>()); // 24
println!("Vec<u32> = {}", std::mem::size_of::<Vec<u32>>()); // 24
println!("Option<Vec<u32>> = {}", std::mem::size_of::<Option<Vec<u32>>>()); // 24
println!("Vec<u64> = {}", std::mem::size_of::<Vec<u64>>()); // 24
println!("Option<Vec<u64>> = {}", std::mem::size_of::<Option<Vec<u64>>>()); // 24
println!("Vec<u128> = {}", std::mem::size_of::<Vec<u128>>()); // 24
println!("Option<Vec<u128>> = {}", std::mem::size_of::<Option<Vec<u128>>>()); // 24
println!("Vec<String> = {}", std::mem::size_of::<Vec<String>>()); // 24
println!("Option<Stirng> = {}", std::mem::size_of::<Option<String>>()); // 24
通常,当对一个枚举应用 size_of 函数时,结果等于需要的判别字段大小加上最大变体的大小,同时考虑到合适的内存对齐填充。
// 零变体枚举
pub enum Foo {}
// 单变体枚举,变体大小 = 0
pub enum Bar {
A(())
}
// 单变体枚举,变体大小 = 8
pub enum Baz {
A(u64)
}
// 最大判别 = 1 (u8,1 字节) + 最大变体大小 = 16
pub enum Qux {
A(u8),
B(u128)
}
// 判别 (u16,2 字节) + 最大变体大小 = 4
#[repr(u16)]
pub enum Fred {
A(bool),
B(u32) = 200,
C(u8) = 404
}
// 判别 (u64,8 字节) + 最大变体大小 = 4
#[repr(u64)]
pub enum Thud {
A(bool),
B(u32) = 200,
C(u8) = 404
}
fn main() {
println!("Foo : {}", std::mem::size_of::<Foo>()); // 0
println!("Bar : {}", std::mem::size_of::<Bar>()); // 0
println!("Baz : {}", std::mem::size_of::<Baz>()); // 8
println!("Qux : {}", std::mem::size_of::<Qux>()); // 24
println!("Fred: {}", std::mem::size_of::<Fred>()); // 8
println!("Thud: {}", std::mem::size_of::<Thud>()); // 16
}
在 Rust 中,size_of 函数返回固定值,基于给定类型的大小信息,但可能不会准确计算对象占用的内存。
因此,在计算账户大小时,重要的是要注意使用 size_of 函数可能导致计算出的大小与实际账户大小之间的不一致。为了确保准确性,建议根据 官方 Anchor 文档 的相应值手动计算账户大小。
sec3 是一家安全研究公司,旨在为数百万用户准备 Web3 项目。启动审计是一种严格的、研究人员主导的代码审查,调查并认证主网级的智能合约;sec3 的持续审计软件平台 X-ray 与 Github 集成,逐步扫描拉取请求,帮助项目在部署之前加固代码;sec3 的后部署安全解决方案 WatchTower 确保资金安全。sec3 正在为 Web3 项目构建基于技术的可扩展解决方案,以确保协议在扩展时保持安全。
要了解有关 sec3 的更多信息,请访问 https://www.sec3.dev
- 原文链接: sec3.dev/blog/all-about-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!