Solana开发教程:初识合约开发

Solong
12 min readNov 18, 2020

准备工作

在前面的文章中,我们已经安装好了solana的命令行工具,其中包括了秘钥生成以及编译工具等。有了 这些工具后,我们就可以开始进行相应的开发了

连接开发网

如果你还没有搭建自己的节点,为了省去节点的相关操作,可以先使用Solana提供的开发网进行开发, 因为开发网的配置和主网基本是一致的。

通过设置:

ubuntu@VM-0-12-ubuntu:~$ solana config set --url https://devnet.solana.com
Config File: /home/ubuntu/.config/solana/cli/config.yml
RPC URL: https://devnet.solana.com
WebSocket URL: wss://devnet.solana.com/ (computed)
Keypair Path: /home/ubuntu/.config/solana/id.json

这样设置了我们的默认API节点连到 https://devnet.solana.com ,其后面对应的节点网络是开发网。

准备账号

每个Onchain Program本质上其实也是一个Account,但是其被标记为”Executable:true”,也就是 可以执行的文件内容。为了存放这个可执行文件,我们需要创建一个账号,而创建账号需要另一个拥有 SOL的账号来付fee。所以首先我们来创建这个付fee的账号:

solana-keygen new

输入密码后,为将新创建的账号存在”/home/ubuntu/.config/solana/id.json”这个文件中, 后续如果solana命令没有指定 — key,那么默认就是用的这个文件。

然后查看这个私钥对应的公钥:

ubuntu@VM-0-12-ubuntu:~$ solana-keygen pubkey /home/ubuntu/.config/solana/id.json
7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf

然后在开发网上面申请SOL空投,从而获得需要的SOL:

ubuntu@VM-0-12-ubuntu:~$ solana airdrop 10 7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf
Requesting airdrop of 10 SOL from 34.82.57.86:9900
10 SOL

来查询一下余额:

ubuntu@VM-0-12-ubuntu:~$ solana balance 7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf
10 SOL

然后再创建一个账号:

solana-keygen new -o solana_memo_program.json

输入密码后,保存好key,这个后面再发布程序的时候使用。前面那个账号是发布程序的账号,这个账号 就是对应的程序的地址。

编译合约

2020–10–22 之后的1.4.x版本,Solana提供了 “cargo-build-bpf”, 以及”cargo-test-bpf”等工具,用于直接将cargo工程编译出BPF文件。

在上面安装的Solana命令行中以及包含了”carg-build-bpf”。

这里checkout出官方的memo程序以做测试:

git clone https://github.com/solana-labs/solana-program-library.git

然后进入到目录中的solana-program-library/memo/program/ 并执行:

cargo build-bpf

他会调用上面的”cargo-build-bpf”程序,这个memo程序会记录发送请求中的内容在链上。

如果出现:

= note: /usr/bin/ld: cannot find Scrt1.o: No such file or directory
/usr/bin/ld: cannot find crti.o: No such file or directory
collect2: error: ld returned 1 exit status

报错,是因为缺少一些32位的依赖, 执行:

sudo apt install gcc-multilib

再次编译,编译成功得到:

To deploy this program:
$ solana deploy /home/ubuntu/solana/solana-program-library/target/deploy/spl_memo.so

这里用上我们前面创建的”solana_memo_program.json”:

solana-keygen pubkey ~/solana_memo_program.json
D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j

然后deploy:

solana deploy /home/ubuntu/solana/solana-program-library/target/deploy/spl_memo.so ~/solana_memo_program.json
{"programId":"D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j"}

这样就表示我们成功发布了一个程序到开发网上,程序地址为:D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j

打开,Solana浏览器,切换网络为开发网,可以查看程序:

4.测试合约

与链交互我们采用@solana/web3.js

新建一个工程,并添加@solana/web3.js

yarn init
yarn add @solana/web3.js

假设入口文件为src/index.js,整个目录如下:

├── package.json
├── src
│ └── index.js

