Rust编写Near智能合约

编程入门 行业动态 更新时间:2024-10-26 08:29:08

Rust编写Near智能<a href=https://www.elefans.com/category/jswz/34/1770939.html style=合约"/>

Rust编写Near智能合约

先决条件

  • Rust(安装指南,如果您想了解有关 Rust 的更多信息,请在此处查看本指南)
  • NEAR CLI(安装指南)
  • NEAR Testnet 帐户(如果您没有 testnet 帐户,请在此处查看本指南)

设置

要设置我们的项目,我们需要将 WASM (WebAssembly) 目标添加到我们的工具链中。要添加,我们需要在终端中运行以下命令:

rustup target add wasm32-unknown-unknown

终端输出:

info: downloading component 'rust-std' for 'wasm32-unknown-unknown'
info: installing component 'rust-std' for 'wasm32-unknown-unknown'
info: using up to 500.0 MiB of RAM to unpack components 13.9 MiB /  13.9 MiB (100 %)  10.0 MiB/s in  1s ETA:  0s

如果目标已经添加,则终端中的输出将是:

info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date

什么是 Rust 工具链?工具链是编译 Rust 应用程序所需的程序集合的特定版本。

为什么我们需要添加 WASM 目标?要在 NEAR 上部署我们的智能合约,我们需要将其编译为 WebAssembly(文件)。上面的命令为 WebAssembly 目标三元组 (wasm32-unknown-unknown) 安装标准库。.wasm``rustup在docs上rustup阅读有关交叉编译的更多信息。

现在,让我们创建一个名为key_value_storage 的目录,然后切换到该目录并在终端中运行以下命令:

cargo init --lib

输出:

Created library package

修改默认的 Cargo.toml 文件,将下列代码粘贴:

[package]
name = "key_value_storage"
version = "0.1.0"
authors = ["Your Name <Your Email>"]
edition = "2018"# See more keys and their definitions at .html
[lib]
crate-type = ["cdylib", "rlib"][dependencies]
near-sdk = "3.1.0"[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
lto = true
debug = false
panic = "abort"
# Opt into extra safety checks on arithmetic operations 
overflow-checks = true

编写智能合约

删除之前lib.rs代码,将下面代码粘贴过去

我们将在 Rust 中创建一个简单的创建、读取、更新、删除 ( CRUD ) 后端,它利用 NEAR 提供的链上存储。

use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::{env, near_bindgen};
use near_sdk::collections::UnorderedMap;near_sdk::setup_alloc!();// 1. Main Struct// 2. Default Implementation// 3. Core Logic// 4. Tests

在合约的顶部,我们需要导入一些带有use 声明的代码模块。我们将在下面的这些部分进行扩展。near_sdk

接下来,我们使用宏从crate设置全局分配器。分配器是 Rust 中的程序在运行时从系统获取内存的方式。是为 WebAssembly 设计的内存分配器。它生成不到 1 KB 的未压缩 WebAssembly 代码。

#[cfg(target_arch = "wasm32")]
#[global_allocator]
static ALLOC: near_sdk::wee_alloc::WeeAlloc<'_> = near_sdk::wee_alloc::WeeAlloc::INIT;

Main Struct

在编写我们的智能合约时,我们将遵循一种使用一个结构 ( ) 和与之相关的实现 ( ) 的模式。这是 NEAR 上大多数 Rust 合约中使用的模式。在注释下添加以下代码段中:// 1. Main Struct

#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
pub struct KeyValue {pairs: UnorderedMap<String, String>,
}

pairsUnorderedMap中在near_sdk::collections的一种更有效地利用底层区块链存储的数据结构。

Default implementation

// 2. Default Implementation 中添加如下代码

impl Default for KeyValue {fn default() -> Self {Self {pairs: UnorderedMap::new(b"r".to_vec())}}
}

Rust 中的每种类型都有一个实现,但在这里我们想为struct提供我们自己的默认实现。

Core Logic

// 3. Core Logic:现在我们要向struct添加方法。这些方法是我们智能合约的核心逻辑

#[near_bindgen]
impl KeyValue {pub fn create_update(&mut self, k: String, v: String) {env::log(b"created or updated");self.pairs.insert(&k, &v);}pub fn read(&self, k: String) -> Option<String> {env::log(b"read");return self.pairs.get(&k);}pub fn delete(&mut self, k: String) {env::log(b"delete");self.pairs.remove(&k);}
}

测试合约代码

我们的 CRUD 智能合约的代码现已完成。Rust 的一个很好的特性是它允许内联单元测试。这意味着我们可以在与合约相同的源文件中编写单元测试lib.rs

为什么要为智能合约编写单元测试?单元测试是软件开发中的常见做法。在编写智能合约时,单元测试很重要,因为智能合约通常是不可变的,有时负责管理资金。编写好的单元测试是安全可靠的智能合约开发的关键组成部分。

复制并粘贴在注释下下面的代码中:// 4. Tests

#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {use super::*;use near_sdk::MockedBlockchain;use near_sdk::{testing_env, VMContext};fn get_context(input: Vec<u8>, is_view: bool) -> VMContext {VMContext {current_account_id: "alice_near".to_string(),signer_account_id: "bob_near".to_string(),signer_account_pk: vec![0, 1, 2],predecessor_account_id: "carol_near".to_string(),input,block_index: 0,block_timestamp: 0,account_balance: 0,account_locked_balance: 0,storage_usage: 0,attached_deposit: 0,prepaid_gas: 10u64.pow(18),random_seed: vec![0, 1, 2],is_view,output_data_receivers: vec![],epoch_height: 0,}}// Test 1#[test]fn create_read_pair() {let context = get_context(vec![], false);testing_env!(context);let mut contract = KeyValue::default();contract.create_update("first_key".to_string(), "hello".to_string());assert_eq!("hello".to_string(),contract.read("first_key".to_string()).unwrap());}// Test 2#[test]fn read_nonexistent_pair() {let context = get_context(vec![], true);testing_env!(context);let contract = KeyValue::default();assert_eq!(None, contract.read("first_key".to_string()));}
}

