在 Solana 上,智能合约被称为程序。程序是一种无状态的 账户,其中包含可执行代码。这些代码被组织为称为指令的函数。用户通过发送包含一个或多个 指令 的 交易 与程序进行交互。一笔交易可以包含来自多个程序的指令。
当程序被部署时,Solana 会使用 LLVM 将其编译为可执行与可链接格式(ELF)。ELF 文件包含以 Solana 字节码格式(sBPF)编译的程序二进制文件,并保存在链上的可执行账户中。
sBPF 是 Solana 定制的 eBPF 字节码版本。
编写程序
大多数程序都是用 Rust 编写的,常见的开发方式有两种:
- Anchor:Anchor 是为 Solana 快速、便捷开发而设计的框架。它利用 Rust 宏 来减少样板代码,非常适合初学者。
- 原生 Rust:直接用 Rust 编写程序,不依赖任何框架。这种方式更灵活,但复杂度也更高。
更新程序
要修改现有程序,必须有一个账户被指定为升级权限账户。(通常是最初部署程序的账户。)如果升级权限被撤销并设置为
None,则该程序将无法再被更新。
验证程序
Solana 支持可验证构建,用户可以检查链上程序代码是否与其公开源代码一致。Anchor 框架提供了内置支持来创建可验证构建。
要检查现有程序是否已验证,请在 Solana Explorer 上搜索其 program ID。或者,你也可以使用 Ellipsis Labs 的 Solana Verifiable Build CLI,独立验证链上程序。
内置程序
The System Program
System Program 是唯一可以创建新账户的账户。默认情况下,所有新账户都归 System Program 所有,尽管许多账户在创建时会被分配给新的所有者。System Program 执行以下关键功能:
| 功能 | 描述 |
|---|---|
| 新账户创建 | 只有 System Program 可以创建新账户。 |
| 空间分配 | 设置每个账户数据字段的字节容量。 |
| 分配程序所有权 | System Program 创建账户后,可以将指定的程序所有者重新分配给其他 program account。自定义程序就是通过这种方式获得 System Program 创建的新账户的所有权。 |
| 转账 SOL | 将 lamports(SOL)从 System Program 账户转移到其他账户。 |
system program 的地址是 11111111111111111111111111111111。
Loader 程序
每个程序都归另一个账户所有——即其 loader。Loader 用于部署、重新部署、升级或关闭程序,也用于完成程序和转移程序权限。
目前有五个 loader 程序,如下表所示。
| Loader | Program ID | 说明 | 指令链接 |
|---|---|---|---|
| native | NativeLoader1111111111111111111111111111111 | 拥有其他四个 loader | — |
| v1 | BPFLoader1111111111111111111111111111111111 | 管理指令已禁用,但程序仍可执行 | — |
| v2 | BPFLoader2111111111111111111111111111111111 | 管理指令已禁用,但程序仍可执行 | 指令 |
| v3 | BPFLoaderUpgradeab1e11111111111111111111111 | 程序部署后可更新。可执行文件存储在单独的 program data account 中 | 指令 |
| v4 | LoaderV411111111111111111111111111111111111 | 开发中(未发布) | 指令 |
使用 loader-v3 或 loader-v4 部署的程序,在部署后可能仍可修改,具体取决于其升级权限。
预编译程序
除了 loader 程序外,Solana 还提供以下预编译程序。
验证 ed25519 签名
ed25519 程序用于验证一个或多个 ed25519 签名。
| 程序 | 程序 ID | 描述 | 指令 |
|---|---|---|---|
| Ed25519 程序 | Ed25519SigVerify111111111111111111111111111 | 验证 ed25519 签名。如果有任何签名验证失败,则返回错误。 | 指令 |
ed25519 程序会处理一个指令。指令的第一个 u8
包含要验证的签名数量,后跟一个单字节填充。之后,以下结构会被序列化,每个待验证签名各有一个。
struct Ed25519SignatureOffsets {signature_offset: u16, // offset to ed25519 signature of 64 bytessignature_instruction_index: u16, // instruction index to find signaturepublic_key_offset: u16, // offset to public key of 32 bytespublic_key_instruction_index: u16, // instruction index to find public keymessage_data_offset: u16, // offset to start of message datamessage_data_size: u16, // size of message datamessage_instruction_index: u16, // index of instruction data to get message data}
process_instruction() {for i in 0..count {// i'th index values referenced:instructions = &transaction.message().instructionsinstruction_index = ed25519_signature_instruction_index != u16::MAX ? ed25519_signature_instruction_index : current_instruction;signature = instructions[instruction_index].data[ed25519_signature_offset..ed25519_signature_offset + 64]instruction_index = ed25519_pubkey_instruction_index != u16::MAX ? ed25519_pubkey_instruction_index : current_instruction;pubkey = instructions[instruction_index].data[ed25519_pubkey_offset..ed25519_pubkey_offset + 32]instruction_index = ed25519_message_instruction_index != u16::MAX ? ed25519_message_instruction_index : current_instruction;message = instructions[instruction_index].data[ed25519_message_data_offset..ed25519_message_data_offset + ed25519_message_data_size]if pubkey.verify(signature, message) != Success {return Error}}return Success}
验证 secp256k1 公钥恢复
secp256k1 程序用于验证 secp256k1 公钥恢复操作。
| 程序 | 程序 ID | 描述 | 指令 |
|---|---|---|---|
| Secp256k1 程序 | KeccakSecp256k11111111111111111111111111111 | 验证 secp256k1 公钥恢复操作(ecrecover)。 | 指令 |
secp256k1 程序会处理一个指令。指令的第一个字节包含要验证的公钥数量。之后,以下结构会为每个公钥创建一次,然后序列化并添加到 instruction data。
struct Secp256k1SignatureOffsets {secp_signature_offset: u16, // offset to [signature,recovery_id] of 64+1 bytessecp_signature_instruction_index: u8, // instruction index to find signaturesecp_pubkey_offset: u16, // offset to ethereum_address pubkey of 20 bytessecp_pubkey_instruction_index: u8, // instruction index to find pubkeysecp_message_data_offset: u16, // offset to start of message datasecp_message_data_size: u16, // size of message datasecp_message_instruction_index: u8, // instruction index to find message data}
process_instruction() {for i in 0..count {// i'th index values referenced:instructions = &transaction.message().instructionssignature = instructions[secp_signature_instruction_index].data[secp_signature_offset..secp_signature_offset + 64]recovery_id = instructions[secp_signature_instruction_index].data[secp_signature_offset + 64]ref_eth_pubkey = instructions[secp_pubkey_instruction_index].data[secp_pubkey_offset..secp_pubkey_offset + 20]message_hash = keccak256(instructions[secp_message_instruction_index].data[secp_message_data_offset..secp_message_data_offset + secp_message_data_size])pubkey = ecrecover(signature, recovery_id, message_hash)eth_pubkey = keccak256(pubkey[1..])[12..]if eth_pubkey != ref_eth_pubkey {return Error}}return Success}
这允许用户在交易中为签名和消息数据指定任意 instruction data。通过指定特殊的 instructions sysvar,还可以从交易本身接收数据。
交易成本将按需验证的签名数量乘以签名验证成本系数计算。
secp256r1 程序用于验证最多 8 个 secp256r1 签名。
| 程序 | 程序 ID | 描述 | 指令 |
|---|---|---|---|
| Secp256r1 程序 | Secp256r1SigVerify1111111111111111111111111 | 验证最多 8 个 secp256r1 签名。接收签名、公钥和消息。如果有任何验证失败,则返回错误。 | 指令 |
secp256r1 程序会处理一个指令。指令的第一个 u8
是待验证签名的数量,后跟一个单字节填充。之后,为每个签名创建以下结构体,然后序列化并添加到 instruction
data。
struct Secp256r1SignatureOffsets {signature_offset: u16, // offset to compact secp256r1 signature of 64 bytessignature_instruction_index: u16, // instruction index to find signaturepublic_key_offset: u16, // offset to compressed public key of 33 bytespublic_key_instruction_index: u16, // instruction index to find public keymessage_data_offset: u16, // offset to start of message datamessage_data_size: u16, // size of message datamessage_instruction_index: u16, // index of instruction data to get message data}
process_instruction() {if data.len() < SIGNATURE_OFFSETS_START {return Error}num_signatures = data[0] as usizeif num_signatures == 0 || num_signatures > 8 {return Error}expected_data_size = num_signatures * SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_STARTif data.len() < expected_data_size {return Error}for i in 0..num_signatures {offsets = parse_signature_offsets(data, i)signature = get_data_slice(data, instruction_datas, offsets.signature_instruction_index, offsets.signature_offset, SIGNATURE_SERIALIZED_SIZE)if s > half_curve_order {return Error}pubkey = get_data_slice(data, instruction_datas, offsets.public_key_instruction_index, offsets.public_key_offset, COMPRESSED_PUBKEY_SERIALIZED_SIZE)message = get_data_slice(data, instruction_datas, offsets.message_instruction_index, offsets.message_data_offset, offsets.message_data_size)if !verify_signature(signature, pubkey, message) {return Error}}return Success}
核心程序
下表中的程序为网络提供核心功能。
| 程序 | 程序 ID | 描述 | 指令 |
|---|---|---|---|
| System | 11111111111111111111111111111111 | 创建新账户、分配账户数据、将账户分配给所属程序、从 System Program 拥有的账户转移 lamports,并支付交易费用 | SystemInstruction |
| Vote | Vote111111111111111111111111111111111111111 | 创建和管理跟踪 validator 投票状态和奖励的账户 | VoteInstruction |
| Stake | Stake11111111111111111111111111111111111111 | 创建和管理代表委托给 validator 的质押和奖励的账户 | StakeInstruction |
| Config | Config1111111111111111111111111111111111111 | 向链中添加配置信息,并指定允许修改该配置的公钥列表。与其他程序不同,Config 程序没有定义任何单独的指令。它只有一个隐式指令:“store”。其 instruction data 是一组控制账户访问权限和存储数据的密钥 | ConfigInstruction |
| Compute Budget | ComputeBudget111111111111111111111111111111 | 设置交易的计算单元限制和价格,允许用户控制计算资源和优先级费用 | ComputeBudgetInstruction |
| Address Lookup Table | AddressLookupTab1e1111111111111111111111111 | 管理地址查找表,使交易能够引用比账户列表中可容纳的更多账户 | ProgramInstruction |
| ZK ElGamal Proof | ZkE1Gama1Proof11111111111111111111111111111 | 为 ElGamal 加密数据提供零知识证明验证 | — |
Is this page helpful?