Solana 文档开发程序

使用 Rust 开发程序

Solana 程序主要使用 Rust 编程语言开发。本页面重点介绍如何在不使用 Anchor 框架的情况下用 Rust 编写 Solana 程序,这种方法通常被称为编写“原生 Rust”程序。

原生 Rust 开发为开发者提供了对 Solana 程序的直接控制。然而,与使用 Anchor 框架相比,这种方法需要更多的手动设置和样板代码。此方法推荐给以下开发者:

  • 需要对程序逻辑和优化进行精细控制
  • 希望在使用高级框架之前学习底层概念

对于初学者,我们建议从 Anchor 框架开始。有关更多信息,请参阅 Anchor 部分。

前置条件

有关详细的安装说明,请访问 安装 页面。

在开始之前,请确保已安装以下内容:

  • Rust:用于构建 Solana 程序的编程语言。
  • Solana CLI:用于 Solana 开发的命令行工具。

入门

以下示例涵盖了用 Rust 编写第一个 Solana 程序的基本步骤。我们将创建一个最小化的程序,在程序日志中打印 "Hello, world!"。

创建一个新程序

首先,使用标准 cargo new 命令和 --lib 标志创建一个新的 Rust 项目。

Terminal
$
cargo new hello_world --lib

导航到项目目录。您应该会看到默认的 src/lib.rsCargo.toml 文件。

Terminal
$
cd hello_world

更新 edition 字段到 2021,否则在构建程序时可能会遇到错误。

添加 solana-program 依赖

接下来,添加 solana-program 依赖。这是构建 Solana 程序所需的最低依赖项。

Terminal
$
cargo add solana-program@2.2.0

添加 crate-type

接下来,将以下代码片段添加到 Cargo.toml

Cargo.toml
[lib]
crate-type = ["cdylib", "lib"]

如果您未包含此配置,在构建程序时将不会生成 target/deploy 目录。

您的 Cargo.toml 文件应如下所示:

Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
solana-program = "2.2.0"

添加程序代码

接下来,将 src/lib.rs 的内容替换为以下代码。这是一个最小的 Solana 程序,当程序被调用时,它会在程序日志中打印 "Hello, world!"。

在 Solana 程序中,msg! 宏用于向程序日志打印消息。

src/lib.rs
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,
};
entrypoint!(process_instruction);
pub fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
msg!("Hello, world!");
Ok(())
}

构建程序

接下来,使用 cargo build-sbf 命令构建程序。

Terminal
$
cargo build-sbf

此命令会生成一个 target/deploy 目录,其中包含两个重要文件:

  1. 一个 .so 文件(例如,hello_world.so):这是编译后的 Solana 程序,将作为“智能合约”部署到网络。
  2. 一个 keypair 文件(例如,hello_world-keypair.json):在部署程序时,该 keypair 的公钥用作程序 ID。

要查看程序 ID,请在终端中运行以下命令。此命令会打印指定文件路径中 keypair 的公钥:

Terminal
$
solana address -k ./target/deploy/hello_world-keypair.json

示例输出:

4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz

添加测试依赖项

接下来,使用 litesvm crate 测试程序。将以下依赖项添加到 Cargo.toml

Terminal
$
cargo add litesvm --dev
$
cargo add solana-sdk@2.2.0 --dev

测试程序

将以下测试添加到 src/lib.rs,放在程序代码的下方。这是一个调用 Hello World 程序的测试模块。

src/lib.rs
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,
};
entrypoint!(process_instruction);
pub fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
msg!("Hello, world!");
Ok(())
}
#[cfg(test)]
mod test {
use litesvm::LiteSVM;
use solana_sdk::{
instruction::Instruction,
message::Message,
signature::{Keypair, Signer},
transaction::Transaction,
};
#[test]
fn test_hello_world() {
// Create a new LiteSVM instance
let mut svm = LiteSVM::new();
// Create a keypair for the transaction payer
let payer = Keypair::new();
// Airdrop some lamports to the payer
svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
// Load our program
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
svm.add_program_from_file(program_id, "target/deploy/hello_world.so")
.unwrap();
// Create instruction with no accounts and no data
let instruction = Instruction {
program_id,
accounts: vec![],
data: vec![],
};
// Create transaction
let message = Message::new(&[instruction], Some(&payer.pubkey()));
let transaction = Transaction::new(&[&payer], message, svm.latest_blockhash());
// Send transaction and verify it succeeds
let result = svm.send_transaction(transaction);
assert!(result.is_ok(), "Transaction should succeed");
let logs = result.unwrap().logs;
println!("Logs: {logs:#?}");
}
}