然后填充内容:

var solana_web3 = require('@solana/web3.js');
function testMemo(connection, account){
const instruction = new solana_web3.TransactionInstruction({
keys: [],
programId:new solana_web3.PublicKey('D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j'),
data: Buffer.from('cztest'),
});
console.log("account:", account.publicKey.toBase58())
solana_web3.sendAndConfirmTransaction(
connection,
new solana_web3.Transaction().add(instruction),
[account],
{
skipPreflight: true,
commitment: "singleGossip",
},
).then(()=>{console.log("done")}).catch((e)=>{console.log("error",e)});
}
function main() {
connection = new solana_web3.Connection("https://devnet.solana.com", 'singleGossip');
const account = new solana_web3.Account()
const lamports = 10*1000000000
connection.requestAirdrop(account.publicKey, lamports).then(()=>{
console.log("airdrop done")
testMemo(connection, account)
});
}
main()

这里的逻辑是先创建一个账号,然后用这个账号申请空投10SOL,接着,用这个账号发送合约请求到 我们部署的”D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j”程序,结束后,我们来看 浏览器:

这里Memo程序在链上记录的信息为”637a74657374",是具体内容的hex结果:

>>> import binascii
>>> binascii.a2b_hex('637a74657374')
b'cztest'

和我们发送的值是一样的。

合约工程

上面我们是直接拿了别人的合约来编译部署的,那么如果我们要写一个自己的合约要怎么做呢?这里和普通的rust项目 一致,通过cargo来进行项目管理。

首先通过cargo创建一个项目,比如:

cargo new onchain_program

然后打开onchain_program/Cargo.toml,并增加修改:

[dependencies]
arrayref = "0.3.6"
num-derive = "0.3"
num-traits = "0.2"
num_enum = "0.5.1"
solana-program = "1.4.8"
thiserror = "1.0"
[dev-dependencies]
solana-sdk = "1.4.8"
[lib]
crate-type = ["cdylib", "lib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

同时在这个目录下增加Xargo.toml:

[target.bpfel-unknown-unknown.dependencies.std]
features = []

增加bpf的跨平台编译支持。

接着编写合约内容,在src/lib.rs里面设置entrypoint:

#![deny(missing_docs)]//! A simple program that return success.#[cfg(not(feature = "no-entrypoint"))]
mod entrypoint;
// Export current sdk types for downstream users building with a different sdk version
pub use solana_program;

然后在entrypoint.rs里面书写合约内容:

//! Program entrypointuse solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, program_error::ProgramError,
pubkey::Pubkey,
};
use std::str::from_utf8;
entrypoint!(process_instruction);
fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
Ok(())
}

这个合约的内容,是不做任何处理。直接返回成功。

函数”process_instruction”是整个合约入口,传入的是一个instruction结构。他包含了 表示执行指令的程序的:_program_id,所要执行使用的账户集:_accounts,以及序列化之后的 instruction_data部分。当认为执行成功时,通过Ok(())返回成功,否则用Err(error) 返回出错。

此时onchain_program目录看起来如下:

在该目录下执行:

cargo build-bpf...To deploy this program:
$ solana deploy /home/ubuntu/solana/memo_test/onchain_program/target/deploy/onchain_program.so

即可构建出bpf的so文件了。

英文版请看这里

总结

Solana提供的Onchain Program开发的SDK以及构建工具,结合Cargo已经可以很方便的构建出目标 文件。只要弄明白了这个过程,就可以不被原来繁杂的脚本弄晕。其本质就是通过rustc结合LLVM将 rust程序编译成BPF格式的目标文件,然后将整个目标文件发布到链上。这其中需要注意的是不是所有的 rust特性都可以在开发中使用,具体可以参考Rust limitations。另外当前的主网还是1.3.x的版本, 如果想用1.3.x的编译工具链,则需要使用xargo-build.sh

参考

  1. example-helloworld
  2. solana-program-library
  3. cargo-build-bpf

--

--