Solana 程序通过 Anchor 部署无需构造函数,默认可升级且可转为不可变,简化了状态管理与升级流程。
当你运行 anchor init deploy_tutorial 时,Anchor 会生成一个默认的测试文件:
describe("deploy_tutorial", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.DeployTutorial as Program<DeployTutorial>;
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods.initialize().rpc();
console.log("Your transaction signature", tx);
});
});
对应的程序代码如下:
use anchor_lang::prelude::*;
declare_id!("A5Y9992TvbUD8Q7bmaCY9j9wztwgcFweUBV5y1JXEyjo");
#[program]
pub mod deploy_tutorial {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("Greetings from: {:?}", ctx.program_id);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
那么,这个程序究竟在何时何地被部署呢?
乍看之下,测试文件中的这行代码似乎与部署有关:
const program = anchor.workspace.DeployTutorial as Program<DeployTutorial>;
但这显然不是部署的实际位置,因为它只是定义了一个程序实例,而非异步部署操作。实际上,Anchor 在后台默默完成了程序的部署工作,通常在运行 anchor test 时自动执行。
对于习惯 Solidity 的开发者来说,Solana 程序没有构造函数可能显得反常。Rust 本身没有传统意义上的类或对象,因此 Solana 程序的部署逻辑也与以太坊智能合约截然不同。
在以太坊中,构造函数用于初始化存储、设置字节码或定义不可变变量。而在 Solana 中,“部署步骤”又是如何实现的呢?
让我们通过一个实验来验证部署过程。创建一个名为 deploy 的新 Anchor 项目,并确保在后台运行 solana-test-validator 和 solana logs。
运行 anchor build 编译程序后,跳过 anchor test,直接在终端执行:
anchor deploy
输出日志如下:
Transaction executed in slot 465:
Signature: 5SNBgNimNvRQRanTXrriHoWsmXmu1NFtXLqGU4KLRGuAFeQApvbZPbGCifAffbkCpB7V94hh3gvHrkYy5ozMFUtX
Status: Ok
Log Messages:
Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
Program BPFLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Deployed program A5Y9992TvbUD8Q7bmaCY9j9wztwgcFweUBV5y1JXEyjo
Program BPFLoaderUpgradeab1e11111111111111111111111 success
日志显示,程序已被成功部署,程序 ID 为 A5Y9992TvbUD8Q7bmaCY9j9wztwgcFweUBV5y1JXEyjo。
有趣的是,再次运行 anchor deploy,会得到以下结果:
Transaction executed in slot 657:
Signature: 2Yik43HpT6h4tZ9zDmhtsqqrVFZsbSaU7se6MpsCPJ32xzg1E5V8WuzRgQc5LNJS7hyNo5C9xSeZo7Q2Jqzrh2oA
Status: Ok
Log Messages:
Program BPFLoaderUpgradeab1e11111111111111111111111 invoke [1]
Upgraded program A5Y9992TvbUD8Q7bmaCY9j9wztwgcFweUBV5y1JXEyjo
Program BPFLoaderUpgradeab1e11111111111111111111111 success
这次日志显示程序被“升级”(Upgraded),而非重新部署。程序 ID 未变,表明 Solana 的程序字节码被直接覆盖。
这对于以太坊开发者来说可能有些奇怪,因为以太坊合约默认不可变,而 Solana 程序默认是可升级的。程序作者可以随时更新字节码,这引发一个问题:如果程序可以随意更改,其可信度如何保证?
Solana 提供了一种解决方案:程序可以选择部署为不可变版本。开发者通常会先部署可变程序进行测试,待确认无误后,再将其升级为不可变状态。这种模式类似于以太坊中代理合约的管理员权限最终转移至零地址,但 Solana 的实现更为简洁,避免了代理模式中常见的复杂性。
在 Solidity 中,delegatecall 常用于通过代理调用新实现合约来升级功能。然而,Solana 的可升级字节码设计消除了对 delegatecall 的需求——开发者只需直接更新程序即可。
另一个推论是,Solana 没有类似 Solidity 的不可变变量(即在构造函数中设置且不可更改的变量)。由于没有构造函数,程序的状态初始化通常通过普通指令完成。
Anchor 默认在运行 anchor test 时重新部署程序。但我们可以通过参数跳过部署,测试已有程序。清除日志并重启 solana-test-validator 后,运行:
anchor test --skip-local-validator --skip-deploy
日志输出:
Signature: 2go5NbbWMFAbXNvTiNRn5o5x9h7mBWSa649p7ZJDs2PotqhZMyEMMyDKD86QNnjEFvjCHq72R3giGXATDLX1Tiav
Status: Ok
Log Messages:
Program A5Y9992TvbUD8Q7bmaCY9j9wztwgcFweUBV5y1JXEyjo invoke [1]
Program log: Instruction: Initialize
Program log: Greetings from: A5Y9992TvbUD8Q7bmaCY9j9wztwgcFweUBV5y1JXEyjo
Program A5Y9992TvbUD8Q7bmaCY9j9wztwgcFweUBV5y1JXEyjo consumed 11880 of 200000 compute units
Program A5Y9992TvbUD8Q7bmaCY9j9wztwgcFweUBV5y1JXEyjo success
可以看到,initialize 指令成功执行,但程序未被部署或升级,验证了 --skip-deploy 的效果。
【笔记配套代码】 https://github.com/0xE1337/rareskills_evm_to_solana 【参考资料】 https://learnblockchain.cn/column/119 https://www.rareskills.io/solana-tutorial
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!