使用 cargo test 命令运行测试。程序日志将显示 "Hello, world!"。

Terminal
$
cargo test -- --no-capture

示例输出:

Terminal
running 1 test
Logs: [
"Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk invoke [1]",
"Program log: Hello, world!",
"Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk consumed 211 of 200000 compute units",
"Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk success",
]
test test::test_hello_world ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s

部署程序

接下来,部署程序。在本地开发时,我们可以使用 solana-test-validator

首先,配置 Solana CLI 以使用本地 Solana 集群。

Terminal
$
solana config set -ul

示例输出:

Config File: /.config/solana/cli/config.yml
RPC URL: http://localhost:8899
WebSocket URL: ws://localhost:8900/ (computed)
Keypair Path: /.config/solana/id.json
Commitment: confirmed

打开一个新终端并运行 solana-test-validators 命令以启动本地 validator。

Terminal
$
solana-test-validator

在测试 validator 运行时,在另一个终端中运行 solana program deploy 命令,将程序部署到本地 validator。

Terminal
$
solana program deploy ./target/deploy/hello_world.so

示例输出:

Program Id: 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz
Signature:
5osMiNMiDZGM7L1e2tPHxU8wdB8gwG8fDnXLg5G7SbhwFz4dHshYgAijk4wSQL5cXiu8z1MMou5kLadAQuHp7ybH

您可以在 Solana Explorer 上查看程序 ID 和交易签名。

请注意,Solana Explorer 上的集群也必须是 localhost。Solana Explorer 上的“Custom RPC URL”选项默认为 http://localhost:8899

创建示例客户端

接下来,我们将演示如何使用 Rust 客户端调用程序。

首先创建一个 examples 目录和一个 client.rs 文件。

Terminal
$
mkdir -p examples && touch examples/client.rs

将以下内容添加到 Cargo.toml

Cargo.toml
[[example]]
name = "client"
path = "examples/client.rs"

添加 solana-clienttokio 依赖项。

Terminal
$
cargo add solana-client@2.2.0 --dev
$
cargo add tokio --dev

添加客户端

将以下代码添加到 examples/client.rs。这是一个 Rust 客户端脚本,它为一个新的 keypair 提供资金以支付交易费用,然后调用 Hello World 程序。

examples/client.rs
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
instruction::Instruction,
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
};
use std::str::FromStr;
#[tokio::main]
async fn main() {
// Program ID (replace with your actual program ID)
let program_id = Pubkey::from_str("4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz").unwrap();
// Connect to the Solana devnet
let rpc_url = String::from("http://localhost:8899");
let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
// Generate a new keypair for the payer
let payer = Keypair::new();
// Request airdrop
let airdrop_amount = 1_000_000_000; // 1 SOL
let signature = client
.request_airdrop(&payer.pubkey(), airdrop_amount)
.expect("Failed to request airdrop");
// Wait for airdrop confirmation
loop {
let confirmed = client.confirm_transaction(&signature).unwrap();
if confirmed {
break;
}
}
// Create the instruction
let instruction = Instruction::new_with_borsh(
program_id,
&(), // Empty instruction data
vec![], // No accounts needed
);
// Add the instruction to new transaction
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
transaction.sign(&[&payer], client.get_latest_blockhash().unwrap());
// Send and confirm the transaction
match client.send_and_confirm_transaction(&transaction) {
Ok(signature) => println!("Transaction Signature: {}", signature),
Err(err) => eprintln!("Error sending transaction: {}", err),
}
}

替换程序 ID

在运行客户端代码之前,将代码片段中的程序 ID 替换为您的程序的 ID。

您可以通过运行以下命令获取您的程序 ID。

Terminal
$
solana address -k ./target/deploy/hello_world-keypair.json

调用程序

使用以下命令运行客户端脚本。

Terminal
$
cargo run --example client

示例输出:

Transaction Signature: 54TWxKi3Jsi3UTeZbhLGUFX6JQH7TspRJjRRFZ8NFnwG5BXM9udxiX77bAACjKAS9fGnVeEazrXL4SfKrW7xZFYV

您可以在 Solana Explorer(本地集群)上检查交易签名,以在程序日志中查看 "Hello, world!"。

更新程序

Solana 程序可以通过重新部署到相同的程序 ID 来更新。在 src/lib.rs 中更新程序,将 "Hello, world!" 修改为 "Hello, Solana!"。

lib.rs
pub fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
-
msg!("Hello, world!");
+
msg!("Hello, Solana!");
Ok(())
}

运行 cargo build-sbf 命令以生成更新的 .so 文件。

Terminal
$
cargo build-sbf

通过运行 cargo test 命令测试更新后的程序。

Terminal
$
cargo test -- --no-capture

您应该在程序日志中看到 "Hello, Solana!"。

Terminal
running 1 test
Logs: [
"Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV invoke [1]",
"Program log: Hello, Solana!",
"Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV consumed 211 of 200000 compute units",
"Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV success",
]
test test::test_hello_world ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s

重新部署程序

使用相同的 solana program deploy 命令重新部署程序。

Terminal
$
solana program deploy ./target/deploy/hello_world.so

再次运行客户端代码,并在 Solana Explorer 上检查交易签名,以在程序日志中查看 "Hello, Solana!"。

Terminal
$
cargo run --example client

关闭程序

您可以关闭您的 Solana 程序以回收分配给账户的 SOL。关闭程序是不可逆的,因此应谨慎操作。

要关闭程序,请使用 solana program close <PROGRAM_ID> 命令。例如:

Terminal
$
solana program close 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz --bypass-warning

示例输出:

Closed Program Id 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz, 0.1350588 SOL
reclaimed

请注意,一旦程序被关闭,其程序 ID 将无法再次使用。尝试使用已关闭的程序 ID 部署程序将导致错误。

Error: Program 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz has been closed, use
a new Program Id

重新部署已关闭的程序

如果在关闭程序后需要使用相同的源代码重新部署程序,您必须生成一个新的程序 ID。要为程序生成一个新的 keypair,请运行以下命令:

Terminal
$
solana-keygen new -o ./target/deploy/hello_world-keypair.json --force

或者,您可以删除现有的 keypair 文件(例如 ./target/deploy/hello_world-keypair.json),然后再次运行 cargo build-sbf, 这将生成一个新的 keypair 文件。

创建一个新程序

首先,使用标准 cargo new 命令和 --lib 标志创建一个新的 Rust 项目。

Terminal
$
cargo new hello_world --lib

导航到项目目录。您应该会看到默认的 src/lib.rsCargo.toml 文件。

Terminal
$
cd hello_world

更新 edition 字段到 2021,否则在构建程序时可能会遇到错误。

添加 solana-program 依赖

接下来,添加 solana-program 依赖。这是构建 Solana 程序所需的最低依赖项。

Terminal
$
cargo add solana-program@2.2.0

添加 crate-type

接下来,将以下代码片段添加到 Cargo.toml

Cargo.toml
[lib]
crate-type = ["cdylib", "lib"]

如果您未包含此配置,在构建程序时将不会生成 target/deploy 目录。

添加程序代码

接下来,将 src/lib.rs 的内容替换为以下代码。这是一个最小的 Solana 程序,当程序被调用时,它会在程序日志中打印 "Hello, world!"。

在 Solana 程序中,msg! 宏用于向程序日志打印消息。

构建程序

接下来,使用 cargo build-sbf 命令构建程序。

Terminal
$
cargo build-sbf

此命令会生成一个 target/deploy 目录,其中包含两个重要文件:

  1. 一个 .so 文件(例如,hello_world.so):这是编译后的 Solana 程序,将作为“智能合约”部署到网络。
  2. 一个 keypair 文件(例如,hello_world-keypair.json):在部署程序时,该 keypair 的公钥用作程序 ID。

要查看程序 ID,请在终端中运行以下命令。此命令会打印指定文件路径中 keypair 的公钥:

Terminal
$
solana address -k ./target/deploy/hello_world-keypair.json

示例输出:

4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz

添加测试依赖项

接下来,使用 litesvm crate 测试程序。将以下依赖项添加到 Cargo.toml

Terminal
$
cargo add litesvm --dev
$
cargo add solana-sdk@2.2.0 --dev

测试程序

将以下测试添加到 src/lib.rs,放在程序代码的下方。这是一个调用 Hello World 程序的测试模块。

使用 cargo test 命令运行测试。程序日志将显示 "Hello, world!"。

Terminal
$
cargo test -- --no-capture

示例输出:

Terminal
running 1 test
Logs: [
"Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk invoke [1]",
"Program log: Hello, world!",
"Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk consumed 211 of 200000 compute units",
"Program 9528phXNvdWp5kkR4rgpoeZvR8ZWT5THVywK95YRprkk success",
]
test test::test_hello_world ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s

部署程序

接下来,部署程序。在本地开发时,我们可以使用 solana-test-validator

首先,配置 Solana CLI 以使用本地 Solana 集群。

Terminal
$
solana config set -ul

示例输出:

Config File: /.config/solana/cli/config.yml
RPC URL: http://localhost:8899
WebSocket URL: ws://localhost:8900/ (computed)
Keypair Path: /.config/solana/id.json
Commitment: confirmed

