러스트 코드 많다그래서 공부할 겸 이 파트 한다했는데...... 파트 1, 2로 나눌 정도로 분량 많을 줄이야...... 허허허
이 튜토리얼에서 Substrate kitties라 불리는 NFT를 생성하고 소유권을 다루는 블록체인을 만드는 법을 배울 겁니다.
파트 1에서는 당신이 만든 키티들과 상호작용할 수 있는 기능을 포함해서 키티 팔레트를 만드는 방법을 다룹니다.
파트 2에서는 파트 1에서 만든 Substarte Kitties 블록체인과 상호작용하도록 프론트엔드를 개발하는 방법을 다룹니다.
시작하기 전에 설치할 것
참고
우리가 만드는 것
우리가 다루지 않는 것
커스텀 팔렛을 설정하고 간단한 스토리지 아이템을 포함하는 섭스트레이트 노드 템플릿을 사용하는 기본적인 패턴을 다룹니다.
당신의 템플릿 노드 설정하기
섭스트레이트 노드 템플릿에 네트워킹과 합희 계층이 포함되어 있기 때문에, 런타임과 팔렛의 로직을 만드는 것에만 집중하면 됩니다.
시작하기 위해서, 우리는 프로젝트 이름과 의존성을 설정해야 합니다. kickstart라는 CLI 도구로 쉽게 노드템플릿의 이름을 바꾸겠습니다.
cargo install kickstart
프로젝트를 시작하고 싶은 루트 디렉토리에서
kickstart https://github.com/sacha-l/kickstart-substrate
: 제일 최근의 노드 템플릿 복사본을 클론해오고 노드와 팔레트 이름 설정하게 함
kitties(node 폴더 안에 생김), kitties(pallet 폴더 안에 생김)
좋아하는 IDE로 kitties 디렉토리를 열고 이름을 kitties-tutorial로 바꿈
kickstart가 수정한 디렉토리들
5.runtime/src/lib.rs에서 커스텀 팔렛의 이름을 TemplateModule에서 SubstrateKitties로 수정
construct_runtime!(
// --snip
{
// --snip
SubstrateKitties: pallet_kitties,
}
);
kitties-tutorial <-- The name of our project directory
|
+-- node
|
+-- pallets
| |
| +-- kitties
| |
| +-- Cargo.toml
| |
| +-- src
| |
| +-- benchmarking.rs <-- Remove file
| |
| +-- lib.rs <-- Remove contents
| |
| +-- mock.rs <-- Remove file
| |
| +-- tests.rs <-- Remove file
|
+-- Cargo.toml
섭스트레이트에서 팔렛은 런타임 로직을 정의하는 데 사용되고, 우리는 섭스트레이트 키티들을 관리하는 모든 로직을 하나의 팔렛에 만들 것
모든 FRAME 팔렛은
#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::{
sp_runtime::traits::{Hash, Zero},
dispatch::{DispatchResultWithPostInfo, DispatchResult},
traits::{Currency, ExistenceRequirement, Randomness},
pallet_prelude::*
};
use frame_system::pallet_prelude::*;
use sp_io::hashing::blake2_128;
// TODO Part II: Struct for holding Kitty information.
// TODO Part II: Enum and implementation to handle Gender type in Kitty struct.
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
/// Configure the pallet by specifying the parameters and types it depends on.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// The Currency handler for the Kitties pallet.
type Currency: Currency<Self::AccountId>;
// TODO Part II: Specify the custom types for our runtime.
}
// Errors.
#[pallet::error]
pub enum Error<T> {
// TODO Part III
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
// TODO Part III
}
// ACTION: Storage item to keep a count of all existing Kitties.
// TODO Part II: Remaining storage items.
// TODO Part III: Our pallet's genesis configuration.
#[pallet::call]
impl<T: Config> Pallet<T> {
// TODO Part III: create_kitty
// TODO Part III: set_price
// TODO Part III: transfer
// TODO Part III: buy_kitty
// TODO Part III: breed_kitty
}
// TODO Part II: helper function for Kitty struct
impl<T: Config> Pallet<T> {
// TODO Part III: helper functions for dispatchable functions
// TODO: increment_nonce, random_hash, mint, transfer_from
}
}
sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.20" }
cargo build -p pallet-kitties
우리의 런타임에 변수를 저장하는 함수 로직을 추가해보자
storage macro에 의존하는 trait인 섭스트레이트 storage API에 있는 StorageValue를 사용할 것
우리가 선언하고 싶은 어떤 스토리지 아이템에 대해서 우리는 #[pallet::storage] 매크로를 먼저 써줘야 함
pallet/kitties/src/lib.rs 파일에서 Action 부분을 다음으로 교체
#[pallet::storage]
#[pallet::getter(fn count_for_kitties)]
pub(super) type CountForKitties<T: Config> = StorageValue<_, u64, ValueQuery>;
: 이것으로 총 존재하는 키티들의 개수를 우리의 팔렛에 추적하기 위한 스토리지 아이템을 생성
우리의 노드를 만들기 전에, currency type을 팔렛 런타임 구현에 추가해야 함
runtime/src/lib.rs 에 다음 부분을 추가
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances; // <-- Add this line
}
이제 진짜로 노드를 빌드하고 에러가 없는지 확인하기
cargo build --release
pallet/kitties/src/lib.rs 파일을 다음 코드로 교체
#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use frame_support::{
sp_runtime::traits::Hash,
traits::{ Randomness, Currency, tokens::ExistenceRequirement },
transactional
};
use sp_io::hashing::blake2_128;
#[cfg(feature = "std")]
use frame_support::serde::{Deserialize, Serialize};
// ACTION #1: Write a Struct to hold Kitty information.
// ACTION #2: Enum declaration for Gender.
// ACTION #3: Implementation to handle Gender type in Kitty struct.
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
/// Configure the pallet by specifying the parameters and types it depends on.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// The Currency handler for the Kitties pallet.
type Currency: Currency<Self::AccountId>;
// ACTION #5: Specify the type for Randomness we want to specify for runtime.
// ACTION #9: Add MaxKittyOwned constant
}
// Errors.
#[pallet::error]
pub enum Error<T> {
// TODO Part III
}
// Events.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
// TODO Part III
}
#[pallet::storage]
#[pallet::getter(fn count_for_kitties)]
pub(super) type CountForKitties<T: Config> = StorageValue<_, u64, ValueQuery>;
// ACTION #7: Remaining storage items.
// TODO Part IV: Our pallet's genesis configuration.
#[pallet::call]
impl<T: Config> Pallet<T> {
// TODO Part III: create_kitty
// TODO Part IV: set_price
// TODO Part IV: transfer
// TODO Part IV: buy_kitty
// TODO Part IV: breed_kitty
}
//** Our helper functions.**//
impl<T: Config> Pallet<T> {
// ACTION #4: helper function for Kitty struct
// TODO Part III: helper functions for dispatchable functions
// ACTION #6: function to randomly generate DNA
// TODO Part III: mint
// TODO Part IV: transfer_kitty_to
}
}
serde를 팔렛의 Cargo.toml파일에 추가하기
맞는 버전 찾아서!!
kitty struct 구조
하나의 Kitty가 가지고 있어야 하는 정보
구조체 선언하기 전에 BalanceOf와 AccountOf를 추가해줘야 함
Action1 부분을 다음으로 교체
type AccountOf<T> = <T as frame_system::Config>::AccountId;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
// Struct for holding Kitty information.
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[codec(mel_bound())]
pub struct Kitty<T: Config> {
pub dna: [u8; 16],
pub price: Option<BalanceOf<T>>,
pub gender: Gender,
pub owner: AccountOf<T>,
}
이 파일 맨 위에
use scale_info::TypeInfo;
이 줄 추가해서 우리의 구조체가 이 trait에 접근할 수 있도록 해주기
Gender 커스텀 타입 작성
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum Gender {
Male,
Female,
}
enum 선언하기 전에 dereive 매크로 써줘야 함
우리 런타임의 다른 타입들과 소통하려면 이렇게 감싸줘야 함?
Action4를 다음 코드로 교체
fn gen_gender() -> Gender {
let random = T::KittyRandomness::random(&b"gender"[..]).0;
match random.as_ref()[0] % 2 {
0 => Gender::Male,
_ => Gender::Female,
}
}
on-chain 무작위성 구현
위에서 아직 정의하지 않은 KittyRandomness라는 걸 사용했음 이제 알아볼까
frame_support에 있는 Randomness trait을 사용할 것
type KittyRandomness: Randomness<Self::Hash, Self::BlockNumber>;
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances;
type KittyRandomness = RandomnessCollectiveFlip; // <-- ACTION: add this line.
}
fn gen_dna() -> [u8; 16] {
let payload = (
T::KittyRandomness::random(&b"dna"[..]).0,
<frame_system::Pallet<T>>::extrinsic_index().unwrap_or_default(),
<frame_system::Pallet<T>>::block_number(),
);
payload.using_encoded(blake2_128)
}
남은 스토리지 아이템들 작성
모든 고양이들을 쉽게 추적하기 위해서 유일한 아이디를 우리의 스토리지 아이템의 글로벌 키로 사용하는 로직을 표준화할 것임
새 고양이의 아이디는 항상 유일해야 함
해시값을 아이디로 갖고 그에 대한 고양이 오브젝트를 맵핑하는 Kitties 스토리지 아이템을 사용할 것임
Action 7을 교체
런타임은
Kitties라는 스토리지 맵으로 유일한 자산인 currency나 kitties
#[pallet::storage]
#[pallet::getter(fn kitties)]
pub(super) type Kitties<T: Config> = StorageMap<
_,
Twox64Concat,
T::Hash,
Kitty<T>,
>;
그 자산들의 소유권인 계정 아이디를 KittiesOwned라는 스토리지 맵으로 다룸
#[pallet::storage]
#[pallet::getter(fn kitties_owned)]
/// Keeps track of what accounts own what Kitty.
pub(super) type KittiesOwned<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
BoundedVec<T::Hash, T::MaxKittyOwned>,
ValueQuery,
>;
FRAME이 제공하는 StorageMap으로 Kitty 구조체의 스토리지 인스턴스 생성할 것
ACtion 9를 다음 줄로 교체
#[pallet::constant]
type MaxKittyOwned: Get<u32>;
MaxKittyowned라는 타입을 config trait에 추가
parameter_types! { // <- add this macro
// One can own at most 9,999 Kitties
pub const MaxKittyOwned: u32 = 9999;
}
/// Configure the pallet-kitties in pallets/kitties.
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances;
type KittyRandomness = RandomnessCollectiveFlip;
type MaxKittyOwned = MaxKittyOwned; // <- add this line
}
이 코드를 추가해라~~
컴파일 되는지 확인!
cargo build --release
Public and Private 함수들
최적화를 위해 복잡한 로직을 private 도우미 함수로 쪼갤 것
private함수는 다양한 dispatchavle함수들에 의해 호출 가능
create_kitty는 dispatchable 함수 또는 extrinsic임: origin이 sign받았는지 확인, 사인한 계정으로 랜덤 해시 생성, 그걸로 새로운 고양이 오브젝트 만들고 private mint()함수 호출
mint는 private helper function: 그 고양이가 이미 존재하지 않는지 확인, 새 고양이 아이디로 스토리지 업데이트, 스토리지에 있는 새 총 고양이 개수와 새 주인의 계정 업데이트, 고양이가 성공적으로 만들어졌다ㅡㄴ 신호에 대한 이벤트 쌓기
create_kitty dispatchable 작성하기
FRAME에서 dispatchable은 항상 같은 구조임
모든 팔렛 dispatchables는 #[pallet::call] 매크로 아래에 있고, impl<T: Config>Pallet<T> {}로 선언되어야 함
서브스트레이트의 weighting system은 개발자들이 연산 복잡도를 매 extrinsic에 대해 생각하도록 강제한다. 그래서 노드가 최악의 실행시간을 가정하게 하고 구체적인 블록 시간보다 더 오래 걸리는 extrinsic들과 네트워킹하는 것을 피하게 한다. 무게는 어떠한 사인된 extrinsic에 대해서도 수수료 시스템과도 내부적으로 연결되어 있음
pallets/kitties/src/lib.rs에서 action1을 다음으로 교체
#[pallet::weight(100)]
pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult {
let sender = ensure_signed(origin)?; // <- add this line
let kitty_id = Self::mint(&sender, None, None)?; // <- add this line
// Logging to the console
log::info!("A kitty is born with ID: {:?}.", kitty_id); // <- add this line
// ACTION #4: Deposit `Created` event
Ok(())
}
로깅하기 위해서 맞는 버전의 log::info Cargo.toml파일에 추가하기
mint() 함수 작성하기
Result<T::Hash, Error\<\T>>를 리턴
Action 2부분을 다음으로 교체
// Helper to mint a Kitty.
pub fn mint(
owner: &T::AccountId,
dna: Option<[u8; 16]>,
gender: Option<Gender>,
) -> Result<T::Hash, Error<T>> {
let kitty = Kitty::<T> {
dna: dna.unwrap_or_else(Self::gen_dna),
price: None,
gender: gender.unwrap_or_else(Self::gen_gender),
owner: owner.clone(),
};
let kitty_id = T::Hashing::hash_of(&kitty);
// Performs this operation first as it may fail
let new_cnt = Self::count_for_kitties().checked_add(1)
.ok_or(<Error<T>>::CountForKittiesOverflow)?;
// Check if the kitty does not already exist in our storage map
ensure!(Self::kitties(&kitty_id) == None, <Error<T>>::KittyExists);
// Performs this operation first because as it may fail
<KittiesOwned<T>>::try_mutate(&owner, |kitty_vec| {
kitty_vec.try_push(kitty_id)
}).map_err(|_| <Error<T>>::ExceedMaxKittyOwned)?;
<Kitties<T>>::insert(kitty_id, kitty);
<CountForKitties<T>>::put(new_cnt);
Ok(kitty_id)
}
먼저 새 고양이 오브젝트를 생성
유일한 kitty_id를 고양이의 현재 특성에 기초한 해시 함수를 사용해 생성
CountForKitties를 스토리지 게터함수인 Self::count_for_kitties()를 사용해 증가시키고
check_add()함수로 오버플로우 확인
kitty_id가 이미 존재하지는 않는지 검사
검사 다 통과하면 스토리지 아이템을 업데이트:
try_mutate로 고양이 주인 벡터 업데이트
섭스트레이트 스토리지 맵 API가 제공하는 insert 메소드로 실제 고양이 오브젝트를 저장하고 kitty_id와 연관시키기
StorageValue API에 의해 제공된 put으로 최신 고양이 개수 저장
pallet Events 구현하기
우리 팔렛은 함수 마지막에 이벤트도 발생시킬 수 있음
FRAME의 #[pallet::event]를 통해서 쉽게 팔렛의 이벤트를 관리하고 선언할 수 있음, FRAME 매크로로 이벤트를 enum 선언처럼 할 수 있음
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config>{
/// A function succeeded. [time, day]
Success(T::Time, T::Day),
}
attribute macro인 #[pallet::generate_deposit(pub(super) fn deposit_event)]을 사용할 수 있음
이를 통해 특정 이벤트를 적립할 수 있음, ㅇSelf::deposit_event(Event::Success(var_time, var_day));
이런 패턴을 통해서!
우리 팔렛의 설정 trait Config 안에다 새로운 연관 타입인 Event를 추가해줘야 함
그리고 runtime/src/lib.rs에도 추가해줘야 함
``rust
/// Configure the pallet by specifying the parameters and types it depends on.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event> + IsType<::Event>;
//--snip--//
}
Action3을 다음으로 교체해서 이벤트 선언해라
```rust
// A new Kitty was successfully created. \[sender, kitty_id\]
Created(T::AccountId, T::Hash),
/// Kitty price was successfully set. \[sender, kitty_id, new_price\]
PriceSet(T::AccountId, T::Hash, Option<BalanceOf<T>>),
/// A Kitty was successfully transferred. \[from, to, kitty_id\]
Transferred(T::AccountId, T::AccountId, T::Hash),
/// A Kitty was successfully bought. \[buyer, seller, kitty_id, bid_price\]
Bought(T::AccountId, T::AccountId, T::Hash, BalanceOf<T>),
Action 4를 다음으로 교체해서 완성해라
Self::deposit_event(Event::Created(sender, kitty_id));
에러 처리
FRAME을 통해 [#pallet::error]로 우리 팔렛에 에러를 구체화하고 팔렛 함수 안에서 사용할 수 있게 해줌
#[pallet::error]아래에 FRAME 매크로를 사용해서 가능한 모든 에러를 Action5줄로 교체
/// Handles arithmetic overflow when incrementing the Kitty counter.
CountForKittiesOverflow,
/// An account cannot own more Kitties than `MaxKittyCount`.
ExceedMaxKittyOwned,
/// Buyer cannot be the owner.
BuyerIsKittyOwner,
/// Cannot transfer a kitty to its owner.
TransferToSelf,
/// This kitty already exists
KittyExists,
/// This kitty doesn't exist
KittyNotExist,
/// Handles checking that the Kitty is owned by the account transferring, buying or setting a price for it.
NotKittyOwner,
/// Ensures the Kitty is for sale.
KittyNotForSale,
/// Ensures that the buying price is greater than the asking price.
KittyBidPriceTooLow,
/// Ensures that an account has enough funds to purchase a Kitty.
NotEnoughBalance,
다 잘 빌드되는지 확인
cargo build --release
Polkadat-JS Apps UI로 테스트하기
루트 디렉토리에서 다음 실행
./target/release/node-kitties --tmp --dev
Developer에서 extrinsc으로 가서 사인된 extrincic을 substrateKitties를 사용해서 사인하고 제출하는데, createKitty 디스패쳐블 호출해서 해라. 앨리스, 밥, 찰리의 계정으로 해봐라
이벤트로 가서 Created찾고 블록 디테일 이벤트 실행된 거 확인가능해야 함
chain state가서 substrateKitties팔렛 가서 Kitties(hash)Kitty 쿼리 실행해서 디테일 확인
include option체크 안 한 상태에서만 다음 정보 볼 수 있음
고양이 가격 설정
kitty.price = new_price.clone();
<Kitties<T>>::insert(&kitty_id, kitty);
// Deposit a "PriceSet" event.
Self::deposit_event(Event::PriceSet(sender, kitty_id, new_price));
고양이 교환
transfer(): 디스패쳐블 함수, publickly 호출가능하 다스패처블
transfer_kitty_to() : private helper function. transfer사 호출, 고양이 교환할 때 모든 스토리지 업데이트 담당
action 4
#[pallet::weight(100)]
pub fn transfer(
origin: OriginFor<T>,
to: T::AccountId,
kitty_id: T::Hash
) -> DispatchResult {
let from = ensure_signed(origin)?;
// Ensure the kitty exists and is called by the kitty owner
ensure!(Self::is_kitty_owner(&kitty_id, &from)?, <Error<T>>::NotKittyOwner);
// Verify the kitty is not transferring back to its owner.
ensure!(from != to, <Error<T>>::TransferToSelf);
// Verify the recipient has the capacity to receive one more kitty
let to_owned = <KittiesOwned<T>>::get(&to);
ensure!((to_owned.len() as u32) < T::MaxKittyOwned::get(), <Error<T>>::ExceedMaxKittyOwned);
Self::transfer_kitty_to(&kitty_id, &to)?;
Self::deposit_event(Event::Transferred(from, to, kitty_id));
Ok(())
}
트랜잭션 사인됐나 확인하고
고양이 주인이 보냈나 확인하고
주인에게 보내는 거 아닌가 확인하고
받는 사람이 하나 이상의 고양이 가질 수 있는지 확인하고
transfer_kitty_to함수 실행
Action5
#[transactional]
pub fn transfer_kitty_to(
kitty_id: &T::Hash,
to: &T::AccountId,
) -> Result<(), Error<T>> {
let mut kitty = Self::kitties(&kitty_id).ok_or(<Error<T>>::KittyNotExist)?;
let prev_owner = kitty.owner.clone();
// Remove `kitty_id` from the KittiesOwned vector of `prev_owner`
<KittiesOwned<T>>::try_mutate(&prev_owner, |owned| {
if let Some(ind) = owned.iter().position(|&id| id == *kitty_id) {
owned.swap_remove(ind);
return Ok(());
}
Err(())
}).map_err(|_| <Error<T>>::KittyNotExist)?;
// Update the kitty owner
kitty.owner = to.clone();
// Reset the ask price so the kitty is not for sale until `set_price()` is called
// by the current owner.
kitty.price = None;
<Kitties<T>>::insert(kitty_id, kitty);
<KittiesOwned<T>>::try_mutate(to, |vec| {
vec.try_push(*kitty_id)
}).map_err(|_| <Error<T>>::ExceedMaxKittyOwned)?;
Ok(())
}
#[transactional]은 오직 ok를 리턴할 때만 스토리지 바꿀 수 있도록 해줌
고양이 구매
그 고양이가 판매중인지 확인
현재가격이 사용자의 예산에 있는지와 충분한 free balance를 가지고 있는지 확인
ACtion 6 교체
// Check the kitty is for sale and the kitty ask price <= bid_price
if let Some(ask_price) = kitty.price {
ensure!(ask_price <= bid_price, <Error<T>>::KittyBidPriceTooLow);
} else {
Err(<Error<T>>::KittyNotForSale)?;
}
// Check the buyer has enough free balance
ensure!(T::Currency::free_balance(&buyer) >= bid_price, <Error<T>>::NotEnoughBalance);
고ㅑㅇ이 받을 공간 있나 확인
Action7다음으로 교체
// Verify the buyer has the capacity to receive one more kitty
let to_owned = <KittiesOwned<T>>::get(&buyer);
ensure!((to_owned.len() as u32) < T::MaxKittyOwned::get(), <Error<T>>::ExceedMaxKittyOwned);
let seller = kitty.owner.clone();
Action 8
// Transfer the amount from buyer to seller
T::Currency::transfer(&buyer, &seller, bid_price, ExistenceRequirement::KeepAlive)?;
// Transfer the kitty from seller to buyer
Self::transfer_kitty_to(&kitty_id, &buyer)?;
// Deposit relevant Event
Self::deposit_event(Event::Bought(buyer, seller, kitty_id, bid_price));
고양이 기르기
ACtion 9
let new_dna = Self::breed_dna(&parent1, &parent2)?;
ACtino 10
Self::mint(&sender, Some(new_dna), None)?;
제네시스 설정
초기 상태 설정해주기
FRAME's #[pallet::genesis_config] 사용
제네시스 블록에 어떤 고양이 오브젝트 선언하도록 허용?
ACtion 11
// Our pallet's genesis configuration.
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub kitties: Vec<(T::AccountId, [u8; 16], Gender)>,
}
// Required to implement default for GenesisConfig.
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig {
fn default() -> GenesisConfig {
GenesisConfig { kitties: vec![] }
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild for GenesisConfig {
fn build(&self) {
// When building a kitty from genesis config, we require the dna and gender to be supplied.
for (acct, dna, gender) in &self.kitties {
let _ = <Pallet>::mint(acct, Some(dna.clone()), Some(gender.clone()));
}
}
}
node/src/chain_spec.rs로 가서 맨 위에 use node_kitties_runtime::SubstrateKittiesConfig; 추가하고 testnet_genesis함수에 추가
substrate_kitties: SubstrateKittiesConfig {
kitties: vec![],
},
빌드, 실행, 상호작용하기
cargo build --release
./target/release/node-kitties --dev --tmp
Fund multiple users with tokens so they can all participate
Have each user create multiple Kitties
Try to transfer a Kitty from one user to another using the right and wrong owner
Try to set the price of a Kitty using the right and wrong owner
Buy a Kitty using an owner and another user
Use too little funds to purchase a Kitty
Overspend on the cost of the Kitty and ensure that the balance is reduced appropriately
Breed a Kitty and check that the new DNA is a mix of the old and new