Help us understand the problem. What is going on with this article?

Substrate ink!でlockdropの実装を試す

サマリー

  • substrateノードを自分のローカルで実行する
  • Substrateのスマートコントラクトink!でlockdropを実装する
  • 自分で立てたノードにwasmをアップロードして、実行する

環境

  • wsl Ubuntu 18.04
$ cargo -V
cargo 1.44.0 (05d080faa 2020-05-06)

$ cargo contract -V
cargo-contract 0.6.1

$ substrate -V
substrate 2.0.0-rc3-44978b9b1-x86_64-linux-gnu

$ rustc -V
rustc 1.44.0 (49cae5576 2020-06-01)

ノードの準備、環境準備

どんなlockdropにするか

  • デプロイ時に独自トークン(ERC20)を発行します。
  • lock時にlockする時間を設定します。
  • lockしたのと同量のトークンをlock者に送信します。
  • lock時間を経過していない場合はunlockをコールしても返金されない。
  • lock時間を経過していれば、unlockコールで返金される。

ソースコード

  • ※エラー処理をどうすれば良いか正しく理解出来ていません。
  • ※substrate内での正しい演算の仕方が理解出来ていません。
  • ※一旦は、期待通りに動くものになりました。
#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract(version = "0.1.0")]
mod lockdrop {
    //    use ink_core::memory::string::String;
    use ink_core::storage;
    use scale::{Decode, Encode};

    /// Defines the storage of your contract.
    /// Add new fields to the below struct in order
    /// to add new static storage fields to your contract.
    #[ink(storage)]
    struct Lockdrop {
        /// Stores a single `bool` value on the storage.
        total_supply: storage::Value<Balance>,
        balances: storage::HashMap<AccountId, Balance>,
        lock_balance: storage::HashMap<AccountId, Balance>,
        lock_time: storage::HashMap<AccountId, Timestamp>,
        //name: String,
    }

    // #[ink(storage)]
    // struct LockData {
    //     balance: Balance,
    //     time: Timestamp,
    // }

    #[ink(event)]
    struct Transfer {
        #[ink(topic)]
        from: Option<AccountId>,
        #[ink(topic)]
        to: Option<AccountId>,
        #[ink(topic)]
        value: Balance,
    }

    #[derive(Encode, Decode, Debug, PartialEq, Eq, Copy, Clone)]
    #[cfg_attr(feature = "ink-generate-abi", derive(type_metadata::Metadata))]
    pub enum Error {
        NotEnoughBalance,
        NotSpendLockTime,
        NoValue,
        SendFailed,
    }

    #[ink(event)]
    struct Lock {
        #[ink(topic)]
        from: Option<AccountId>,
        #[ink(topic)]
        value: Balance,
    }

    #[ink(event)]
    struct UnLock {
        #[ink(topic)]
        to: Option<AccountId>,
        #[ink(topic)]
        value: Balance,
    }

    impl Lockdrop {
        /// constructor
        #[ink(constructor)]
        fn new(&mut self, initial_supply: Balance) {
            let caller = self.env().caller();
            self.total_supply.set(initial_supply);
            self.balances.insert(caller, initial_supply);
            //self.name = token_name;
            self.env().emit_event(Transfer {
                from: None,
                to: Some(caller),
                value: initial_supply,
            });
        }

        /// get total_supply of token
        #[ink(message)]
        fn total_supply(&self) -> Balance {
            *self.total_supply
        }

        /// get my token balance
        #[ink(message)]
        fn balance_of_token(&self, owner: AccountId) -> Balance {
            self.balance_of_or_zero(&owner)
        }

        /// get my lock balance
        #[ink(message)]
        fn balance_of_lock(&self, owner: AccountId) -> Balance {
            let balance = *self.lock_balance.get(&owner).unwrap_or(&0);
            balance
        }

        /// lock function
        #[ink(message)]
        fn lock(&mut self, milliseconds: u64) -> Result<(), Error> {
            if self.total_supply < self.env().transferred_balance() {
                return Err(Error::NotEnoughBalance);
            }

            let to_time = self.env().block_timestamp() + milliseconds;

            self.lock_balance
                .insert(self.env().caller(), self.env().transferred_balance());
            self.lock_time.insert(self.env().caller(), to_time);
            self.balances
                .insert(self.env().caller(), self.env().transferred_balance());
            self.total_supply
                .set(*self.total_supply.get() - self.env().transferred_balance());
            Ok(())
        }

        fn balance_of_or_zero(&self, owner: &AccountId) -> Balance {
            *self.balances.get(owner).unwrap_or(&0)
        }

        fn get_lock_time(&self, owner: &AccountId) -> Timestamp {
            *self.lock_time.get(&owner).unwrap_or(&0)
        }

        #[ink(message)]
        fn pub_get_lock_time(&self, owner: AccountId) -> Timestamp {
            *self.lock_time.get(&owner).unwrap_or(&0)
        }

        #[ink(message)]
        fn pub_get_block_time(&self) -> Timestamp {
            self.env().block_timestamp()
        }

        /// unlock function
        #[ink(message)]
        fn unlock(&mut self) -> Result<(), Error> {
            if self.env().block_timestamp() < self.get_lock_time(&self.env().caller()) {
                return Err(Error::NotSpendLockTime);
            }
            if let Err(e) = self.send_unlock() {
                return Err(e);
            }
            self.lock_balance.remove(&self.env().caller());
            Ok(())
        }

        fn send_unlock(&self) -> Result<(), Error> {
            //let lock_b: Balance = self.balance_of_lock(self.env().caller());
            //let lock_b: Balance;
            // let lock_b: u128;
            // match self.lock_balance.get(&self.env().caller()).cloned() {
            //     Some(result) => lock_b = result,
            //     None => return Err(Error::NoValue),
            // }
            if let Err(_) = self.env().transfer(
                self.env().caller(),
                self.balance_of_lock(self.env().caller()),
            ) {
                return Err(Error::SendFailed);
            }
            Ok(())
        }
    }

    /// Unit tests in Rust are normally defined within such a `#[cfg(test)]`
    /// module and test functions are marked with a `#[test]` attribute.
    /// The below code is technically just normal Rust code.
    #[cfg(test)]
    mod tests {
        /// Imports all the definitions from the outer scope so we can use them here.
        use super::*;
        use ink_core::env;

        /// We test a simple use case of our contract.
        #[test]
        fn lock() {
            let accounts =
                env::test::default_accounts::<env::DefaultEnvTypes>().expect("Cannot get accounts");
            let mut lockdrop = Lockdrop::new(9999999999);
            assert_eq!(lockdrop.lock(30), Ok(()));
        }
    }
}

まとめと今後

  • エラー処理や演算方法等、もう少しink!になれないといけない課題が見付かったので追い付いていきたいです。
  • 僕の注目しているPlasm Nodeでも試していきたいです。
  • それとは別に自由研究として取り組んでいる「Active KYC」も設計していきたいと思います。
realtakahashi
Fintechベンチャーで働いています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした