在分配存储空间时,支付方必须为分配的每个字节支付一定数量的 SOL。
Solana 称之为“租金”(rent)。这个名字有点误导,因为它暗示需要按月充值,但实际情况并非总是如此。一旦支付了租金,即使过了两年也不需要再支付。当预付了两年的租金时,该账户就被视为“免租金”(rent exempt)。
这个名字来源于 Solana 最初按每年每字节对账户收费。如果你只支付了半年的租金,你的账户就会在六个月后被删除。如果你提前支付了两年的租金,账户就会“免租金”。该账户将永远不需要再支付租金。如今,所有账户都必须是免租金的;你不能支付少于 2 年的租金。
尽管租金是按“每字节”计算的,但数据量为零的账户并不是免费的;Solana 仍然需要对它们进行索引并存储相关的元数据。
初始化账户时,所需的租金数量是在后台计算的;你不需要明确地计算租金。
但是,你确实希望能够预估存储成本,以便正确设计你的应用程序。
如果你想要一个快速的估算,在命令行中运行 solana rent <number of bytes> 会给出一个快速的答案:

如前所述,分配零字节并不是免费的:

让我们来看看这个费用是如何计算的。
Anchor Rent Module 为我们提供了一些与租金相关的常量:
ACCOUNT_STORAGE_OVERHEAD:这个常量的值为 128(字节),顾名思义,一个空账户有 128 字节的开销。DEFAULT_EXEMPTION_THRESHOLD:这个常量的值为 2.0(64 位浮点数),它指的是提前支付两年租金使账户免于支付后续租金这一设定。DEFAULT_LAMPORTS_PER_BYTE_YEAR:这个常量的值为 3,480,意味着每个字节每年需要 3,480 个 lamports。因为我们必须预付两年的费用,所以每个字节将花费我们 6,960 个 lamports。
下面的 rust 程序打印出了一个空账户将花费我们多少钱。请注意,结果与上面 solana rent 0 的截图相匹配:
use anchor_lang::prelude::*;
use anchor_lang::solana_program::rent as rent_module;
declare_id!("BfMny1VwizQh89rZtikEVSXbNCVYRmi6ah8kzvze5j1S");
#[program]
pub mod rent {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let cost_of_empty_acc = rent_module::ACCOUNT_STORAGE_OVERHEAD as f64 *
rent_module::DEFAULT_LAMPORTS_PER_BYTE_YEAR as f64 *
rent_module::DEFAULT_EXEMPTION_THRESHOLD;
msg!("cost to create an empty account: {}", cost_of_empty_acc);
// 890880
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
如果我们想计算一个非空账户的花费,那么只需将字节数添加到空账户的成本中,如下所示:
use anchor_lang::prelude::*;
use anchor_lang::solana_program::rent as rent_module;
declare_id!("BfMny1VwizQh89rZtikEVSXbNCVYRmi6ah8kzvze5j1S");
#[program]
pub mod rent {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let cost_of_empty_acc = rent_module::ACCOUNT_STORAGE_OVERHEAD as f64 *
rent_module::DEFAULT_LAMPORTS_PER_BYTE_YEAR as f64 *
rent_module::DEFAULT_EXEMPTION_THRESHOLD;
msg!("cost to create an empty account: {}", cost_of_empty_acc);
// 890,880 lamports
let cost_for_32_bytes = cost_of_empty_acc +
32 as f64 *
rent_module::DEFAULT_LAMPORTS_PER_BYTE_YEAR as f64 *
rent_module::DEFAULT_EXEMPTION_THRESHOLD;
msg!("cost to create a 32 byte account: {}", cost_for_32_bytes);
// 1,113,600 lamports
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
同样,请注意该程序的输出与命令行上的输出相匹配。
将存储成本与 ETH 进行比较
在撰写本文时,ETH 的价值约为 2,425 美元。初始化一个新账户的成本为 22,100 gas,假设 gas 成本为 15 gwei,我们可以计算出 32 字节的 gas 成本为 0.80 美元。
目前,Solana 的价格为 90 美元 / SOL,因此支付 1,113,600 lamports 初始化一个 32 字节的存储将花费 0.10 美元。
然而,ETH 的市值是 SOL 的 7.5 倍,如果 SOL 拥有与 ETH 相同的市值,那么 SOL 当前的价格将是 675 美元,32 字节的存储成本将是 0.75 美元。
Solana 有一个永久的通胀模型,最终将收敛到每年 1.5%,这应该有助于反映存储变得越来越便宜这一事实,这符合摩尔定律(Moore’s Law),该定律指出同等成本下晶体管密度每 18 个月翻一番。
请记住,从字节到加密货币的转换比率是协议中设定的常量,硬分叉(hard fork)随时都可以改变它。
余额低于 2 年免租金阈值的账户将被持续扣减,直至账户被删除
这里有一个相当幽默的 Reddit 帖子,一位用户的钱包账户被慢慢“吸干”:https://www.reddit.com/r/solana/comments/qwin1h/my_sol_balance_in_the_wallet_is_decreasing/
原因是该钱包的余额低于免租金阈值,而 Solana 运行时(runtime)正在慢慢减少账户余额以支付租金。
如果一个钱包因为余额低于免租金阈值而最终被删除,可以通过向其发送更多的 SOL 来“复活”它,但如果该账户中存储了数据,这些数据将会丢失。
大小限制
初始化账户时,我们不能初始化超过 10,240 字节的大小。
练习:创建一个基础的存储初始化程序并设置 space=10241。这比限制高出了 1 个字节。你应该会看到类似以下的错误:

更改账户大小
如果你需要增加账户的大小,我们可以使用 realloc 宏。如果账户正在存储一个向量(vector)并且需要更多空间,这可能会很方便。在 increase_account_size 函数和 IncreaseAccountSize 上下文结构体中有一个示例,它将大小增加了 1,000 字节(请参阅下面代码中的全大写注释):
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("GLKUcCtHx6nkuDLTz5TNFrR4tt4wDNuk24Aid2GrDLC6");
#[program]
pub mod basic_storage {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn increase_account_size(ctx: Context<IncreaseAccountSize>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct IncreaseAccountSize<'info> {
#[account(mut,
// ***** 1,000 BYTE INCREMENT IS OVER HERE *****
realloc = size_of::<MyStorage>() + 8 + 1000,
realloc::payer = signer,
realloc::zero = false,
seeds = [],
bump)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init,
payer = signer,
space=size_of::<MyStorage>() + 8,
seeds = [],
bump)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyStorage {
x: u64,
}
当增加账户大小时,如果你不想擦除账户数据,请确保设置 realloc::zero = false(如上面的代码所示)。如果你希望将账户数据全部置零,请使用 realloc::zero = true。你不需要修改测试代码。该宏将在幕后为你处理这些细节。
练习:在测试中初始化一个账户,然后调用 increase_account_size 函数。在命令行中使用 solana account <addr> 查看账户大小。你需要在本地验证器(local validator)上执行此操作,以便账户得以持久化。
Solana 账户最大大小
每次 realloc 允许的最大账户大小增量为 10240。Solana 中一个账户的最大尺寸可以达到 10 MB。
预估部署程序的成本
部署 Solana 程序的大部分成本来自于为存储字节码(bytecode)支付的租金。字节码被存储在一个不同于 anchor deploy 返回的地址的独立账户中。
下面的截图显示了如何获取此信息:

部署一个简单的 hello world 程序目前大概需要略多于 2.47 SOL。通过编写原生 Rust 代码而不是使用 Anchor 框架,可以显著降低该成本,但在你完全了解 Anchor 默认消除的所有安全风险之前,我们不建议这样做。
通过 RareSkills 了解更多
请参阅我们的 Solana developer course 以了解更多信息。
最初发布于 2024 年 2 月 28 日