打开一个新终端并运行 solana-test-validators 命令以启动本地 validator。

Terminal
$
solana-test-validator

在测试 validator 运行时,在另一个终端中运行 solana program deploy 命令,将程序部署到本地 validator。

Terminal
$
solana program deploy ./target/deploy/hello_world.so

示例输出:

Program Id: 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz
Signature:
5osMiNMiDZGM7L1e2tPHxU8wdB8gwG8fDnXLg5G7SbhwFz4dHshYgAijk4wSQL5cXiu8z1MMou5kLadAQuHp7ybH

您可以在 Solana Explorer 上查看程序 ID 和交易签名。

请注意,Solana Explorer 上的集群也必须是 localhost。Solana Explorer 上的“Custom RPC URL”选项默认为 http://localhost:8899

创建示例客户端

接下来,我们将演示如何使用 Rust 客户端调用程序。

首先创建一个 examples 目录和一个 client.rs 文件。

Terminal
$
mkdir -p examples && touch examples/client.rs

将以下内容添加到 Cargo.toml

Cargo.toml
[[example]]
name = "client"
path = "examples/client.rs"

添加 solana-clienttokio 依赖项。

Terminal
$
cargo add solana-client@2.2.0 --dev
$
cargo add tokio --dev

添加客户端

将以下代码添加到 examples/client.rs。这是一个 Rust 客户端脚本,它为一个新的 keypair 提供资金以支付交易费用,然后调用 Hello World 程序。

替换程序 ID

在运行客户端代码之前,将代码片段中的程序 ID 替换为您的程序的 ID。

您可以通过运行以下命令获取您的程序 ID。

Terminal
$
solana address -k ./target/deploy/hello_world-keypair.json

调用程序

使用以下命令运行客户端脚本。

Terminal
$
cargo run --example client

示例输出:

Transaction Signature: 54TWxKi3Jsi3UTeZbhLGUFX6JQH7TspRJjRRFZ8NFnwG5BXM9udxiX77bAACjKAS9fGnVeEazrXL4SfKrW7xZFYV

您可以在 Solana Explorer(本地集群)上检查交易签名,以在程序日志中查看 "Hello, world!"。

更新程序

Solana 程序可以通过重新部署到相同的程序 ID 来更新。在 src/lib.rs 中更新程序,将 "Hello, world!" 修改为 "Hello, Solana!"。

lib.rs
pub fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
-
msg!("Hello, world!");
+
msg!("Hello, Solana!");
Ok(())
}

运行 cargo build-sbf 命令以生成更新的 .so 文件。

Terminal
$
cargo build-sbf

通过运行 cargo test 命令测试更新后的程序。

Terminal
$
cargo test -- --no-capture

您应该在程序日志中看到 "Hello, Solana!"。

Terminal
running 1 test
Logs: [
"Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV invoke [1]",
"Program log: Hello, Solana!",
"Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV consumed 211 of 200000 compute units",
"Program 5y8bHrnwfq2dLDgLn3WoTHb9dDuyorj9gyapW6aeyrpV success",
]
test test::test_hello_world ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s

重新部署程序

使用相同的 solana program deploy 命令重新部署程序。

Terminal
$
solana program deploy ./target/deploy/hello_world.so

再次运行客户端代码,并在 Solana Explorer 上检查交易签名,以在程序日志中查看 "Hello, Solana!"。

Terminal
$
cargo run --example client

关闭程序

您可以关闭您的 Solana 程序以回收分配给账户的 SOL。关闭程序是不可逆的,因此应谨慎操作。

要关闭程序,请使用 solana program close <PROGRAM_ID> 命令。例如:

Terminal
$
solana program close 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz --bypass-warning

示例输出:

Closed Program Id 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz, 0.1350588 SOL
reclaimed

请注意,一旦程序被关闭,其程序 ID 将无法再次使用。尝试使用已关闭的程序 ID 部署程序将导致错误。

Error: Program 4Ujf5fXfLx2PAwRqcECCLtgDxHKPznoJpa43jUBxFfMz has been closed, use
a new Program Id

重新部署已关闭的程序

如果在关闭程序后需要使用相同的源代码重新部署程序,您必须生成一个新的程序 ID。要为程序生成一个新的 keypair,请运行以下命令:

Terminal
$
solana-keygen new -o ./target/deploy/hello_world-keypair.json --force

或者,您可以删除现有的 keypair 文件(例如 ./target/deploy/hello_world-keypair.json),然后再次运行 cargo build-sbf, 这将生成一个新的 keypair 文件。

lib.rs
Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"
[dependencies]

Is this page helpful?

Table of Contents

Edit Page