substrateで独自パレットを実装するためのメモ #送金


やったこと

  • 独自のパレットを生成して、送金する

経緯

  • polkadot、plasmに注目する中で、substrateを学びたくなった。
  • 英語を自由に操れないレベルですが、ドキュメントを読んだけど細部までは理解出来ず。
  • substrate developer hubのチュートリアルをやって、ノード起動とink!は割と理解出来たが、palletはよく分からなかった。
  • ink!でERC20のサンプルを動かしたり、自分でロックドロップを書いて動かしたりしてみた。
  • substrate recipesを見付けたので、読んだら、分かり易くて、もう少し理解が進んだ。
  • そして、palletをもう一回やってみようと思った。

やっぱり分かり辛いPallet

  • Recipesを読んでわかったつもりになって、Developper hubのpallet関連のチュートリアルをやると、一回目にやった時は比べ物にならないくらい理解が進んだ
  • その一方で「送金を自分のPalletに実装して下さい」と自分に問うと、「・・・・」となってしまう自分がいた

手探りで始める

  • まず、独自Palletの作り方はここで実施済みなので、出来る。
  • Recipesを色々見て、「送金にはCurrency Traitを使う」ことがわかってきた(※間違っている場合があります。)
  • 色んなものを読んで、切って貼ってをして作ったのがこれ。

備考

※templateの残骸が残っています。
※宣言箇所等最適な状態になっていないと思われるものが多数あります。

ソース

  • pallets/first-pallet/src/lib.rs
#![cfg_attr(not(feature = "std"), no_std)]

/// A FRAME pallet template with necessary imports

/// Feel free to remove or edit this file as needed.
/// If you change the name of this file, make sure to update its references in runtime/src/lib.rs
/// If you remove this file, you can remove those references

/// For more guidance on Substrate FRAME, see the example pallet
/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs
use frame_support::{
    decl_error, decl_event, decl_module, decl_storage, dispatch, traits::Currency,
    traits::ExistenceRequirement,
};
use frame_system::{self as system, ensure_signed};

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

type Balance<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;

/// The pallet's configuration trait.
pub trait Trait: system::Trait {
    // Add other types and constants required to configure this pallet.
    type Currency: Currency<Self::AccountId>;
    /// The overarching event type.
    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}

// This pallet's storage items.
decl_storage! {
    // It is important to update your storage name so that your pallet's
    // storage items are isolated from other pallets.
    // ---------------------------------vvvvvvvvvvvvvv
    trait Store for Module<T: Trait> as TemplateModule {
        // Just a dummy storage item.
        // Here we are declaring a StorageValue, `Something` as a Option<u32>
        // `get(fn something)` is the default getter which returns either the stored `u32` or `None` if nothing stored
                //Something get(fn something): Option<u32>;
        //pub BalanceOf get(fn balance_of): map T::AccountId => Balance;
        pub BalanceOf get(fn get_balance_of): map hasher(blake2_128_concat) T::AccountId => Balance<T>;
    }
}

// The pallet's events
decl_event!(
    pub enum Event<T>
    where
        AccountId = <T as system::Trait>::AccountId,
    {
        /// Just a dummy event.
        /// Event `Something` is declared with a parameter of the type `u32` and `AccountId`
        /// To emit this event, we call the deposit function, from our runtime functions
        SomethingStored(u32, AccountId),
    }
);

// The pallet's errors
decl_error! {
    pub enum Error for Module<T: Trait> {
        /// Value was None
        NoneValue,
        /// Value reached maximum and cannot be incremented further
        StorageOverflow,
    }
}

// The pallet's dispatchable functions.
decl_module! {
    /// The module declaration.
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        // Initializing errors
        // this includes information about your errors in the node's metadata.
        // it is needed only if you are using errors in your pallet
        type Error = Error<T>;

        // Initializing events
        // this is needed only if you are using events in your pallet
        fn deposit_event() = default;

        /// Just a dummy entry point.
        /// function that can be called by the external world as an extrinsics call
        /// takes a parameter of the type `AccountId`, stores it, and emits an event
        // #[weight = 10_000]
        // pub fn do_something(origin, something: u32) -> dispatch::DispatchResult {
        //     // Check it was signed and get the signer. See also: ensure_root and ensure_none
        //     let who = ensure_signed(origin)?;

        //     // Code to execute when something calls this.
        //     // For example: the following line stores the passed in u32 in the storage
        //     Something::put(something);

        //     // Here we are raising the Something event
        //     Self::deposit_event(RawEvent::SomethingStored(something, who));
        //     Ok(())
        // }

        /// Another dummy entry point.
        /// takes no parameters, attempts to increment storage value, and possibly throws an error
        // #[weight = 10_000]
        // pub fn cause_error(origin) -> dispatch::DispatchResult {
        //     // Check it was signed and get the signer. See also: ensure_root and ensure_none
        //     let _who = ensure_signed(origin)?;

        //     match Something::get() {
        //         None => Err(Error::<T>::NoneValue)?,
        //         Some(old) => {
        //             let new = old.checked_add(1).ok_or(Error::<T>::StorageOverflow)?;
        //             Something::put(new);
        //             Ok(())
        //         },
        //     }
        // }
        #[weight = 10_000]
        pub fn my_transfer(origin,to: T::AccountId,value: Balance<T>) -> dispatch::DispatchResult{
          let who = ensure_signed(origin)?;
          T::Currency::transfer(&who,&to,value,ExistenceRequirement::KeepAlive);
          <BalanceOf<T>>::insert(to, value);
          Ok(())
        }
    }
}

これから

  • 恐らく英語に精通していて、正しくドキュメントが読めれば、こんな苦労しないのだろうと思います。
  • 何となくやり方が肌感覚として分かってきたので、もう少しやり続けようと思います。