Test1和Test2分别测试了合约的两个方法

在命令行运行:

cargo test -- --nocapture

终端输出:

Finished test [unoptimized + debuginfo] target(s) in 1m 05sRunning target/debug/deps/key_value_storage-958f616e81cf3269running 2 tests
test tests::read_nonexistent_pair ... ok
test tests::create_read_pair ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sDoc-tests key_value_storagerunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

编译合约

设置环境变量,交叉编译合约代码

// Linux and macOS users can use these commands:
env 'RUSTFLAGS=-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release// Windows users can use these commands:
set RUSTFLAGS=-C link-arg=-s
cargo build --target wasm32-unknown-unknown --release

编译后输出:

Compiling near-sdk v3.1.0
Compiling key_value_storage v0.1.0 (/home/xqc/key_value_storage)
Finished release [optimized] target(s) in 1m 00s

现在生成了WebAssembly 文件,下面我们可以登陆账号,把合约部署在testnet上

登陆Testnet账号

使用near cli命令登陆账号

near login 

默认$NEAR_ENV就是Testnet网络

执行后命令行会弹出浏览器钱包界面,将Testnet账号助记词导入,allow授权后,命令行就有账号登陆成功的回执信息

我们使用near cli工具创建子账号,用于合约部署

near create-account CONTRACT_NAME.ACCOUNT_ID --masterAcount ACCOUNT_ID --initialBalance 10

创建完成后,会有账号保存路径信息如下:

Saving key to '/home/xxx/.near-credentials/testnet/CONTRACT_NAME.ACCOUNT_ID.json'
Account CONTRACT_NAME.ACCOUNT_ID.testnet for network "testnet" was created.
  • 例如,假设您在 testnet 附近的当前account_idfido.testnet并且您想命名合约**dodo,**那么您最终将创建以下新 account_id dodo.fido.testnet,它代表contract_id
  • --initialBalance如果省略默认为100 Near

部署合约

CONTRACT_ID 为我们创建的子账号

near deploy --wasmFile target/wasm32-unknown-unknown/release/key_value_storage.wasm --accountId CONTRACT_ID

部署成功后,会有下面信息输出:

Starting deployment. Account id: CONTRACT_ID, node: , helper: , file: target/wasm32-unknown-unknown/release/key_value_storage.wasm
Transaction Id E4uT8wV5uXSgsJpB73Uox27iPgXztWfL3b5gzxfA3fHo
To see the transaction in the transaction explorer, please open this url in your browser

Done deploying to CONTRACT_ID

调用合约

–accountId 你的near 账户

1.创建键值对 调用合约create_update方法

near call CONTRACT_ID create_update '{"k": "first_key", "v" : "1"}' --accountId ACCOUNT_ID

2.读取键值对 调用read方法

near view CONTRACT_ID read '{"k": "first_key"}' --accountId ACCOUNT_ID
View call: CONTRACT_ID.read({"k": "first_key"})
Log [CONTRACT_ID]: read
'1'

3.删除键值对 调用delete方法

near call CONTRACT_ID delete '{"k": "first_key"}' --accountId ACCOUNT_ID

以上就是简单的near 合约创建 部署 调用的实例

一个投票合约

我们使用cargo创建一个vote的项目

lib.rs

use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::{env, near_bindgen};
use near_sdk::serde::{Deserialize, Serialize};
use std::collections::HashMap;#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;#[derive(Serialize, Deserialize, Clone, BorshDeserialize, BorshSerialize)]
pub struct VotingOption {option_id: String,message: String,
}#[derive(Serialize, Deserialize, Clone, BorshDeserialize, BorshSerialize)]
pub struct VotingOptions {// Author of the vote (account id).creator: String,// Unique voting id.poll_id: String,// Question voted on.question: String,variants: Vec<VotingOption>,
}#[derive(Serialize, Deserialize, Clone, BorshDeserialize, BorshSerialize)]
pub struct VotingResults {// Unique poll id.poll_id: String,// Map of option id to the number of votes.variants: HashMap<String, i32>,// Map of voters who already voted.voted: HashMap<String, i32>,
}#[derive(Serialize, Deserialize)]
pub struct VotingStats {poll: VotingOptions,results: VotingResults,
}#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct Voting {// Map of poll id to voting options.polls: HashMap<String, VotingOptions>,// Map of poll id to voting results.results: HashMap<String, VotingResults>,
}#[near_bindgen]
impl Voting {pub fn vote(&mut self, poll_id: String, votes: HashMap<String, i32>) -> bool {let voter_contract = env::signer_account_id();let owner_contract = env::current_account_id();env::log(format!("{} is voting on {} owner is {}",voter_contract, poll_id, owner_contract).as_bytes(),);// Now we need to find a contract to vote for.match self.results.get_mut(&poll_id) {Some(results) => {match results.voted.get(&voter_contract) {Some(_) => {env::log(format!("{} already voted in {}", voter_contract, poll_id).as_bytes(),);return false;}None => {results.voted.insert(voter_contract, 1);}}for (vote, checked) in votes.iter() {if *checked == 0 {continue;}match results.variants.get_mut(vote) {Some(result) => {*result = *result + 1;}None => {results.variants.insert(vote.to_string(), 1);}}}return true;}None => {env::log(format!("no poll known for {}", poll_id).as_bytes());return false;}};}pub fn create_poll(&mut self, question: String, variants: HashMap<String, String>) -> String {env::log(format!("create_poll for {} currently have {} polls",question,self.polls.len()).as_bytes(),);let creator_account_id = env::signer_account_id();let poll_id = bs58::encode(env::sha256(&env::random_seed())).into_string();let result = poll_id.clone();let mut variants_vec = <Vec<VotingOption>>::new();for (k, v) in variants.iter() {variants_vec.push(VotingOption {option_id: k.to_string(),message: v.to_string(),})}self.polls.insert(poll_id.clone(),VotingOptions {creator: creator_account_id,poll_id: poll_id.clone(),question: question,variants: variants_vec,},);self.results.insert(poll_id.clone(),VotingResults {poll_id: poll_id,variants: HashMap::new(),voted: HashMap::new(),},);return result;}pub fn show_poll(&self, poll_id: String) -> Option<VotingOptions> {match self.polls.get(&poll_id) {Some(options) => Some(options.clone()),None => {env::log(format!("Unknown voting {}", poll_id).as_bytes());None}}}pub fn show_results(&self, poll_id: String) -> Option<VotingStats> {match self.polls.get(&poll_id) {Some(poll) => match self.results.get(&poll_id) {Some(results) => Some(VotingStats {results: results.clone(),poll: poll.clone(),}),None => None,},None => None,}}pub fn ping(&self) -> String {"PONG".to_string()}
}

cargo.toml:

[package]
name = "vote"
version = "0.2.0"
authors = ["Near lk2684753"]
edition = "2018"[lib]
crate-type = ["cdylib", "rlib"][dependencies]
# serde = { version = "1.0", features = ["derive"] }
# serde_json = { git = "", rev = "1f5779f3b0bd3d2a4b0b975abc46f3d3fe873331", features = ["no_floats"] }
# near-bindgen = "0.6.0"
# borsh = "*"
# wee_alloc = "0.4.5"
serde = { version = "*", features = ["derive"] }
serde_json = "*"
borsh = "*"
near-sdk = "2.0.0"
wee_alloc = { version = "0.4.5", default-features = false, features = [] }
bs58 = "0.3"[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
lto = true
debug = false
panic = "abort"
# Opt into extra safety checks on arithmetic operations 
overflow-checks = true[workspace]
members = []

按照上述讲的将合约部署在testnet网络

部分调用方法实例

#投票
near call lkvote.$ID  vote  '{"poll_id":"f1bVk4smE3t55qPCt9gASy9BKNr1wmj7ifqwXNEALrH","votes":{"0":1}}'  --accountId $ID
#投票内容
near call lkvote.$ID  show_poll  '{"poll_id":"f1bVk4smE3t55qPCt9gASy9BKNr1wmj7ifqwXNEALrH"}'  --accountId lk26847853.testnet
#投票目前结果
near call lkvote.$ID  show_results  '{"poll_id":"f1bVk4smE3t55qPCt9gASy9BKNr1wmj7ifqwXNEALrH"}'  --accountId $ID

$ID可通过环境变量设置

ID=lk26847853.testnet

echo $ID 检查是否设置成功

更多推荐

Rust编写Near智能合约

本文发布于:2024-03-23 16:58:38,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1740605.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:合约   智能   Rust

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!