简单学习下Solana的合约开发
Solana简介
Solana利用了 工作历史证明 PoH、基站拜占庭容错(Tower BFT)、涡轮机(区块传播协议)、海湾流(无内存交易转发协议)、海平面(并行智能合约)、管道(验证交易)、云散(水平扩展账户数据库)以及 档案(分布式账本存储)
等一系列牛逼哄哄的技术构建出一个超高性能的区块链公链,目前TPS实测已经达到了65,000TPS。高TPS带来的一个问题是数据暴胀,相对来讲对节点要提供可以回溯久远的历史查询服务应该会带来更大的挑战!
模型
一笔交易中包含一个或多个指令,可执行Program会根据接受到的指令、指令参数和账号签名,运行相关逻辑,载入相关的账户数据
根据提供的签名,可以读取或修改一些账户的状态数据等,如果任何一个指令无效,则所有的账户的状态更新都会被重置等等,详见 Overview
账户
每个账户都有自己的data 大小上限为10MB,Solana的合约状态都是存储在不同账户上的data里实现的,和EOS一样需要租金来获得一定量的存储空间,具体计算详见Accounts1
2Rent: 2,439 = 19.055441478439427 (rent rate) * 128 bytes (minimum account size) * 1 (epoch)
Account Balance: 7,561 = 10,000 (transfered lamports) - 2,439 (this account's rent fee for an epoch)
合约
在Solana体系中叫program,Solana使用LLVM编译器基础结构,因此可以使用可以针对LLVM的BPF后端的任何编程语言编写程序。
Solana目前支持用Rust和C / C ++编写程序(官方合约都是rust写的,最近刚学的rust可以用上场了hh)详见Runtime
Program学习
官方有一些列的Program叫SPL,具体在:https://spl.solana.com/
- Token - 同质和非同质化代币的实现
- Token Swap - Uniswap-like 的AMM交易所
- Token-Lending - 类似Aave的借贷协议
等等…
简单看看SPL Token指令的实现
create-token
创建Tokentoken/cli/src/main.src:21941
2
3
4
5
6
7
8
9
10
11("create-token", Some(arg_matches)) => {
...
command_create_token(
&config,
decimals,
token,
mint_authority,
arg_matches.is_present("enable_freeze"),
memo,
)
}
command_create_token
- system_instruction::create_account
- initialize_mint
创建Token账户,并对Token账户执行了initialize_mint指令
1 | let mut instructions = vec![ |
process_initialize_mint 指令的具体处理逻辑
Mint 实现了Pack Trait1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23pub struct Mint {
/// Optional authority used to mint new tokens. The mint authority may only be provided during
/// mint creation. If no mint authority is present then the mint has a fixed supply and no
/// further tokens may be minted.
pub mint_authority: COption<Pubkey>,
/// Total supply of tokens.
pub supply: u64,
/// Number of base 10 digits to the right of the decimal place.
pub decimals: u8,
/// Is `true` if this structure has been initialized
pub is_initialized: bool,
/// Optional authority to freeze token accounts.
pub freeze_authority: COption<Pubkey>,
}
impl Pack for Mint {
fn unpack_from_slice(src: &[u8]) {
...
},
fn pack_into_slice(&self, dst: &mut [u8]){
...
}
}
具体的指令实现
/// token/program/src/processor.rs:281
2
3
4
5
6
7
pub fn process_initialize_mint(
accounts: &[AccountInfo],
decimals: u8,
mint_authority: Pubkey,
freeze_authority: COption<Pubkey>,
) -> ProgramResult {
获取第一个AccountInfo1
2let account_info_iter = &mut accounts.iter();
let mint_info = next_account_info(account_info_iter)?;
从data中反序列化Mint1
let mut mint = Mint::unpack_unchecked(&mint_info.data.borrow())?;
修改一些属性1
2
3
4
5// 铸造权限的pubKey
mint.mint_authority = COption::Some(mint_authority);
mint.decimals = decimals;
mint.is_initialized = true;
mint.freeze_authority = freeze_authority;
序列化更新mint_info.data1
2
3Mint::pack(mint, &mut mint_info.data.borrow_mut())?;
Ok(())
}
create-account
创建token账户和create-token一样创建了个新账号,并执行了初始化
Account
Token Acount 主要用来存储Token的amountowner 指向了所属账户mint 指向了token账户
1 | pub struct Account { |
1 |
|
mint账户 和要初始化的new_account_info1
2
3
4
5
6
7
8
9
let new_account_info = next_account_info(account_info_iter)?;
// mint账户
let mint_info = next_account_info(account_info_iter)?;
let owner = if let Some(owner) = owner {
owner
} else {
next_account_info(account_info_iter)?.key
};
从new_account_info.data中反序列化Account1
2
3
4let mut account = Account::unpack_unchecked(&new_account_info.data.borrow())?;
if account.is_initialized() {
return Err(TokenError::AlreadyInUse.into());
}
初始化Token Account的信息1
2
3
4
5
6
7
8
9
10
11if *mint_info.key != crate::native_mint::id() {
let _ = Mint::unpack(&mint_info.data.borrow_mut())
.map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?;
}
account.mint = *mint_info.key;
account.owner = *owner;
account.delegate = COption::None;
account.delegated_amount = 0;
account.amount = 0;
account.state = AccountState::Initialized;
写入状态1
2
3Account::pack(account, &mut new_account_info.data.borrow_mut())?;
Ok(())
}
transfer,mint
像其他的指令都是对不同账户的状态(Mint、Account)进行增删改查
浏览器
用区块链浏览器看看不同账户的数据 https://explorer.solana.com/,以[USD Coin](https://explorer.solana.com/address/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v “USD Coin”)为例:

通过getTokenLargestAccounts接口可以直接查询到持有量比较大的账户
常规program

executeable是否可执行last_deployed_slot上一次部署的slotupgrade_authority具有更新权限的账户executable_data指向了存program代码的账户
Program Executable Data Account

用来存储编译后代码的账户,代码占用903506Bytes
Token Account

owner 指向了所属的Account账户
Account

根据getTokenAccountsByOwner接口可查询某个账户到相关的TokenAccount持有币种的数量