substrate學習筆記9:開發智慧合約

linghuyichong發表於2022-03-30

本節學習智慧合約的開發,主要包括:

  • ink!智慧合約的結構;
  • 儲存單個的值和hash map;
  • 安全的設定或獲取這些值;
  • 編寫public和private函式;
  • 配置Rust使用安全的數學庫。
  • ink!

ink!是一種嵌入式領域特定語言,可以用Rust來編寫基於webassembly的智慧合約。

ink!實際上是一種使用#[ink(…)]屬性宏標準的Rust寫法。

  • 開始一個新的專案

從建立到編譯,執行命令如下:

cargo contract new incrementer

cd incrementer/

cargo +nightly test

cargo +nightly contract build

substrate合約可以儲存使用Parity Codec編碼和解碼的型別,例如像bool, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, string, 元組,陣列等。
另外ink!還提供substrate特定型別如AccountId,Balance,Hash等作為原始型別。

  • 合約函式

寫法如下:

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);
    }
}
  • 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);

總的來說,寫ink!合約和直接用Rust編碼沒有太大的區別,只要能使用Rust都能很快的編寫合約。

docs.substrate.io/tutorials/v3/ink...

本作品採用《CC 協議》,轉載必須註明作者和本文連結
令狐一衝

相關文章