substrate学习笔记9:开发智能合约
1 学习内容
本节学习智能合约的开发,主要包括:
- ink!智能合约的结构;
- 存储单个的值和hash map;
- 安全的设置或获取这些值;
- 编写public和private函数;
- 配置Rust使用安全的数学库。
2 合约模板
- ink!
ink!是一种嵌入式领域特定语言,可以用Rust来编写基于webassembly的智能合约。
ink!实际上是一种使用#[ink(…)]属性宏标准的Rust写法。
- 开始一个新的项目
从创建到编译,执行命令如下:
cargo contract new incrementer
cd incrementer/
cargo +nightly test
cargo +nightly contract build
3 存储类型
substrate合约可以存储使用Parity Codec编码和解码的类型,例如像bool, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, string, 元组,数组等。
另外ink!还提供substrate特定类型如AccountId,Balance,Hash等作为原始类型。
4 函数
- 合约函数
写法如下:
impl MyContract {
// Public and Private functions go here
}
- 构造函数
每个ink!智能合约必须有在创建时运行一次的构造函数。每个ink!智能合约可以有多个构造函数。 构造函数写法如下:
use ink_lang as ink;
#[ink::contract]
mod mycontract {
#[ink(storage)]
pub struct MyContract {
number: u32,
}
impl MyContract {
/// Constructor that initializes the `u32` value to the given `init_value`.
#[ink(constructor)]
pub fn new(init_value: u32) -> Self {
Self {
number: init_value,
}
}
/// Constructor that initializes the `u32` value to the `u32` default.
///
/// Constructors can delegate to other constructors.
#[ink(constructor)]
pub fn default() -> Self {
Self {
number: Default::default(),
}
}
/* --snip-- */
}
}
- 公有函数和私有函数
写法如下:
impl MyContract {
/// Public function
#[ink(message)]
pub fn my_public_function(&self) {
/* --snip-- */
}
/// Private function
fn my_private_function(&self) {
/* --snip-- */
}
/* --snip-- */
}
- 获取合约上的值
写法如下:
impl MyContract {
#[ink(message)]
pub fn my_getter(&self) -> u32 {
self.number
}
}
- 可变和不可变的函数
写法举例如下:
impl MyContract {
#[ink(message)]
pub fn my_getter(&self) -> u32 {
self.my_number
}
#[ink(message)]
pub fn my_setter(&mut self, new_value: u32) {
self.my_number = new_value;
}
}
- 惰性存储值
使用示例如下:
#[ink(storage)]
pub struct MyContract {
// Store some number
my_number: ink_storage::Lazy<u32>,
}
impl MyContract {
#[ink(constructor)]
pub fn new(init_value: u32) -> Self {
Self {
my_number: ink_storage::Lazy::<u32>::new(init_value),
}
}
#[ink(message)]
pub fn my_setter(&mut self, new_value: u32) {
ink_storage::Lazy::<u32>::set(&mut self.my_number, new_value);
}
#[ink(message)]
pub fn my_adder(&mut self, add_value: u32) {
let my_number = &mut self.my_number;
let cur = ink_storage::Lazy::<u32>::get(my_number);
ink_storage::Lazy::<u32>::set(my_number, cur + add_value);
}
}
5 存储mapping
- HashMap
使用示例如下:
#[ink(storage)]
pub struct MyContract {
// Store a mapping from AccountIds to a u32
my_number_map: ink_storage::collections::HashMap<AccountId, u32>,
}
- HashMap API
HashMap常用的API如下:
/// Inserts a key-value pair into the map.
///
/// Returns the previous value associated with the same key if any.
/// If the map did not have this key present, `None` is returned.
pub fn insert(&mut self, key: K, new_value: V) -> Option<V> {/* --snip-- */}
/// Removes the key/value pair from the map associated with the given key.
///
/// - Returns the removed value if any.
pub fn take<Q>(&mut self, key: &Q) -> Option<V> {/* --snip-- */}
/// Returns a shared reference to the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type,
/// but `Hash` and `Eq` on the borrowed form must match those for the key type.
pub fn get<Q>(&self, key: &Q) -> Option<&V> {/* --snip-- */}
/// Returns a mutable reference to the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type,
/// but `Hash` and `Eq` on the borrowed form must match those for the key type.
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V> {/* --snip-- */}
/// Returns `true` if there is an entry corresponding to the key in the map.
pub fn contains_key<Q>(&self, key: &Q) -> bool {/* --snip-- */}
/// Converts the OccupiedEntry into a mutable reference to the value in the entry
/// with a lifetime bound to the map itself.
pub fn into_mut(self) -> &'a mut V {/* --snip-- */}
/// Gets the given key's corresponding entry in the map for in-place manipulation.
pub fn entry(&mut self, key: K) -> Entry<K, V> {/* --snip-- */}
- 初始化一个HashMap
初始化示例如下:
#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract]
mod mycontract {
#[ink(storage)]
pub struct MyContract {
// Store a mapping from AccountIds to a u32
my_number_map: ink_storage::collections::HashMap<AccountId, u32>,
}
impl MyContract {
/// Public function.
/// Default constructor.
#[ink(constructor)]
pub fn default() -> Self {
Self {
my_number_map: Default::default(),
}
}
/// Private function.
/// Returns the number for an AccountId or 0 if it is not set.
fn my_number_or_zero(&self, of: &AccountId) -> u32 {
let balance = self.my_number_map.get(of).unwrap_or(&0);
*balance
}
}
}
- 合约调用者
返回合约调用者的示例如下:
#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract]
mod mycontract {
#[ink(storage)]
pub struct MyContract {
// Store a contract owner
owner: AccountId,
}
impl MyContract {
#[ink(constructor)]
pub fn new() -> Self {
Self {
owner: Self::env().caller();
}
}
/* --snip-- */
}
}
- 修改hash map
示例1如下:
impl MyContract {
/* --snip-- */
// Set the value for the calling AccountId
#[ink(message)]
pub fn set_my_number(&mut self, value: u32) {
let caller = self.env().caller();
self.my_number_map.insert(caller, value);
}
// Add a value to the existing value for the calling AccountId
#[ink(message)]
pub fn add_my_number(&mut self, value: u32) {
let caller = self.env().caller();
let my_number = self.my_number_or_zero(&caller);
self.my_number_map.insert(caller, my_number + value);
}
/// Returns the number for an AccountId or 0 if it is not set.
fn my_number_or_zero(&self, of: &AccountId) -> u32 {
*self.my_number_map.get(of).unwrap_or(&0)
}
}
示例2如下:
let caller = self.env().caller();
self.my_number_map
.entry(caller)
.and_modify(|old_value| *old_value += by)
.or_insert(by);
6 总结
总的来说,写ink!合约和直接用Rust编码没有太大的区别,只要能使用Rust都能很快的编写合约。
7 参考资料
docs.substrate.io/tutorials/v3/ink...
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: