<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>coding_baby_adult.log</title>
        <link>https://velog.io/</link>
        <description>풀스택 웹개발자👩‍💻✨️</description>
        <lastBuildDate>Sun, 03 Jul 2022 05:16:15 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>coding_baby_adult.log</title>
            <url>https://velog.velcdn.com/images/myungha_cho/profile/d8c87368-5d09-4524-a932-4160b6d1ca4f/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. coding_baby_adult.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/myungha_cho" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Custom Balances Pallet 작성]]></title>
            <link>https://velog.io/@myungha_cho/Custom-Balances-Pallet-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@myungha_cho/Custom-Balances-Pallet-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Sun, 03 Jul 2022 05:16:15 GMT</pubDate>
            <description><![CDATA[<h1 id="1-dispatchable">1. Dispatchable</h1>
<h2 id="set_balance">set_balance()</h2>
<pre><code class="language-rust"> // 명하
        #[pallet::weight] 
        pub fn set_balance(
            origin: OriginFor&lt;T&gt;, 
            who: &lt;T::LookUp as StaticLookUp&gt;::Source, 
            #[pallet::compact] new_free: T::Balance, 
            #[pallet:compact] new_reserved: T::Balance,
        ) {
                // check the origin === root
                ensure_root(origin)?;
                let who = T::LookUp::lookup(who)?;
                let existential_deposit = T::ExistentialDeposit::get();

                // get current target and his balance
                let wipeout = new_free + new_reserved &lt; existential_deposit;
                let new_free = if wipeout {Zero::zero()} else {new_free};
                let new_reseved = if wipeout {Zero::zero()} else {new_reserved};

                // calculate new free/reseved balance &gt; existential deposit
                let (old_free, old_reserved) = Self::mutate_account(&amp;who, |account| {
                    let old_free = account.free;
                    let old_reserved = account.reseved;

                    account.free = new_free;
                    account.reserved - new_reserved;

                    (old_free, old_reserved)
                })?;

                // change total issuance
                if new_free &gt;  old_free {
                    mem::drop(PositiveImbalance::&lt;T, I&gt;::new(new_free - old_free));
                }else if new_free &lt; old_free {
                    mem::drop(NegativeImbalance::&lt;T, I&gt;::new(old_free - new_free));
                }


                if new_reseved &gt; old_reserved {
                    mem::drop(PositiveImbalance::&lt;T, I&gt;::new(new_reseved - old_reserved));
                }else if new_reseved &lt; old_reserved {
                    mem::drop(NegativeImbalance::&lt;T, I&gt;::new(old_reserved - new_reseved));
                }

                // trigger deposit event
                Self::deposit_event(Event::BalacneSet {who, free:new_free, reseved:new_reserved });
                Ok(().into())
            }</code></pre>
<ul>
<li>set_balance_creating(), set_balance_killing()</li>
</ul>
<pre><code class="language-rust">// Storage: System Account (r:1 w:1)
    fn set_balance_creating() -&gt; Weight {
        (22_279_000 as Weight)
            .saturating_add(T::DbWeight::get().reads(1 as Weight))
            .saturating_add(T::DbWeight::get().writes(1 as Weight))
    }
    // Storage: System Account (r:1 w:1)
    fn set_balance_killing() -&gt; Weight {
        (25_488_000 as Weight)
            .saturating_add(T::DbWeight::get().reads(1 as Weight))
            .saturating_add(T::DbWeight::get().writes(1 as Weight))
    }</code></pre>
<ul>
<li><code>#[pallet::compact]</code> :  좀 더 메모리를 적게 차지하는 방식으로 인코딩하고 싶을 때 사용</li>
<li><code>&lt;T::LookUp as StaticLookUp&gt;::Source</code> : Source 타입을 받아서 AccountId로 바꿀 수 있게 해줌</li>
<li>ensure_root() :  origin이 root이면 ok, 아니면 error 리턴</li>
<li>T::ExistentialDeposit::get(); : exsistentialDeposit 값 리턴</li>
<li>Zefo::zero() : existential deposit 보다 작은 값 넣으려고 하면 아예 0으로 설정해버림</li>
<li>account 는 갑자기 어디서 튀어나온 거?</li>
</ul>
<h1 id="2-internalexternal-functions">2. Internal/External Functions</h1>
<h2 id="usable_balance">usable_balance()</h2>
<pre><code class="language-rust"> pub fn usable_balance(who: impl sp_std::borrow::Borrow&lt;T::AccountId&gt;) -&gt; T::Balance {
            // usable balance 가져오기
            self.account(whoe.borrow()).usable(Reasons::Misc)

        }</code></pre>
<ul>
<li>self.account:는 어디에 선언되어있는거?</li>
</ul>
<h2 id="mutate_account">mutate_account()</h2>
<pre><code class="language-rust">// 명하
        // account의 balance 값을 업데이트. 기존에 있는 계정인지 헤크해야 함
        pub fn mutate_account&lt;R&gt;(who:&amp;T::AccountId, f: impl FnOnce(&amp;mut AccountData&lt;T::Balance&gt; -&gt; R)) -&gt; Result&lt;R, DispatchError&gt; {
            Self::try_mutate_account(who, |a, _| -&gt; Result&lt;R, DispatchError&gt; {
                Ok(f(a))
            })
        }</code></pre>
<ul>
<li>FnOnce: 한 번만 호출될 수 있음</li>
<li>|a,_|: a는 또 어디서 튀어나온거</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[static in Rust]]></title>
            <link>https://velog.io/@myungha_cho/static</link>
            <guid>https://velog.io/@myungha_cho/static</guid>
            <pubDate>Mon, 27 Jun 2022 02:48:42 GMT</pubDate>
            <description><![CDATA[<h1 id="constants-유형">constants 유형</h1>
<p>러스트에는 두 가지 종류의 상수 타입이 있는데,</p>
<h2 id="1-const는">1. <code>const</code>는</h2>
<p>변하지 않는 값을 담는 타입이고, </p>
<h2 id="2-static은">2. <code>static</code>은</h2>
<ul>
<li><strong>&#39;static 라이프타임</strong>을 가지고 있는 </li>
<li>값이 변할 수도 있는 변수를 담는 타입입니다.</li>
</ul>
<p>static 키워드는 글로벌 변수를 선언하는데, 이 변수들은 read-only 메모리 영역에 위치하기 때문에 값을 수정하는 것이 금지됩니다. </p>
<h1 id="사용법">사용법</h1>
<h2 id="1-참조값">1. 참조값</h2>
<p>참조 라이프타임을 나타내는 &#39;static은 해당 참조가 가리키는 데이터가 프로그램 전체 라이프타임 동안 유효하다는 것을 의미합니다.</p>
<pre><code class="language-rust">static NUM: i32 = 18;

let s: &amp;&#39;static str = &quot;hello world&quot;;</code></pre>
<h2 id="2-trait-bound">2. Trait bound</h2>
<p>만약에 어떠한 레퍼런스의 라이프타임이 &#39;static 으로 명시되어 있다면 해당 레퍼런스는 프로그램의 전체 실행 시간 동안 존재하는 데이터를 레퍼런스 한다는 의미가 됩니다. 
그래서 해당 값을 받는 쪽에서 원하는 만큼 해당 타입을 사용할 수 있습니다. 소유한 데이터를 넘길 때 &#39;static 라이프타임 바운드를 갖습니다.</p>
<pre><code class="language-rust">fn print_it( input: impl Debug + &#39;static ) {
    println!( &quot;&#39;static value passed in is: {:?}&quot;, input );
}

fn main() {
    // i is owned and contains no references, thus it&#39;s &#39;static:
    let i = 5;
    print_it(i);

    // oops, &amp;i only has the lifetime defined by the scope of
    // main(), so it&#39;s not &#39;static:
    print_it(&amp;i);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Substrate Balance Pallet 분석]]></title>
            <link>https://velog.io/@myungha_cho/Substrate-Balance-Pallet-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@myungha_cho/Substrate-Balance-Pallet-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Tue, 21 Jun 2022 00:43:59 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/paritytech/substrate/blob/master/frame/balances/src/lib.rs">깃허브</a></p>
<h1 id="balances-pallet">Balances Pallet</h1>
<p>account와 balances를 다루는 기능 제공</p>
<h2 id="기능">기능</h2>
<ul>
<li>free balances 가져오고 설정하기</li>
<li>전체, reseved, unreserved balances 검색</li>
<li>reserved balances을 해당 계좌로 보내기</li>
<li>계좌들 사이의 송금 not reseved</li>
<li>계좌 잔액 줄이기</li>
<li>계좌 생성과 제거</li>
<li>총 발행량 관리</li>
<li>잠금 설정 관리</li>
</ul>
<h3 id="용어">용어</h3>
<ul>
<li><p>존재예금Existential Deposit : 한 계좌를 만들고 유지하기 위해 필요한 최소한의 잔액. 이를 통해 스토리지를 채우는 &quot;먼지 계정&quot;들을 방지할 수 있다. 무료 + 예약된 잔액이 이 금액보다 낮아지면 그 계좌는 죽은 것으로 여겨진다. 기능뿐만 아니라 이전 내역까지 체인 스토리지에서 지워진다. </p>
</li>
<li><p>총 발행량: 시스템에 있는 통화의 총 발행량</p>
</li>
<li><p>계정 수익 : 논스를 재설정해서 계정을 삭제하는 행동. 총 잔액이 0이 된 후에 발생한다(엄격하게 말하자면, 존재 예그몹다 적은 경우에)</p>
</li>
<li><p>무료 잔고 : 예약되지 않은 잔고의 비율. 무료잔고는 대부분의 동작에 중요한 유일한 잔고이다.</p>
</li>
<li><p>예약 잔고 : 예약 잔고는 여전히 계좌 주인에게 속해 있지만, 지연되어 있다. 예약 잔고는 여전히 지워질 수 있지만, 오직 무료 잔고가 지워진 후에만 가능하다.</p>
</li>
<li><p>불균형 : 일부 자금이 동일하고 반대되는 회계(총 발행 잔액과 계정 잔액의 차) 없이 신용 또는 차변되는 조건. 불균형을 초래하는 함수는 런타임 로직 안에서 관리할 수 있는 Imbalance trait 객체를 반환(불균형이 해소되면 총 발행과 같은 book-keeping을 자동으로 유지해야 함)</p>
</li>
<li><p>잠금: 특정 블록 번호까지 한 계정의 free balances를 특정량 잠그는 것. 동일한 자본에 대해 여러 개의 잠금이 가능하므로 스택이 아니라 오버레이된다.</p>
</li>
</ul>
<h3 id="구현">구현</h3>
<p>Balances 팔레트는 다음과 같은 trait들의 구현을 제공하므로 중복해서 구현할 필요 없다.</p>
<ul>
<li><code>Currency</code>(frame_support::traits::Currency) : 복제 가능한 자산 시스템을 다루는 함수</li>
<li><code>ReservableCurrency</code>(frame_support::traits::ReservableCurrency)</li>
<li><code>NamedReservableCurrency</code>(frame_support::traits::NamedReservableCurrency) : 한 계정에 예약될 수 있는 자산들을 다루는 함수</li>
<li><code>LockableCurrency</code>(frame_support::traits::LockableCurrency) : 유동성 제한을 허용하는 계정들을 다루는 함수</li>
<li><code>Imbalance</code>(frame_support::traits::Imbalance) : 총 발행량과 계정 잔액의 불균형을 다루는 함수. 함수가 새로운 기금을 만들거나 어떤 기금을 파괴할 때 사용되어야만 함</li>
</ul>
<h2 id="인터페이스">인터페이스</h2>
<h3 id="dispatchable-functions">Dispatchable Functions</h3>
<ul>
<li><code>transfer</code> - 무료 잔액(liquid free balance)을 다른 계정으로 옮김</li>
<li><code>set_balance</code> - 주어진 계정의 잔액을 설정. 반드시 루트가 호출해야만 함</li>
</ul>
<h2 id="사용법">사용법</h2>
<h3 id="frame-예시">FRAME 예시</h3>
<ul>
<li>Contract Pallet는 가스비 지불을 위해<code>Currency</code> trait을 사용하고, 그 타입은 <code>Currency</code>에서 상속받음</li>
</ul>
<pre><code class="language-rust">use frame_support::traits::Currency;
pub trait Config: frame_system::Config {
     type Currency: Currency&lt;Self::AccountId&gt;;
}

pub type BalanceOf&lt;T&gt; = &lt;&lt;T as Config&gt;::Currency as Currency&lt;&lt;T as frame_system::Config&gt;::AccountId&gt;&gt;::Balance;
pub type NegativeImbalanceOf&lt;T&gt; = &lt;&lt;T as Config&gt;::Currency as Currency&lt;&lt;T as frame_system::Config&gt;::AccountId&gt;&gt;::NegativeImbalance;

fn main() {}</code></pre>
<ul>
<li>Staking pallet는 숨겨진 계정의 자금을 잠그기 위해 <code>LockableCurrency</code> trait을 사용</li>
</ul>
<pre><code class="language-rust">use frame_support::traits::{WithdrawReasons, LockableCurrency};
use sp_runtime::traits::Bounded;
pub trait Config: frame_system::Config {
     type Currency: LockableCurrency&lt;Self::AccountId, Moment=Self::BlockNumber&gt;;
 }
struct StakingLedger&lt;T: Config&gt; {
     stash: &lt;T as frame_system::Config&gt;::AccountId,
     total: &lt;&lt;T as Config&gt;::Currency as frame_support::traits::Currency&lt;&lt;T as frame_system::Config&gt;::AccountId&gt;&gt;::Balance,
     phantom: std::marker::PhantomData&lt;T&gt;,
 }
 const STAKING_ID: [u8; 8] = *b&quot;staking &quot;;

 fn update_ledger&lt;T: Config&gt;(
     controller: &amp;T::AccountId,
     ledger: &amp;StakingLedger&lt;T&gt;
 ) {
     T::Currency::set_lock(
         STAKING_ID,
         &amp;ledger.stash,
         ledger.total,
         WithdrawReasons::all()
     );
     // &lt;Ledger&lt;T&gt;&gt;::insert(controller, ledger); 
    // Commented out as we don&#39;t have access to Staking&#39;s storage here.
 }
 fn main() {}</code></pre>
<h2 id="초기-설정">초기 설정</h2>
<p>Balances pallet는 <code>GenesisConfig</code>에 의존</p>
<h2 id="assumptions">Assumptions</h2>
<p>모든 계정의 총 발행된 잔액의 합은<code>Config::Balance::max_value()</code> 보다 작아야 함</p>
<h1 id="실제-코드">실제 코드</h1>
<pre><code class="language-rust">#![cfg_attr(not(feature = &quot;std&quot;), no_std)]

#[macro_use]
mod tests;
mod benchmarking;
mod tests_composite;
mod tests_local;
#[cfg(test)]
mod tests_reentrancy;
pub mod weights;

pub use self::imbalances::{NegativeImbalance, PositiveImbalance};
use codec::{Codec, Decode, Encode, MaxEncodedLen};
#[cfg(feature = &quot;std&quot;)]
use frame_support::traits::GenesisBuild;
use frame_support::{
    ensure,
    pallet_prelude::DispatchResult,
    traits::{
        tokens::{fungible, BalanceStatus as Status, DepositConsequence, WithdrawConsequence},
        Currency, DefensiveSaturating, ExistenceRequirement,
        ExistenceRequirement::{AllowDeath, KeepAlive},
        Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency, OnUnbalanced,
        ReservableCurrency, SignedImbalance, StoredMap, TryDrop, WithdrawReasons,
    },
    WeakBoundedVec,
};
use frame_system as system;
use scale_info::TypeInfo;
use sp_runtime::{
    traits::{
        AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize,
        Saturating, StaticLookup, Zero,
    },
    ArithmeticError, DispatchError, RuntimeDebug,
};
use sp_std::{cmp, fmt::Debug, mem, ops::BitOr, prelude::*, result};
pub use weights::WeightInfo;

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
    use super::*;
    use frame_support::pallet_prelude::*;
    use frame_system::pallet_prelude::*;

    #[pallet::config]
    pub trait Config&lt;I: &#39;static = ()&gt;: frame_system::Config {
        /// The balance of an account.
        type Balance: Parameter
            + Member
            + AtLeast32BitUnsigned
            + Codec
            + Default
            + Copy
            + MaybeSerializeDeserialize
            + Debug
            + MaxEncodedLen
            + TypeInfo;

        /// Handler for the unbalanced reduction when removing a dust account.
        type DustRemoval: OnUnbalanced&lt;NegativeImbalance&lt;Self, I&gt;&gt;;

        /// The overarching event type.
        type Event: From&lt;Event&lt;Self, I&gt;&gt; + IsType&lt;&lt;Self as frame_system::Config&gt;::Event&gt;;

        /// The minimum amount required to keep an account open.
        #[pallet::constant]
        type ExistentialDeposit: Get&lt;Self::Balance&gt;;

        /// The means of storing the balances of an account.
        type AccountStore: StoredMap&lt;Self::AccountId, AccountData&lt;Self::Balance&gt;&gt;;

        /// Weight information for extrinsics in this pallet.
        type WeightInfo: WeightInfo;

        /// The maximum number of locks that should exist on an account.
        /// Not strictly enforced, but used for weight estimation.
        #[pallet::constant]
        type MaxLocks: Get&lt;u32&gt;;

        /// The maximum number of named reserves that can exist on an account.
        #[pallet::constant]
        type MaxReserves: Get&lt;u32&gt;;

        /// The id type for named reserves.
        type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
    }

    #[pallet::pallet]
    #[pallet::generate_store(pub(super) trait Store)]
    pub struct Pallet&lt;T, I = ()&gt;(PhantomData&lt;(T, I)&gt;);

    #[pallet::call]
    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Pallet&lt;T, I&gt; {
        /// Transfer some liquid free balance to another account.
        ///
        /// `transfer` will set the `FreeBalance` of the sender and receiver.
        /// If the sender&#39;s account is below the existential deposit as a result
        /// of the transfer, the account will be reaped.
        ///
        /// The dispatch origin for this call must be `Signed` by the transactor.
        ///
        /// # &lt;weight&gt;
        /// - Dependent on arguments but not critical, given proper implementations for input config
        ///   types. See related functions below.
        /// - It contains a limited number of reads and writes internally and no complex
        ///   computation.
        ///
        /// Related functions:
        ///
        ///   - `ensure_can_withdraw` is always called internally but has a bounded complexity.
        ///   - Transferring balances to accounts that did not exist before will cause
        ///     `T::OnNewAccount::on_new_account` to be called.
        ///   - Removing enough funds from an account will trigger `T::DustRemoval::on_unbalanced`.
        ///   - `transfer_keep_alive` works the same way as `transfer`, but has an additional check
        ///     that the transfer will not kill the origin account.
        /// ---------------------------------
        /// - Origin account is already in memory, so no DB operations for them.
        /// # &lt;/weight&gt;
        #[pallet::weight(T::WeightInfo::transfer())]
        pub fn transfer(
            origin: OriginFor&lt;T&gt;,
            dest: &lt;T::Lookup as StaticLookup&gt;::Source,
            #[pallet::compact] value: T::Balance,
        ) -&gt; DispatchResultWithPostInfo {
            let transactor = ensure_signed(origin)?;
            let dest = T::Lookup::lookup(dest)?;
            &lt;Self as Currency&lt;_&gt;&gt;::transfer(
                &amp;transactor,
                &amp;dest,
                value,
                ExistenceRequirement::AllowDeath,
            )?;
            Ok(().into())
        }

        /// Set the balances of a given account.
        ///
        /// This will alter `FreeBalance` and `ReservedBalance` in storage. it will
        /// also alter the total issuance of the system (`TotalIssuance`) appropriately.
        /// If the new free or reserved balance is below the existential deposit,
        /// it will reset the account nonce (`frame_system::AccountNonce`).
        ///
        /// The dispatch origin for this call is `root`.
        #[pallet::weight(
            T::WeightInfo::set_balance_creating() // Creates a new account.
                .max(T::WeightInfo::set_balance_killing()) // Kills an existing account.
        )]
        pub fn set_balance(
            origin: OriginFor&lt;T&gt;,
            who: &lt;T::Lookup as StaticLookup&gt;::Source,
            #[pallet::compact] new_free: T::Balance,
            #[pallet::compact] new_reserved: T::Balance,
        ) -&gt; DispatchResultWithPostInfo {
            ensure_root(origin)?;
            let who = T::Lookup::lookup(who)?;
            let existential_deposit = T::ExistentialDeposit::get();

            let wipeout = new_free + new_reserved &lt; existential_deposit;
            let new_free = if wipeout { Zero::zero() } else { new_free };
            let new_reserved = if wipeout { Zero::zero() } else { new_reserved };

            // First we try to modify the account&#39;s balance to the forced balance.
            let (old_free, old_reserved) = Self::mutate_account(&amp;who, |account| {
                let old_free = account.free;
                let old_reserved = account.reserved;

                account.free = new_free;
                account.reserved = new_reserved;

                (old_free, old_reserved)
            })?;

            // This will adjust the total issuance, which was not done by the `mutate_account`
            // above.
            if new_free &gt; old_free {
                mem::drop(PositiveImbalance::&lt;T, I&gt;::new(new_free - old_free));
            } else if new_free &lt; old_free {
                mem::drop(NegativeImbalance::&lt;T, I&gt;::new(old_free - new_free));
            }

            if new_reserved &gt; old_reserved {
                mem::drop(PositiveImbalance::&lt;T, I&gt;::new(new_reserved - old_reserved));
            } else if new_reserved &lt; old_reserved {
                mem::drop(NegativeImbalance::&lt;T, I&gt;::new(old_reserved - new_reserved));
            }

            Self::deposit_event(Event::BalanceSet { who, free: new_free, reserved: new_reserved });
            Ok(().into())
        }

        /// Exactly as `transfer`, except the origin must be root and the source account may be
        /// specified.
        /// # &lt;weight&gt;
        /// - Same as transfer, but additional read and write because the source account is not
        ///   assumed to be in the overlay.
        /// # &lt;/weight&gt;
        #[pallet::weight(T::WeightInfo::force_transfer())]
        pub fn force_transfer(
            origin: OriginFor&lt;T&gt;,
            source: &lt;T::Lookup as StaticLookup&gt;::Source,
            dest: &lt;T::Lookup as StaticLookup&gt;::Source,
            #[pallet::compact] value: T::Balance,
        ) -&gt; DispatchResultWithPostInfo {
            ensure_root(origin)?;
            let source = T::Lookup::lookup(source)?;
            let dest = T::Lookup::lookup(dest)?;
            &lt;Self as Currency&lt;_&gt;&gt;::transfer(
                &amp;source,
                &amp;dest,
                value,
                ExistenceRequirement::AllowDeath,
            )?;
            Ok(().into())
        }

        /// Same as the [`transfer`] call, but with a check that the transfer will not kill the
        /// origin account.
        ///
        /// 99% of the time you want [`transfer`] instead.
        ///
        /// [`transfer`]: struct.Pallet.html#method.transfer
        #[pallet::weight(T::WeightInfo::transfer_keep_alive())]
        pub fn transfer_keep_alive(
            origin: OriginFor&lt;T&gt;,
            dest: &lt;T::Lookup as StaticLookup&gt;::Source,
            #[pallet::compact] value: T::Balance,
        ) -&gt; DispatchResultWithPostInfo {
            let transactor = ensure_signed(origin)?;
            let dest = T::Lookup::lookup(dest)?;
            &lt;Self as Currency&lt;_&gt;&gt;::transfer(&amp;transactor, &amp;dest, value, KeepAlive)?;
            Ok(().into())
        }

        /// Transfer the entire transferable balance from the caller account.
        ///
        /// NOTE: This function only attempts to transfer _transferable_ balances. This means that
        /// any locked, reserved, or existential deposits (when `keep_alive` is `true`), will not be
        /// transferred by this function. To ensure that this function results in a killed account,
        /// you might need to prepare the account by removing any reference counters, storage
        /// deposits, etc...
        ///
        /// The dispatch origin of this call must be Signed.
        ///
        /// - `dest`: The recipient of the transfer.
        /// - `keep_alive`: A boolean to determine if the `transfer_all` operation should send all
        ///   of the funds the account has, causing the sender account to be killed (false), or
        ///   transfer everything except at least the existential deposit, which will guarantee to
        ///   keep the sender account alive (true). # &lt;weight&gt;
        /// - O(1). Just like transfer, but reading the user&#39;s transferable balance first.
        ///   #&lt;/weight&gt;
        #[pallet::weight(T::WeightInfo::transfer_all())]
        pub fn transfer_all(
            origin: OriginFor&lt;T&gt;,
            dest: &lt;T::Lookup as StaticLookup&gt;::Source,
            keep_alive: bool,
        ) -&gt; DispatchResult {
            use fungible::Inspect;
            let transactor = ensure_signed(origin)?;
            let reducible_balance = Self::reducible_balance(&amp;transactor, keep_alive);
            let dest = T::Lookup::lookup(dest)?;
            let keep_alive = if keep_alive { KeepAlive } else { AllowDeath };
            &lt;Self as Currency&lt;_&gt;&gt;::transfer(&amp;transactor, &amp;dest, reducible_balance, keep_alive)?;
            Ok(())
        }

        /// Unreserve some balance from a user by force.
        ///
        /// Can only be called by ROOT.
        #[pallet::weight(T::WeightInfo::force_unreserve())]
        pub fn force_unreserve(
            origin: OriginFor&lt;T&gt;,
            who: &lt;T::Lookup as StaticLookup&gt;::Source,
            amount: T::Balance,
        ) -&gt; DispatchResult {
            ensure_root(origin)?;
            let who = T::Lookup::lookup(who)?;
            let _leftover = &lt;Self as ReservableCurrency&lt;_&gt;&gt;::unreserve(&amp;who, amount);
            Ok(())
        }
    }

    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt; {
        /// An account was created with some free balance.
        Endowed { account: T::AccountId, free_balance: T::Balance },
        /// An account was removed whose balance was non-zero but below ExistentialDeposit,
        /// resulting in an outright loss.
        DustLost { account: T::AccountId, amount: T::Balance },
        /// Transfer succeeded.
        Transfer { from: T::AccountId, to: T::AccountId, amount: T::Balance },
        /// A balance was set by root.
        BalanceSet { who: T::AccountId, free: T::Balance, reserved: T::Balance },
        /// Some balance was reserved (moved from free to reserved).
        Reserved { who: T::AccountId, amount: T::Balance },
        /// Some balance was unreserved (moved from reserved to free).
        Unreserved { who: T::AccountId, amount: T::Balance },
        /// Some balance was moved from the reserve of the first account to the second account.
        /// Final argument indicates the destination balance type.
        ReserveRepatriated {
            from: T::AccountId,
            to: T::AccountId,
            amount: T::Balance,
            destination_status: Status,
        },
        /// Some amount was deposited (e.g. for transaction fees).
        Deposit { who: T::AccountId, amount: T::Balance },
        /// Some amount was withdrawn from the account (e.g. for transaction fees).
        Withdraw { who: T::AccountId, amount: T::Balance },
        /// Some amount was removed from the account (e.g. for misbehavior).
        Slashed { who: T::AccountId, amount: T::Balance },
    }

    #[pallet::error]
    pub enum Error&lt;T, I = ()&gt; {
        /// Vesting balance too high to send value
        VestingBalance,
        /// Account liquidity restrictions prevent withdrawal
        LiquidityRestrictions,
        /// Balance too low to send value
        InsufficientBalance,
        /// Value too low to create account due to existential deposit
        ExistentialDeposit,
        /// Transfer/payment would kill account
        KeepAlive,
        /// A vesting schedule already exists for this account
        ExistingVestingSchedule,
        /// Beneficiary account must pre-exist
        DeadAccount,
        /// Number of named reserves exceed MaxReserves
        TooManyReserves,
    }

    /// The total units issued in the system.
    #[pallet::storage]
    #[pallet::getter(fn total_issuance)]
    pub type TotalIssuance&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt; = StorageValue&lt;_, T::Balance, ValueQuery&gt;;

    /// The Balances pallet example of storing the balance of an account.
    ///
    /// # Example
    ///
    /// ```nocompile
    ///  impl pallet_balances::Config for Runtime {
    ///    type AccountStore = StorageMapShim&lt;Self::Account&lt;Runtime&gt;, frame_system::Provider&lt;Runtime&gt;, AccountId, Self::AccountData&lt;Balance&gt;&gt;
    ///  }
    /// ```
    ///
    /// You can also store the balance of an account in the `System` pallet.
    ///
    /// # Example
    ///
    /// ```nocompile
    ///  impl pallet_balances::Config for Runtime {
    ///   type AccountStore = System
    ///  }
    /// ```
    ///
    /// But this comes with tradeoffs, storing account balances in the system pallet stores
    /// `frame_system` data alongside the account data contrary to storing account balances in the
    /// `Balances` pallet, which uses a `StorageMap` to store balances data only.
    /// NOTE: This is only used in the case that this pallet is used to store balances.
    #[pallet::storage]
    pub type Account&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt; =
        StorageMap&lt;_, Blake2_128Concat, T::AccountId, AccountData&lt;T::Balance&gt;, ValueQuery&gt;;

    /// Any liquidity locks on some account balances.
    /// NOTE: Should only be accessed when setting, changing and freeing a lock.
    #[pallet::storage]
    #[pallet::getter(fn locks)]
    pub type Locks&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt; = StorageMap&lt;
        _,
        Blake2_128Concat,
        T::AccountId,
        WeakBoundedVec&lt;BalanceLock&lt;T::Balance&gt;, T::MaxLocks&gt;,
        ValueQuery,
    &gt;;

    /// Named reserves on some account balances.
    #[pallet::storage]
    #[pallet::getter(fn reserves)]
    pub type Reserves&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt; = StorageMap&lt;
        _,
        Blake2_128Concat,
        T::AccountId,
        BoundedVec&lt;ReserveData&lt;T::ReserveIdentifier, T::Balance&gt;, T::MaxReserves&gt;,
        ValueQuery,
    &gt;;

    /// Storage version of the pallet.
    ///
    /// This is set to v2.0.0 for new networks.
    #[pallet::storage]
    pub(super) type StorageVersion&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt; =
        StorageValue&lt;_, Releases, ValueQuery&gt;;

    #[pallet::genesis_config]
    pub struct GenesisConfig&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt; {
        pub balances: Vec&lt;(T::AccountId, T::Balance)&gt;,
    }

    #[cfg(feature = &quot;std&quot;)]
    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Default for GenesisConfig&lt;T, I&gt; {
        fn default() -&gt; Self {
            Self { balances: Default::default() }
        }
    }

    #[pallet::genesis_build]
    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; GenesisBuild&lt;T, I&gt; for GenesisConfig&lt;T, I&gt; {
        fn build(&amp;self) {
            let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &amp;(_, n)| acc + n);
            &lt;TotalIssuance&lt;T, I&gt;&gt;::put(total);

            &lt;StorageVersion&lt;T, I&gt;&gt;::put(Releases::V2_0_0);

            for (_, balance) in &amp;self.balances {
                assert!(
                    *balance &gt;= &lt;T as Config&lt;I&gt;&gt;::ExistentialDeposit::get(),
                    &quot;the balance of any account should always be at least the existential deposit.&quot;,
                )
            }

            // ensure no duplicates exist.
            let endowed_accounts = self
                .balances
                .iter()
                .map(|(x, _)| x)
                .cloned()
                .collect::&lt;std::collections::BTreeSet&lt;_&gt;&gt;();

            assert!(
                endowed_accounts.len() == self.balances.len(),
                &quot;duplicate balances in genesis.&quot;
            );

            for &amp;(ref who, free) in self.balances.iter() {
                assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() })
                    .is_ok());
            }
        }
    }
}

#[cfg(feature = &quot;std&quot;)]
impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; GenesisConfig&lt;T, I&gt; {
    /// Direct implementation of `GenesisBuild::build_storage`.
    ///
    /// Kept in order not to break dependency.
    pub fn build_storage(&amp;self) -&gt; Result&lt;sp_runtime::Storage, String&gt; {
        &lt;Self as GenesisBuild&lt;T, I&gt;&gt;::build_storage(self)
    }

    /// Direct implementation of `GenesisBuild::assimilate_storage`.
    ///
    /// Kept in order not to break dependency.
    pub fn assimilate_storage(&amp;self, storage: &amp;mut sp_runtime::Storage) -&gt; Result&lt;(), String&gt; {
        &lt;Self as GenesisBuild&lt;T, I&gt;&gt;::assimilate_storage(self, storage)
    }
}

/// Simplified reasons for withdrawing balance.
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub enum Reasons {
    /// Paying system transaction fees.
    Fee = 0,
    /// Any reason other than paying system transaction fees.
    Misc = 1,
    /// Any reason at all.
    All = 2,
}

impl From&lt;WithdrawReasons&gt; for Reasons {
    fn from(r: WithdrawReasons) -&gt; Reasons {
        if r == WithdrawReasons::TRANSACTION_PAYMENT {
            Reasons::Fee
        } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) {
            Reasons::All
        } else {
            Reasons::Misc
        }
    }
}

impl BitOr for Reasons {
    type Output = Reasons;
    fn bitor(self, other: Reasons) -&gt; Reasons {
        if self == other {
            return self
        }
        Reasons::All
    }
}

/// A single lock on a balance. There can be many of these on an account and they &quot;overlap&quot;, so the
/// same balance is frozen by multiple locks.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct BalanceLock&lt;Balance&gt; {
    /// An identifier for this lock. Only one lock may be in existence for each identifier.
    pub id: LockIdentifier,
    /// The amount which the free balance may not drop below when this lock is in effect.
    pub amount: Balance,
    /// If true, then the lock remains in effect even for payment of transaction fees.
    pub reasons: Reasons,
}

/// Store named reserved balance.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct ReserveData&lt;ReserveIdentifier, Balance&gt; {
    /// The identifier for the named reserve.
    pub id: ReserveIdentifier,
    /// The amount of the named reserve.
    pub amount: Balance,
}

/// All balance information for an account.
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)]
pub struct AccountData&lt;Balance&gt; {
    /// Non-reserved part of the balance. There may still be restrictions on this, but it is the
    /// total pool what may in principle be transferred, reserved and used for tipping.
    ///
    /// This is the only balance that matters in terms of most operations on tokens. It
    /// alone is used to determine the balance when in the contract execution environment.
    pub free: Balance,
    /// Balance which is reserved and may not be used at all.
    ///
    /// This can still get slashed, but gets slashed last of all.
    ///
    /// This balance is a &#39;reserve&#39; balance that other subsystems use in order to set aside tokens
    /// that are still &#39;owned&#39; by the account holder, but which are suspendable.
    /// This includes named reserve and unnamed reserve.
    pub reserved: Balance,
    /// The amount that `free` may not drop below when withdrawing for *anything except transaction
    /// fee payment*.
    pub misc_frozen: Balance,
    /// The amount that `free` may not drop below when withdrawing specifically for transaction
    /// fee payment.
    pub fee_frozen: Balance,
}

impl&lt;Balance: Saturating + Copy + Ord&gt; AccountData&lt;Balance&gt; {
    /// How much this account&#39;s balance can be reduced for the given `reasons`.
    fn usable(&amp;self, reasons: Reasons) -&gt; Balance {
        self.free.saturating_sub(self.frozen(reasons))
    }
    /// The amount that this account&#39;s free balance may not be reduced beyond for the given
    /// `reasons`.
    fn frozen(&amp;self, reasons: Reasons) -&gt; Balance {
        match reasons {
            Reasons::All =&gt; self.misc_frozen.max(self.fee_frozen),
            Reasons::Misc =&gt; self.misc_frozen,
            Reasons::Fee =&gt; self.fee_frozen,
        }
    }
    /// The total balance in this account including any that is reserved and ignoring any frozen.
    fn total(&amp;self) -&gt; Balance {
        self.free.saturating_add(self.reserved)
    }
}

// A value placed in storage that represents the current version of the Balances storage.
// This value is used by the `on_runtime_upgrade` logic to determine whether we run
// storage migration logic. This should match directly with the semantic versions of the Rust crate.
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
enum Releases {
    V1_0_0,
    V2_0_0,
}

impl Default for Releases {
    fn default() -&gt; Self {
        Releases::V1_0_0
    }
}

pub struct DustCleaner&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt;(
    Option&lt;(T::AccountId, NegativeImbalance&lt;T, I&gt;)&gt;,
);

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Drop for DustCleaner&lt;T, I&gt; {
    fn drop(&amp;mut self) {
        if let Some((who, dust)) = self.0.take() {
            Pallet::&lt;T, I&gt;::deposit_event(Event::DustLost { account: who, amount: dust.peek() });
            T::DustRemoval::on_unbalanced(dust);
        }
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Pallet&lt;T, I&gt; {
    /// Get the free balance of an account.
    pub fn free_balance(who: impl sp_std::borrow::Borrow&lt;T::AccountId&gt;) -&gt; T::Balance {
        Self::account(who.borrow()).free
    }

    /// Get the balance of an account that can be used for transfers, reservations, or any other
    /// non-locking, non-transaction-fee activity. Will be at most `free_balance`.
    pub fn usable_balance(who: impl sp_std::borrow::Borrow&lt;T::AccountId&gt;) -&gt; T::Balance {
        Self::account(who.borrow()).usable(Reasons::Misc)
    }

    /// Get the balance of an account that can be used for paying transaction fees (not tipping,
    /// or any other kind of fees, though). Will be at most `free_balance`.
    pub fn usable_balance_for_fees(who: impl sp_std::borrow::Borrow&lt;T::AccountId&gt;) -&gt; T::Balance {
        Self::account(who.borrow()).usable(Reasons::Fee)
    }

    /// Get the reserved balance of an account.
    pub fn reserved_balance(who: impl sp_std::borrow::Borrow&lt;T::AccountId&gt;) -&gt; T::Balance {
        Self::account(who.borrow()).reserved
    }

    /// Get both the free and reserved balances of an account.
    fn account(who: &amp;T::AccountId) -&gt; AccountData&lt;T::Balance&gt; {
        T::AccountStore::get(who)
    }

    /// Handles any steps needed after mutating an account.
    ///
    /// This includes DustRemoval unbalancing, in the case than the `new` account&#39;s total balance
    /// is non-zero but below ED.
    ///
    /// Returns two values:
    /// - `Some` containing the the `new` account, iff the account has sufficient balance.
    /// - `Some` containing the dust to be dropped, iff some dust should be dropped.
    fn post_mutation(
        _who: &amp;T::AccountId,
        new: AccountData&lt;T::Balance&gt;,
    ) -&gt; (Option&lt;AccountData&lt;T::Balance&gt;&gt;, Option&lt;NegativeImbalance&lt;T, I&gt;&gt;) {
        let total = new.total();
        if total &lt; T::ExistentialDeposit::get() {
            if total.is_zero() {
                (None, None)
            } else {
                (None, Some(NegativeImbalance::new(total)))
            }
        } else {
            (Some(new), None)
        }
    }

    fn deposit_consequence(
        _who: &amp;T::AccountId,
        amount: T::Balance,
        account: &amp;AccountData&lt;T::Balance&gt;,
        mint: bool,
    ) -&gt; DepositConsequence {
        if amount.is_zero() {
            return DepositConsequence::Success
        }

        if mint &amp;&amp; TotalIssuance::&lt;T, I&gt;::get().checked_add(&amp;amount).is_none() {
            return DepositConsequence::Overflow
        }

        let new_total_balance = match account.total().checked_add(&amp;amount) {
            Some(x) =&gt; x,
            None =&gt; return DepositConsequence::Overflow,
        };

        if new_total_balance &lt; T::ExistentialDeposit::get() {
            return DepositConsequence::BelowMinimum
        }

        // NOTE: We assume that we are a provider, so don&#39;t need to do any checks in the
        // case of account creation.

        DepositConsequence::Success
    }

    fn withdraw_consequence(
        who: &amp;T::AccountId,
        amount: T::Balance,
        account: &amp;AccountData&lt;T::Balance&gt;,
    ) -&gt; WithdrawConsequence&lt;T::Balance&gt; {
        if amount.is_zero() {
            return WithdrawConsequence::Success
        }

        if TotalIssuance::&lt;T, I&gt;::get().checked_sub(&amp;amount).is_none() {
            return WithdrawConsequence::Underflow
        }

        let new_total_balance = match account.total().checked_sub(&amp;amount) {
            Some(x) =&gt; x,
            None =&gt; return WithdrawConsequence::NoFunds,
        };

        // Provider restriction - total account balance cannot be reduced to zero if it cannot
        // sustain the loss of a provider reference.
        // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes,
        // then this will need to adapt accordingly.
        let ed = T::ExistentialDeposit::get();
        let success = if new_total_balance &lt; ed {
            if frame_system::Pallet::&lt;T&gt;::can_dec_provider(who) {
                WithdrawConsequence::ReducedToZero(new_total_balance)
            } else {
                return WithdrawConsequence::WouldDie
            }
        } else {
            WithdrawConsequence::Success
        };

        // Enough free funds to have them be reduced.
        let new_free_balance = match account.free.checked_sub(&amp;amount) {
            Some(b) =&gt; b,
            None =&gt; return WithdrawConsequence::NoFunds,
        };

        // Eventual free funds must be no less than the frozen balance.
        let min_balance = account.frozen(Reasons::All);
        if new_free_balance &lt; min_balance {
            return WithdrawConsequence::Frozen
        }

        success
    }

    /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
    /// `ExistentialDeposit` law, annulling the account as needed.
    ///
    /// NOTE: Doesn&#39;t do any preparatory work for creating a new account, so should only be used
    /// when it is known that the account already exists.
    ///
    /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
    /// the caller will do this.
    pub fn mutate_account&lt;R&gt;(
        who: &amp;T::AccountId,
        f: impl FnOnce(&amp;mut AccountData&lt;T::Balance&gt;) -&gt; R,
    ) -&gt; Result&lt;R, DispatchError&gt; {
        Self::try_mutate_account(who, |a, _| -&gt; Result&lt;R, DispatchError&gt; { Ok(f(a)) })
    }

    /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
    /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the
    /// result of `f` is an `Err`.
    ///
    /// NOTE: Doesn&#39;t do any preparatory work for creating a new account, so should only be used
    /// when it is known that the account already exists.
    ///
    /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
    /// the caller will do this.
    fn try_mutate_account&lt;R, E: From&lt;DispatchError&gt;&gt;(
        who: &amp;T::AccountId,
        f: impl FnOnce(&amp;mut AccountData&lt;T::Balance&gt;, bool) -&gt; Result&lt;R, E&gt;,
    ) -&gt; Result&lt;R, E&gt; {
        Self::try_mutate_account_with_dust(who, f).map(|(result, dust_cleaner)| {
            drop(dust_cleaner);
            result
        })
    }

    /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce
    /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the
    /// result of `f` is an `Err`.
    ///
    /// It returns both the result from the closure, and an optional `DustCleaner` instance which
    /// should be dropped once it is known that all nested mutates that could affect storage items
    /// what the dust handler touches have completed.
    ///
    /// NOTE: Doesn&#39;t do any preparatory work for creating a new account, so should only be used
    /// when it is known that the account already exists.
    ///
    /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
    /// the caller will do this.
    fn try_mutate_account_with_dust&lt;R, E: From&lt;DispatchError&gt;&gt;(
        who: &amp;T::AccountId,
        f: impl FnOnce(&amp;mut AccountData&lt;T::Balance&gt;, bool) -&gt; Result&lt;R, E&gt;,
    ) -&gt; Result&lt;(R, DustCleaner&lt;T, I&gt;), E&gt; {
        let result = T::AccountStore::try_mutate_exists(who, |maybe_account| {
            let is_new = maybe_account.is_none();
            let mut account = maybe_account.take().unwrap_or_default();
            f(&amp;mut account, is_new).map(move |result| {
                let maybe_endowed = if is_new { Some(account.free) } else { None };
                let maybe_account_maybe_dust = Self::post_mutation(who, account);
                *maybe_account = maybe_account_maybe_dust.0;
                (maybe_endowed, maybe_account_maybe_dust.1, result)
            })
        });
        result.map(|(maybe_endowed, maybe_dust, result)| {
            if let Some(endowed) = maybe_endowed {
                Self::deposit_event(Event::Endowed { account: who.clone(), free_balance: endowed });
            }
            let dust_cleaner = DustCleaner(maybe_dust.map(|dust| (who.clone(), dust)));
            (result, dust_cleaner)
        })
    }

    /// Update the account entry for `who`, given the locks.
    fn update_locks(who: &amp;T::AccountId, locks: &amp;[BalanceLock&lt;T::Balance&gt;]) {
        let bounded_locks = WeakBoundedVec::&lt;_, T::MaxLocks&gt;::force_from(
            locks.to_vec(),
            Some(&quot;Balances Update Locks&quot;),
        );

        if locks.len() as u32 &gt; T::MaxLocks::get() {
            log::warn!(
                target: &quot;runtime::balances&quot;,
                &quot;Warning: A user has more currency locks than expected. \
                A runtime configuration adjustment may be needed.&quot;
            );
        }
        // No way this can fail since we do not alter the existential balances.
        let res = Self::mutate_account(who, |b| {
            b.misc_frozen = Zero::zero();
            b.fee_frozen = Zero::zero();
            for l in locks.iter() {
                if l.reasons == Reasons::All || l.reasons == Reasons::Misc {
                    b.misc_frozen = b.misc_frozen.max(l.amount);
                }
                if l.reasons == Reasons::All || l.reasons == Reasons::Fee {
                    b.fee_frozen = b.fee_frozen.max(l.amount);
                }
            }
        });
        debug_assert!(res.is_ok());

        let existed = Locks::&lt;T, I&gt;::contains_key(who);
        if locks.is_empty() {
            Locks::&lt;T, I&gt;::remove(who);
            if existed {
                // TODO: use Locks::&lt;T, I&gt;::hashed_key
                // https://github.com/paritytech/substrate/issues/4969
                system::Pallet::&lt;T&gt;::dec_consumers(who);
            }
        } else {
            Locks::&lt;T, I&gt;::insert(who, bounded_locks);
            if !existed &amp;&amp; system::Pallet::&lt;T&gt;::inc_consumers_without_limit(who).is_err() {
                // No providers for the locks. This is impossible under normal circumstances
                // since the funds that are under the lock will themselves be stored in the
                // account and therefore will need a reference.
                log::warn!(
                    target: &quot;runtime::balances&quot;,
                    &quot;Warning: Attempt to introduce lock consumer reference, yet no providers. \
                    This is unexpected but should be safe.&quot;
                );
            }
        }
    }

    /// Move the reserved balance of one account into the balance of another, according to `status`.
    ///
    /// Is a no-op if:
    /// - the value to be moved is zero; or
    /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
    fn do_transfer_reserved(
        slashed: &amp;T::AccountId,
        beneficiary: &amp;T::AccountId,
        value: T::Balance,
        best_effort: bool,
        status: Status,
    ) -&gt; Result&lt;T::Balance, DispatchError&gt; {
        if value.is_zero() {
            return Ok(Zero::zero())
        }

        if slashed == beneficiary {
            return match status {
                Status::Free =&gt; Ok(Self::unreserve(slashed, value)),
                Status::Reserved =&gt; Ok(value.saturating_sub(Self::reserved_balance(slashed))),
            }
        }

        let ((actual, _maybe_one_dust), _maybe_other_dust) = Self::try_mutate_account_with_dust(
            beneficiary,
            |to_account, is_new| -&gt; Result&lt;(T::Balance, DustCleaner&lt;T, I&gt;), DispatchError&gt; {
                ensure!(!is_new, Error::&lt;T, I&gt;::DeadAccount);
                Self::try_mutate_account_with_dust(
                    slashed,
                    |from_account, _| -&gt; Result&lt;T::Balance, DispatchError&gt; {
                        let actual = cmp::min(from_account.reserved, value);
                        ensure!(best_effort || actual == value, Error::&lt;T, I&gt;::InsufficientBalance);
                        match status {
                            Status::Free =&gt;
                                to_account.free = to_account
                                    .free
                                    .checked_add(&amp;actual)
                                    .ok_or(ArithmeticError::Overflow)?,
                            Status::Reserved =&gt;
                                to_account.reserved = to_account
                                    .reserved
                                    .checked_add(&amp;actual)
                                    .ok_or(ArithmeticError::Overflow)?,
                        }
                        from_account.reserved -= actual;
                        Ok(actual)
                    },
                )
            },
        )?;

        Self::deposit_event(Event::ReserveRepatriated {
            from: slashed.clone(),
            to: beneficiary.clone(),
            amount: actual,
            destination_status: status,
        });
        Ok(actual)
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; fungible::Inspect&lt;T::AccountId&gt; for Pallet&lt;T, I&gt; {
    type Balance = T::Balance;

    fn total_issuance() -&gt; Self::Balance {
        TotalIssuance::&lt;T, I&gt;::get()
    }
    fn minimum_balance() -&gt; Self::Balance {
        T::ExistentialDeposit::get()
    }
    fn balance(who: &amp;T::AccountId) -&gt; Self::Balance {
        Self::account(who).total()
    }
    fn reducible_balance(who: &amp;T::AccountId, keep_alive: bool) -&gt; Self::Balance {
        let a = Self::account(who);
        // Liquid balance is what is neither reserved nor locked/frozen.
        let liquid = a.free.saturating_sub(a.fee_frozen.max(a.misc_frozen));
        if frame_system::Pallet::&lt;T&gt;::can_dec_provider(who) &amp;&amp; !keep_alive {
            liquid
        } else {
            // `must_remain_to_exist` is the part of liquid balance which must remain to keep total
            // over ED.
            let must_remain_to_exist =
                T::ExistentialDeposit::get().saturating_sub(a.total() - liquid);
            liquid.saturating_sub(must_remain_to_exist)
        }
    }
    fn can_deposit(who: &amp;T::AccountId, amount: Self::Balance, mint: bool) -&gt; DepositConsequence {
        Self::deposit_consequence(who, amount, &amp;Self::account(who), mint)
    }
    fn can_withdraw(
        who: &amp;T::AccountId,
        amount: Self::Balance,
    ) -&gt; WithdrawConsequence&lt;Self::Balance&gt; {
        Self::withdraw_consequence(who, amount, &amp;Self::account(who))
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; fungible::Mutate&lt;T::AccountId&gt; for Pallet&lt;T, I&gt; {
    fn mint_into(who: &amp;T::AccountId, amount: Self::Balance) -&gt; DispatchResult {
        if amount.is_zero() {
            return Ok(())
        }
        Self::try_mutate_account(who, |account, _is_new| -&gt; DispatchResult {
            Self::deposit_consequence(who, amount, account, true).into_result()?;
            account.free += amount;
            Ok(())
        })?;
        TotalIssuance::&lt;T, I&gt;::mutate(|t| *t += amount);
        Self::deposit_event(Event::Deposit { who: who.clone(), amount });
        Ok(())
    }

    fn burn_from(
        who: &amp;T::AccountId,
        amount: Self::Balance,
    ) -&gt; Result&lt;Self::Balance, DispatchError&gt; {
        if amount.is_zero() {
            return Ok(Self::Balance::zero())
        }
        let actual = Self::try_mutate_account(
            who,
            |account, _is_new| -&gt; Result&lt;T::Balance, DispatchError&gt; {
                let extra = Self::withdraw_consequence(who, amount, account).into_result()?;
                let actual = amount + extra;
                account.free -= actual;
                Ok(actual)
            },
        )?;
        TotalIssuance::&lt;T, I&gt;::mutate(|t| *t -= actual);
        Self::deposit_event(Event::Withdraw { who: who.clone(), amount });
        Ok(actual)
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; fungible::Transfer&lt;T::AccountId&gt; for Pallet&lt;T, I&gt; {
    fn transfer(
        source: &amp;T::AccountId,
        dest: &amp;T::AccountId,
        amount: T::Balance,
        keep_alive: bool,
    ) -&gt; Result&lt;T::Balance, DispatchError&gt; {
        let er = if keep_alive { KeepAlive } else { AllowDeath };
        &lt;Self as Currency&lt;T::AccountId&gt;&gt;::transfer(source, dest, amount, er).map(|_| amount)
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; fungible::Unbalanced&lt;T::AccountId&gt; for Pallet&lt;T, I&gt; {
    fn set_balance(who: &amp;T::AccountId, amount: Self::Balance) -&gt; DispatchResult {
        Self::mutate_account(who, |account| {
            account.free = amount;
            Self::deposit_event(Event::BalanceSet {
                who: who.clone(),
                free: account.free,
                reserved: account.reserved,
            });
        })?;
        Ok(())
    }

    fn set_total_issuance(amount: Self::Balance) {
        TotalIssuance::&lt;T, I&gt;::mutate(|t| *t = amount);
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; fungible::InspectHold&lt;T::AccountId&gt; for Pallet&lt;T, I&gt; {
    fn balance_on_hold(who: &amp;T::AccountId) -&gt; T::Balance {
        Self::account(who).reserved
    }
    fn can_hold(who: &amp;T::AccountId, amount: T::Balance) -&gt; bool {
        let a = Self::account(who);
        let min_balance = T::ExistentialDeposit::get().max(a.frozen(Reasons::All));
        if a.reserved.checked_add(&amp;amount).is_none() {
            return false
        }
        // We require it to be min_balance + amount to ensure that the full reserved funds may be
        // slashed without compromising locked funds or destroying the account.
        let required_free = match min_balance.checked_add(&amp;amount) {
            Some(x) =&gt; x,
            None =&gt; return false,
        };
        a.free &gt;= required_free
    }
}
impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; fungible::MutateHold&lt;T::AccountId&gt; for Pallet&lt;T, I&gt; {
    fn hold(who: &amp;T::AccountId, amount: Self::Balance) -&gt; DispatchResult {
        if amount.is_zero() {
            return Ok(())
        }
        ensure!(Self::can_reserve(who, amount), Error::&lt;T, I&gt;::InsufficientBalance);
        Self::mutate_account(who, |a| {
            a.free -= amount;
            a.reserved += amount;
        })?;
        Ok(())
    }
    fn release(
        who: &amp;T::AccountId,
        amount: Self::Balance,
        best_effort: bool,
    ) -&gt; Result&lt;T::Balance, DispatchError&gt; {
        if amount.is_zero() {
            return Ok(amount)
        }
        // Done on a best-effort basis.
        Self::try_mutate_account(who, |a, _| {
            let new_free = a.free.saturating_add(amount.min(a.reserved));
            let actual = new_free - a.free;
            ensure!(best_effort || actual == amount, Error::&lt;T, I&gt;::InsufficientBalance);
            // ^^^ Guaranteed to be &lt;= amount and &lt;= a.reserved
            a.free = new_free;
            a.reserved = a.reserved.saturating_sub(actual);
            Ok(actual)
        })
    }
    fn transfer_held(
        source: &amp;T::AccountId,
        dest: &amp;T::AccountId,
        amount: Self::Balance,
        best_effort: bool,
        on_hold: bool,
    ) -&gt; Result&lt;Self::Balance, DispatchError&gt; {
        let status = if on_hold { Status::Reserved } else { Status::Free };
        Self::do_transfer_reserved(source, dest, amount, best_effort, status)
    }
}

// wrapping these imbalances in a private module is necessary to ensure absolute privacy
// of the inner member.
mod imbalances {
    use super::{result, Config, Imbalance, RuntimeDebug, Saturating, TryDrop, Zero};
    use frame_support::traits::SameOrOther;
    use sp_std::mem;

    /// Opaque, move-only struct with private fields that serves as a token denoting that
    /// funds have been created without any equal and opposite accounting.
    #[must_use]
    #[derive(RuntimeDebug, PartialEq, Eq)]
    pub struct PositiveImbalance&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt;(T::Balance);

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; PositiveImbalance&lt;T, I&gt; {
        /// Create a new positive imbalance from a balance.
        pub fn new(amount: T::Balance) -&gt; Self {
            PositiveImbalance(amount)
        }
    }

    /// Opaque, move-only struct with private fields that serves as a token denoting that
    /// funds have been destroyed without any equal and opposite accounting.
    #[must_use]
    #[derive(RuntimeDebug, PartialEq, Eq)]
    pub struct NegativeImbalance&lt;T: Config&lt;I&gt;, I: &#39;static = ()&gt;(T::Balance);

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; NegativeImbalance&lt;T, I&gt; {
        /// Create a new negative imbalance from a balance.
        pub fn new(amount: T::Balance) -&gt; Self {
            NegativeImbalance(amount)
        }
    }

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; TryDrop for PositiveImbalance&lt;T, I&gt; {
        fn try_drop(self) -&gt; result::Result&lt;(), Self&gt; {
            self.drop_zero()
        }
    }

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Default for PositiveImbalance&lt;T, I&gt; {
        fn default() -&gt; Self {
            Self::zero()
        }
    }

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Imbalance&lt;T::Balance&gt; for PositiveImbalance&lt;T, I&gt; {
        type Opposite = NegativeImbalance&lt;T, I&gt;;

        fn zero() -&gt; Self {
            Self(Zero::zero())
        }
        fn drop_zero(self) -&gt; result::Result&lt;(), Self&gt; {
            if self.0.is_zero() {
                Ok(())
            } else {
                Err(self)
            }
        }
        fn split(self, amount: T::Balance) -&gt; (Self, Self) {
            let first = self.0.min(amount);
            let second = self.0 - first;

            mem::forget(self);
            (Self(first), Self(second))
        }
        fn merge(mut self, other: Self) -&gt; Self {
            self.0 = self.0.saturating_add(other.0);
            mem::forget(other);

            self
        }
        fn subsume(&amp;mut self, other: Self) {
            self.0 = self.0.saturating_add(other.0);
            mem::forget(other);
        }
        fn offset(self, other: Self::Opposite) -&gt; SameOrOther&lt;Self, Self::Opposite&gt; {
            let (a, b) = (self.0, other.0);
            mem::forget((self, other));

            if a &gt; b {
                SameOrOther::Same(Self(a - b))
            } else if b &gt; a {
                SameOrOther::Other(NegativeImbalance::new(b - a))
            } else {
                SameOrOther::None
            }
        }
        fn peek(&amp;self) -&gt; T::Balance {
            self.0
        }
    }

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; TryDrop for NegativeImbalance&lt;T, I&gt; {
        fn try_drop(self) -&gt; result::Result&lt;(), Self&gt; {
            self.drop_zero()
        }
    }

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Default for NegativeImbalance&lt;T, I&gt; {
        fn default() -&gt; Self {
            Self::zero()
        }
    }

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Imbalance&lt;T::Balance&gt; for NegativeImbalance&lt;T, I&gt; {
        type Opposite = PositiveImbalance&lt;T, I&gt;;

        fn zero() -&gt; Self {
            Self(Zero::zero())
        }
        fn drop_zero(self) -&gt; result::Result&lt;(), Self&gt; {
            if self.0.is_zero() {
                Ok(())
            } else {
                Err(self)
            }
        }
        fn split(self, amount: T::Balance) -&gt; (Self, Self) {
            let first = self.0.min(amount);
            let second = self.0 - first;

            mem::forget(self);
            (Self(first), Self(second))
        }
        fn merge(mut self, other: Self) -&gt; Self {
            self.0 = self.0.saturating_add(other.0);
            mem::forget(other);

            self
        }
        fn subsume(&amp;mut self, other: Self) {
            self.0 = self.0.saturating_add(other.0);
            mem::forget(other);
        }
        fn offset(self, other: Self::Opposite) -&gt; SameOrOther&lt;Self, Self::Opposite&gt; {
            let (a, b) = (self.0, other.0);
            mem::forget((self, other));

            if a &gt; b {
                SameOrOther::Same(Self(a - b))
            } else if b &gt; a {
                SameOrOther::Other(PositiveImbalance::new(b - a))
            } else {
                SameOrOther::None
            }
        }
        fn peek(&amp;self) -&gt; T::Balance {
            self.0
        }
    }

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Drop for PositiveImbalance&lt;T, I&gt; {
        /// Basic drop handler will just square up the total issuance.
        fn drop(&amp;mut self) {
            &lt;super::TotalIssuance&lt;T, I&gt;&gt;::mutate(|v| *v = v.saturating_add(self.0));
        }
    }

    impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Drop for NegativeImbalance&lt;T, I&gt; {
        /// Basic drop handler will just square up the total issuance.
        fn drop(&amp;mut self) {
            &lt;super::TotalIssuance&lt;T, I&gt;&gt;::mutate(|v| *v = v.saturating_sub(self.0));
        }
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; Currency&lt;T::AccountId&gt; for Pallet&lt;T, I&gt;
where
    T::Balance: MaybeSerializeDeserialize + Debug,
{
    type Balance = T::Balance;
    type PositiveImbalance = PositiveImbalance&lt;T, I&gt;;
    type NegativeImbalance = NegativeImbalance&lt;T, I&gt;;

    fn total_balance(who: &amp;T::AccountId) -&gt; Self::Balance {
        Self::account(who).total()
    }

    // Check if `value` amount of free balance can be slashed from `who`.
    fn can_slash(who: &amp;T::AccountId, value: Self::Balance) -&gt; bool {
        if value.is_zero() {
            return true
        }
        Self::free_balance(who) &gt;= value
    }

    fn total_issuance() -&gt; Self::Balance {
        &lt;TotalIssuance&lt;T, I&gt;&gt;::get()
    }

    fn minimum_balance() -&gt; Self::Balance {
        T::ExistentialDeposit::get()
    }

    // Burn funds from the total issuance, returning a positive imbalance for the amount burned.
    // Is a no-op if amount to be burned is zero.
    fn burn(mut amount: Self::Balance) -&gt; Self::PositiveImbalance {
        if amount.is_zero() {
            return PositiveImbalance::zero()
        }
        &lt;TotalIssuance&lt;T, I&gt;&gt;::mutate(|issued| {
            *issued = issued.checked_sub(&amp;amount).unwrap_or_else(|| {
                amount = *issued;
                Zero::zero()
            });
        });
        PositiveImbalance::new(amount)
    }

    // Create new funds into the total issuance, returning a negative imbalance
    // for the amount issued.
    // Is a no-op if amount to be issued it zero.
    fn issue(mut amount: Self::Balance) -&gt; Self::NegativeImbalance {
        if amount.is_zero() {
            return NegativeImbalance::zero()
        }
        &lt;TotalIssuance&lt;T, I&gt;&gt;::mutate(|issued| {
            *issued = issued.checked_add(&amp;amount).unwrap_or_else(|| {
                amount = Self::Balance::max_value() - *issued;
                Self::Balance::max_value()
            })
        });
        NegativeImbalance::new(amount)
    }

    fn free_balance(who: &amp;T::AccountId) -&gt; Self::Balance {
        Self::account(who).free
    }

    // Ensure that an account can withdraw from their free balance given any existing withdrawal
    // restrictions like locks and vesting balance.
    // Is a no-op if amount to be withdrawn is zero.
    //
    // # &lt;weight&gt;
    // Despite iterating over a list of locks, they are limited by the number of
    // lock IDs, which means the number of runtime pallets that intend to use and create locks.
    // # &lt;/weight&gt;
    fn ensure_can_withdraw(
        who: &amp;T::AccountId,
        amount: T::Balance,
        reasons: WithdrawReasons,
        new_balance: T::Balance,
    ) -&gt; DispatchResult {
        if amount.is_zero() {
            return Ok(())
        }
        let min_balance = Self::account(who).frozen(reasons.into());
        ensure!(new_balance &gt;= min_balance, Error::&lt;T, I&gt;::LiquidityRestrictions);
        Ok(())
    }

    // Transfer some free balance from `transactor` to `dest`, respecting existence requirements.
    // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`.
    fn transfer(
        transactor: &amp;T::AccountId,
        dest: &amp;T::AccountId,
        value: Self::Balance,
        existence_requirement: ExistenceRequirement,
    ) -&gt; DispatchResult {
        if value.is_zero() || transactor == dest {
            return Ok(())
        }

        Self::try_mutate_account_with_dust(
            dest,
            |to_account, _| -&gt; Result&lt;DustCleaner&lt;T, I&gt;, DispatchError&gt; {
                Self::try_mutate_account_with_dust(
                    transactor,
                    |from_account, _| -&gt; DispatchResult {
                        from_account.free = from_account
                            .free
                            .checked_sub(&amp;value)
                            .ok_or(Error::&lt;T, I&gt;::InsufficientBalance)?;

                        // NOTE: total stake being stored in the same type means that this could
                        // never overflow but better to be safe than sorry.
                        to_account.free =
                            to_account.free.checked_add(&amp;value).ok_or(ArithmeticError::Overflow)?;

                        let ed = T::ExistentialDeposit::get();
                        ensure!(to_account.total() &gt;= ed, Error::&lt;T, I&gt;::ExistentialDeposit);

                        Self::ensure_can_withdraw(
                            transactor,
                            value,
                            WithdrawReasons::TRANSFER,
                            from_account.free,
                        )
                        .map_err(|_| Error::&lt;T, I&gt;::LiquidityRestrictions)?;

                        // TODO: This is over-conservative. There may now be other providers, and
                        // this pallet may not even be a provider.
                        let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
                        let allow_death =
                            allow_death &amp;&amp; system::Pallet::&lt;T&gt;::can_dec_provider(transactor);
                        ensure!(
                            allow_death || from_account.total() &gt;= ed,
                            Error::&lt;T, I&gt;::KeepAlive
                        );

                        Ok(())
                    },
                )
                .map(|(_, maybe_dust_cleaner)| maybe_dust_cleaner)
            },
        )?;

        // Emit transfer event.
        Self::deposit_event(Event::Transfer {
            from: transactor.clone(),
            to: dest.clone(),
            amount: value,
        });

        Ok(())
    }

    /// Slash a target account `who`, returning the negative imbalance created and any left over
    /// amount that could not be slashed.
    ///
    /// Is a no-op if `value` to be slashed is zero or the account does not exist.
    ///
    /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn
    /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid
    /// having to draw from reserved funds, however we err on the side of punishment if things are
    /// inconsistent or `can_slash` wasn&#39;t used appropriately.
    fn slash(who: &amp;T::AccountId, value: Self::Balance) -&gt; (Self::NegativeImbalance, Self::Balance) {
        if value.is_zero() {
            return (NegativeImbalance::zero(), Zero::zero())
        }
        if Self::total_balance(who).is_zero() {
            return (NegativeImbalance::zero(), value)
        }

        for attempt in 0..2 {
            match Self::try_mutate_account(
                who,
                |account,
                 _is_new|
                 -&gt; Result&lt;(Self::NegativeImbalance, Self::Balance), DispatchError&gt; {
                    // Best value is the most amount we can slash following liveness rules.
                    let best_value = match attempt {
                        // First attempt we try to slash the full amount, and see if liveness issues
                        // happen.
                        0 =&gt; value,
                        // If acting as a critical provider (i.e. first attempt failed), then slash
                        // as much as possible while leaving at least at ED.
                        _ =&gt; value.min(
                            (account.free + account.reserved)
                                .saturating_sub(T::ExistentialDeposit::get()),
                        ),
                    };

                    let free_slash = cmp::min(account.free, best_value);
                    account.free -= free_slash; // Safe because of above check
                    let remaining_slash = best_value - free_slash; // Safe because of above check

                    if !remaining_slash.is_zero() {
                        // If we have remaining slash, take it from reserved balance.
                        let reserved_slash = cmp::min(account.reserved, remaining_slash);
                        account.reserved -= reserved_slash; // Safe because of above check
                        Ok((
                            NegativeImbalance::new(free_slash + reserved_slash),
                            value - free_slash - reserved_slash, /* Safe because value is gt or
                                                                  * eq total slashed */
                        ))
                    } else {
                        // Else we are done!
                        Ok((
                            NegativeImbalance::new(free_slash),
                            value - free_slash, // Safe because value is gt or eq to total slashed
                        ))
                    }
                },
            ) {
                Ok((imbalance, not_slashed)) =&gt; {
                    Self::deposit_event(Event::Slashed {
                        who: who.clone(),
                        amount: value.saturating_sub(not_slashed),
                    });
                    return (imbalance, not_slashed)
                },
                Err(_) =&gt; (),
            }
        }

        // Should never get here. But we&#39;ll be defensive anyway.
        (Self::NegativeImbalance::zero(), value)
    }

    /// Deposit some `value` into the free balance of an existing target account `who`.
    ///
    /// Is a no-op if the `value` to be deposited is zero.
    fn deposit_into_existing(
        who: &amp;T::AccountId,
        value: Self::Balance,
    ) -&gt; Result&lt;Self::PositiveImbalance, DispatchError&gt; {
        if value.is_zero() {
            return Ok(PositiveImbalance::zero())
        }

        Self::try_mutate_account(
            who,
            |account, is_new| -&gt; Result&lt;Self::PositiveImbalance, DispatchError&gt; {
                ensure!(!is_new, Error::&lt;T, I&gt;::DeadAccount);
                account.free = account.free.checked_add(&amp;value).ok_or(ArithmeticError::Overflow)?;
                Self::deposit_event(Event::Deposit { who: who.clone(), amount: value });
                Ok(PositiveImbalance::new(value))
            },
        )
    }

    /// Deposit some `value` into the free balance of `who`, possibly creating a new account.
    ///
    /// This function is a no-op if:
    /// - the `value` to be deposited is zero; or
    /// - the `value` to be deposited is less than the required ED and the account does not yet
    ///   exist; or
    /// - the deposit would necessitate the account to exist and there are no provider references;
    ///   or
    /// - `value` is so large it would cause the balance of `who` to overflow.
    fn deposit_creating(who: &amp;T::AccountId, value: Self::Balance) -&gt; Self::PositiveImbalance {
        if value.is_zero() {
            return Self::PositiveImbalance::zero()
        }

        Self::try_mutate_account(
            who,
            |account, is_new| -&gt; Result&lt;Self::PositiveImbalance, DispatchError&gt; {
                let ed = T::ExistentialDeposit::get();
                ensure!(value &gt;= ed || !is_new, Error::&lt;T, I&gt;::ExistentialDeposit);

                // defensive only: overflow should never happen, however in case it does, then this
                // operation is a no-op.
                account.free = match account.free.checked_add(&amp;value) {
                    Some(x) =&gt; x,
                    None =&gt; return Ok(Self::PositiveImbalance::zero()),
                };

                Self::deposit_event(Event::Deposit { who: who.clone(), amount: value });
                Ok(PositiveImbalance::new(value))
            },
        )
        .unwrap_or_else(|_| Self::PositiveImbalance::zero())
    }

    /// Withdraw some free balance from an account, respecting existence requirements.
    ///
    /// Is a no-op if value to be withdrawn is zero.
    fn withdraw(
        who: &amp;T::AccountId,
        value: Self::Balance,
        reasons: WithdrawReasons,
        liveness: ExistenceRequirement,
    ) -&gt; result::Result&lt;Self::NegativeImbalance, DispatchError&gt; {
        if value.is_zero() {
            return Ok(NegativeImbalance::zero())
        }

        Self::try_mutate_account(
            who,
            |account, _| -&gt; Result&lt;Self::NegativeImbalance, DispatchError&gt; {
                let new_free_account =
                    account.free.checked_sub(&amp;value).ok_or(Error::&lt;T, I&gt;::InsufficientBalance)?;

                // bail if we need to keep the account alive and this would kill it.
                let ed = T::ExistentialDeposit::get();
                let would_be_dead = new_free_account + account.reserved &lt; ed;
                let would_kill = would_be_dead &amp;&amp; account.free + account.reserved &gt;= ed;
                ensure!(liveness == AllowDeath || !would_kill, Error::&lt;T, I&gt;::KeepAlive);

                Self::ensure_can_withdraw(who, value, reasons, new_free_account)?;

                account.free = new_free_account;

                Self::deposit_event(Event::Withdraw { who: who.clone(), amount: value });
                Ok(NegativeImbalance::new(value))
            },
        )
    }

    /// Force the new free balance of a target account `who` to some new value `balance`.
    fn make_free_balance_be(
        who: &amp;T::AccountId,
        value: Self::Balance,
    ) -&gt; SignedImbalance&lt;Self::Balance, Self::PositiveImbalance&gt; {
        Self::try_mutate_account(
            who,
            |account,
             is_new|
             -&gt; Result&lt;SignedImbalance&lt;Self::Balance, Self::PositiveImbalance&gt;, DispatchError&gt; {
                let ed = T::ExistentialDeposit::get();
                let total = value.saturating_add(account.reserved);
                // If we&#39;re attempting to set an existing account to less than ED, then
                // bypass the entire operation. It&#39;s a no-op if you follow it through, but
                // since this is an instance where we might account for a negative imbalance
                // (in the dust cleaner of set_account) before we account for its actual
                // equal and opposite cause (returned as an Imbalance), then in the
                // instance that there&#39;s no other accounts on the system at all, we might
                // underflow the issuance and our arithmetic will be off.
                ensure!(total &gt;= ed || !is_new, Error::&lt;T, I&gt;::ExistentialDeposit);

                let imbalance = if account.free &lt;= value {
                    SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
                } else {
                    SignedImbalance::Negative(NegativeImbalance::new(account.free - value))
                };
                account.free = value;
                Self::deposit_event(Event::BalanceSet {
                    who: who.clone(),
                    free: account.free,
                    reserved: account.reserved,
                });
                Ok(imbalance)
            },
        )
        .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero()))
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; ReservableCurrency&lt;T::AccountId&gt; for Pallet&lt;T, I&gt;
where
    T::Balance: MaybeSerializeDeserialize + Debug,
{
    /// Check if `who` can reserve `value` from their free balance.
    ///
    /// Always `true` if value to be reserved is zero.
    fn can_reserve(who: &amp;T::AccountId, value: Self::Balance) -&gt; bool {
        if value.is_zero() {
            return true
        }
        Self::account(who).free.checked_sub(&amp;value).map_or(false, |new_balance| {
            Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance).is_ok()
        })
    }

    fn reserved_balance(who: &amp;T::AccountId) -&gt; Self::Balance {
        Self::account(who).reserved
    }

    /// Move `value` from the free balance from `who` to their reserved balance.
    ///
    /// Is a no-op if value to be reserved is zero.
    fn reserve(who: &amp;T::AccountId, value: Self::Balance) -&gt; DispatchResult {
        if value.is_zero() {
            return Ok(())
        }

        Self::try_mutate_account(who, |account, _| -&gt; DispatchResult {
            account.free =
                account.free.checked_sub(&amp;value).ok_or(Error::&lt;T, I&gt;::InsufficientBalance)?;
            account.reserved =
                account.reserved.checked_add(&amp;value).ok_or(ArithmeticError::Overflow)?;
            Self::ensure_can_withdraw(&amp;who, value, WithdrawReasons::RESERVE, account.free)
        })?;

        Self::deposit_event(Event::Reserved { who: who.clone(), amount: value });
        Ok(())
    }

    /// Unreserve some funds, returning any amount that was unable to be unreserved.
    ///
    /// Is a no-op if the value to be unreserved is zero or the account does not exist.
    fn unreserve(who: &amp;T::AccountId, value: Self::Balance) -&gt; Self::Balance {
        if value.is_zero() {
            return Zero::zero()
        }
        if Self::total_balance(who).is_zero() {
            return value
        }

        let actual = match Self::mutate_account(who, |account| {
            let actual = cmp::min(account.reserved, value);
            account.reserved -= actual;
            // defensive only: this can never fail since total issuance which is at least
            // free+reserved fits into the same data type.
            account.free = account.free.defensive_saturating_add(actual);
            actual
        }) {
            Ok(x) =&gt; x,
            Err(_) =&gt; {
                // This should never happen since we don&#39;t alter the total amount in the account.
                // If it ever does, then we should fail gracefully though, indicating that nothing
                // could be done.
                return value
            },
        };

        Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual });
        value - actual
    }

    /// Slash from reserved balance, returning the negative imbalance created,
    /// and any amount that was unable to be slashed.
    ///
    /// Is a no-op if the value to be slashed is zero or the account does not exist.
    fn slash_reserved(
        who: &amp;T::AccountId,
        value: Self::Balance,
    ) -&gt; (Self::NegativeImbalance, Self::Balance) {
        if value.is_zero() {
            return (NegativeImbalance::zero(), Zero::zero())
        }
        if Self::total_balance(who).is_zero() {
            return (NegativeImbalance::zero(), value)
        }

        // NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an
        //   account is attempted to be illegally destroyed.

        for attempt in 0..2 {
            match Self::mutate_account(who, |account| {
                let best_value = match attempt {
                    0 =&gt; value,
                    // If acting as a critical provider (i.e. first attempt failed), then ensure
                    // slash leaves at least the ED.
                    _ =&gt; value.min(
                        (account.free + account.reserved)
                            .saturating_sub(T::ExistentialDeposit::get()),
                    ),
                };

                let actual = cmp::min(account.reserved, best_value);
                account.reserved -= actual;

                // underflow should never happen, but it if does, there&#39;s nothing to be done here.
                (NegativeImbalance::new(actual), value - actual)
            }) {
                Ok((imbalance, not_slashed)) =&gt; {
                    Self::deposit_event(Event::Slashed {
                        who: who.clone(),
                        amount: value.saturating_sub(not_slashed),
                    });
                    return (imbalance, not_slashed)
                },
                Err(_) =&gt; (),
            }
        }
        // Should never get here as we ensure that ED is left in the second attempt.
        // In case we do, though, then we fail gracefully.
        (Self::NegativeImbalance::zero(), value)
    }

    /// Move the reserved balance of one account into the balance of another, according to `status`.
    ///
    /// Is a no-op if:
    /// - the value to be moved is zero; or
    /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
    fn repatriate_reserved(
        slashed: &amp;T::AccountId,
        beneficiary: &amp;T::AccountId,
        value: Self::Balance,
        status: Status,
    ) -&gt; Result&lt;Self::Balance, DispatchError&gt; {
        let actual = Self::do_transfer_reserved(slashed, beneficiary, value, true, status)?;
        Ok(value.saturating_sub(actual))
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; NamedReservableCurrency&lt;T::AccountId&gt; for Pallet&lt;T, I&gt;
where
    T::Balance: MaybeSerializeDeserialize + Debug,
{
    type ReserveIdentifier = T::ReserveIdentifier;

    fn reserved_balance_named(id: &amp;Self::ReserveIdentifier, who: &amp;T::AccountId) -&gt; Self::Balance {
        let reserves = Self::reserves(who);
        reserves
            .binary_search_by_key(id, |data| data.id)
            .map(|index| reserves[index].amount)
            .unwrap_or_default()
    }

    /// Move `value` from the free balance from `who` to a named reserve balance.
    ///
    /// Is a no-op if value to be reserved is zero.
    fn reserve_named(
        id: &amp;Self::ReserveIdentifier,
        who: &amp;T::AccountId,
        value: Self::Balance,
    ) -&gt; DispatchResult {
        if value.is_zero() {
            return Ok(())
        }

        Reserves::&lt;T, I&gt;::try_mutate(who, |reserves| -&gt; DispatchResult {
            match reserves.binary_search_by_key(id, |data| data.id) {
                Ok(index) =&gt; {
                    // this add can&#39;t overflow but just to be defensive.
                    reserves[index].amount = reserves[index].amount.defensive_saturating_add(value);
                },
                Err(index) =&gt; {
                    reserves
                        .try_insert(index, ReserveData { id: *id, amount: value })
                        .map_err(|_| Error::&lt;T, I&gt;::TooManyReserves)?;
                },
            };
            &lt;Self as ReservableCurrency&lt;_&gt;&gt;::reserve(who, value)?;
            Ok(())
        })
    }

    /// Unreserve some funds, returning any amount that was unable to be unreserved.
    ///
    /// Is a no-op if the value to be unreserved is zero.
    fn unreserve_named(
        id: &amp;Self::ReserveIdentifier,
        who: &amp;T::AccountId,
        value: Self::Balance,
    ) -&gt; Self::Balance {
        if value.is_zero() {
            return Zero::zero()
        }

        Reserves::&lt;T, I&gt;::mutate_exists(who, |maybe_reserves| -&gt; Self::Balance {
            if let Some(reserves) = maybe_reserves.as_mut() {
                match reserves.binary_search_by_key(id, |data| data.id) {
                    Ok(index) =&gt; {
                        let to_change = cmp::min(reserves[index].amount, value);

                        let remain = &lt;Self as ReservableCurrency&lt;_&gt;&gt;::unreserve(who, to_change);

                        // remain should always be zero but just to be defensive here.
                        let actual = to_change.defensive_saturating_sub(remain);

                        // `actual &lt;= to_change` and `to_change &lt;= amount`; qed;
                        reserves[index].amount -= actual;

                        if reserves[index].amount.is_zero() {
                            if reserves.len() == 1 {
                                // no more named reserves
                                *maybe_reserves = None;
                            } else {
                                // remove this named reserve
                                reserves.remove(index);
                            }
                        }

                        value - actual
                    },
                    Err(_) =&gt; value,
                }
            } else {
                value
            }
        })
    }

    /// Slash from reserved balance, returning the negative imbalance created,
    /// and any amount that was unable to be slashed.
    ///
    /// Is a no-op if the value to be slashed is zero.
    fn slash_reserved_named(
        id: &amp;Self::ReserveIdentifier,
        who: &amp;T::AccountId,
        value: Self::Balance,
    ) -&gt; (Self::NegativeImbalance, Self::Balance) {
        if value.is_zero() {
            return (NegativeImbalance::zero(), Zero::zero())
        }

        Reserves::&lt;T, I&gt;::mutate(who, |reserves| -&gt; (Self::NegativeImbalance, Self::Balance) {
            match reserves.binary_search_by_key(id, |data| data.id) {
                Ok(index) =&gt; {
                    let to_change = cmp::min(reserves[index].amount, value);

                    let (imb, remain) =
                        &lt;Self as ReservableCurrency&lt;_&gt;&gt;::slash_reserved(who, to_change);

                    // remain should always be zero but just to be defensive here.
                    let actual = to_change.defensive_saturating_sub(remain);

                    // `actual &lt;= to_change` and `to_change &lt;= amount`; qed;
                    reserves[index].amount -= actual;

                    Self::deposit_event(Event::Slashed { who: who.clone(), amount: actual });
                    (imb, value - actual)
                },
                Err(_) =&gt; (NegativeImbalance::zero(), value),
            }
        })
    }

    /// Move the reserved balance of one account into the balance of another, according to `status`.
    /// If `status` is `Reserved`, the balance will be reserved with given `id`.
    ///
    /// Is a no-op if:
    /// - the value to be moved is zero; or
    /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
    fn repatriate_reserved_named(
        id: &amp;Self::ReserveIdentifier,
        slashed: &amp;T::AccountId,
        beneficiary: &amp;T::AccountId,
        value: Self::Balance,
        status: Status,
    ) -&gt; Result&lt;Self::Balance, DispatchError&gt; {
        if value.is_zero() {
            return Ok(Zero::zero())
        }

        if slashed == beneficiary {
            return match status {
                Status::Free =&gt; Ok(Self::unreserve_named(id, slashed, value)),
                Status::Reserved =&gt;
                    Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))),
            }
        }

        Reserves::&lt;T, I&gt;::try_mutate(slashed, |reserves| -&gt; Result&lt;Self::Balance, DispatchError&gt; {
            match reserves.binary_search_by_key(id, |data| data.id) {
                Ok(index) =&gt; {
                    let to_change = cmp::min(reserves[index].amount, value);

                    let actual = if status == Status::Reserved {
                        // make it the reserved under same identifier
                        Reserves::&lt;T, I&gt;::try_mutate(
                            beneficiary,
                            |reserves| -&gt; Result&lt;T::Balance, DispatchError&gt; {
                                match reserves.binary_search_by_key(id, |data| data.id) {
                                    Ok(index) =&gt; {
                                        let remain =
                                            &lt;Self as ReservableCurrency&lt;_&gt;&gt;::repatriate_reserved(
                                                slashed,
                                                beneficiary,
                                                to_change,
                                                status,
                                            )?;

                                        // remain should always be zero but just to be defensive
                                        // here.
                                        let actual = to_change.defensive_saturating_sub(remain);

                                        // this add can&#39;t overflow but just to be defensive.
                                        reserves[index].amount =
                                            reserves[index].amount.defensive_saturating_add(actual);

                                        Ok(actual)
                                    },
                                    Err(index) =&gt; {
                                        let remain =
                                            &lt;Self as ReservableCurrency&lt;_&gt;&gt;::repatriate_reserved(
                                                slashed,
                                                beneficiary,
                                                to_change,
                                                status,
                                            )?;

                                        // remain should always be zero but just to be defensive
                                        // here
                                        let actual = to_change.defensive_saturating_sub(remain);

                                        reserves
                                            .try_insert(
                                                index,
                                                ReserveData { id: *id, amount: actual },
                                            )
                                            .map_err(|_| Error::&lt;T, I&gt;::TooManyReserves)?;

                                        Ok(actual)
                                    },
                                }
                            },
                        )?
                    } else {
                        let remain = &lt;Self as ReservableCurrency&lt;_&gt;&gt;::repatriate_reserved(
                            slashed,
                            beneficiary,
                            to_change,
                            status,
                        )?;

                        // remain should always be zero but just to be defensive here
                        to_change.defensive_saturating_sub(remain)
                    };

                    // `actual &lt;= to_change` and `to_change &lt;= amount`; qed;
                    reserves[index].amount -= actual;

                    Ok(value - actual)
                },
                Err(_) =&gt; Ok(value),
            }
        })
    }
}

impl&lt;T: Config&lt;I&gt;, I: &#39;static&gt; LockableCurrency&lt;T::AccountId&gt; for Pallet&lt;T, I&gt;
where
    T::Balance: MaybeSerializeDeserialize + Debug,
{
    type Moment = T::BlockNumber;

    type MaxLocks = T::MaxLocks;

    // Set a lock on the balance of `who`.
    // Is a no-op if lock amount is zero or `reasons` `is_none()`.
    fn set_lock(
        id: LockIdentifier,
        who: &amp;T::AccountId,
        amount: T::Balance,
        reasons: WithdrawReasons,
    ) {
        if amount.is_zero() || reasons.is_empty() {
            return
        }
        let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
        let mut locks = Self::locks(who)
            .into_iter()
            .filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) })
            .collect::&lt;Vec&lt;_&gt;&gt;();
        if let Some(lock) = new_lock {
            locks.push(lock)
        }
        Self::update_locks(who, &amp;locks[..]);
    }

    // Extend a lock on the balance of `who`.
    // Is a no-op if lock amount is zero or `reasons` `is_none()`.
    fn extend_lock(
        id: LockIdentifier,
        who: &amp;T::AccountId,
        amount: T::Balance,
        reasons: WithdrawReasons,
    ) {
        if amount.is_zero() || reasons.is_empty() {
            return
        }
        let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
        let mut locks = Self::locks(who)
            .into_iter()
            .filter_map(|l| {
                if l.id == id {
                    new_lock.take().map(|nl| BalanceLock {
                        id: l.id,
                        amount: l.amount.max(nl.amount),
                        reasons: l.reasons | nl.reasons,
                    })
                } else {
                    Some(l)
                }
            })
            .collect::&lt;Vec&lt;_&gt;&gt;();
        if let Some(lock) = new_lock {
            locks.push(lock)
        }
        Self::update_locks(who, &amp;locks[..]);
    }

    fn remove_lock(id: LockIdentifier, who: &amp;T::AccountId) {
        let mut locks = Self::locks(who);
        locks.retain(|l| l.id != id);
        Self::update_locks(who, &amp;locks[..]);
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[러스트 공식 문서 01-02]]></title>
            <link>https://velog.io/@myungha_cho/%EB%9F%AC%EC%8A%A4%ED%8A%B8-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C-01-02</link>
            <guid>https://velog.io/@myungha_cho/%EB%9F%AC%EC%8A%A4%ED%8A%B8-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C-01-02</guid>
            <pubDate>Thu, 16 Jun 2022 02:04:19 GMT</pubDate>
            <description><![CDATA[<h1 id="01">01</h1>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/ec4516b1-5a0c-42ea-83e7-e95289217b4b/image.png" alt="1.2 Hello, World!"></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/383a9676-237a-4483-9dee-6787336dd533/image.png" alt="1.3 Hello, Cargo!"></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/b7b6e46b-b673-4209-b9b0-5834f5cf566c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/4351d6e0-e1d7-4c43-9dd6-c6f8f9b72cd7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/c45ee307-efa5-4332-8920-6b87252f336b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/63c34875-ea6a-4c5d-9da0-2add21f77f88/image.png" alt=""></p>
<hr>
<h1 id="02">02</h1>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/13466914-eabc-4d14-98b8-8eb45204a0ce/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/eac62b7d-a799-493b-9268-f074a60dfd6a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/7a928df6-9d4b-44b1-8ca4-eebf00ef99a5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/4473bbbc-4f0f-4350-9b2f-3b445a4ba0cd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/905ec1f8-3369-424f-bd44-a00ac791d02f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/be6e63ed-68bc-46af-bb71-5a0c9d7317fd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/78a1d6b3-4e6d-40b0-b868-90a8937dc426/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/b5517141-d0ef-47cc-9b00-11edb361aad4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/dd4e1e9a-9d67-499c-8fc9-0b53c67f8763/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/ad2df183-b0d1-40d2-9e99-f362d0282c51/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/68f8ae98-673b-45d4-a275-a849c273665b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[10. Generic Types, and Lifetimes]]></title>
            <link>https://velog.io/@myungha_cho/10.-Generic-Types-and-Lifetimes</link>
            <guid>https://velog.io/@myungha_cho/10.-Generic-Types-and-Lifetimes</guid>
            <pubDate>Sun, 29 May 2022 07:45:19 GMT</pubDate>
            <description><![CDATA[<p>제네릭은 구체적인 타입이나 다른 특성들의 추상적인 대역이다. 
코드를 컴파일하고 실행할 때 그 안에 무엇이 들어있는지 몰라도 동작하는 제네릭의 원리에 대해 알아볼 것이다.
라이프타임은 컴파일러에게 참조값들이 서로 어떻게 연관되어있는지 알려 주는 다양한 제네릭들이다.</p>
<h1 id="generic-data-types">Generic Data types</h1>
<p>타입 이름을 &lt;&gt; 안에 쓰고 인자를 써주면 됨</p>
<pre><code class="language-rust">fn largest&lt;T&gt;(list: &amp;[T]) -&gt; T {}</code></pre>
<p>여러 타입을 사용하고 싶으면 두 개 써주면 됨</p>
<pre><code class="language-rust">struct Point&lt;T, U&gt; {
    x: T,
    y: U,
}</code></pre>
<p>struct나 enum에 메소드를 선언하는 법</p>
<pre><code class="language-rust">struct Point&lt;X1, Y1&gt; {
    x: X1,
    y: Y1,
}

impl&lt;X1, Y1&gt; Point&lt;X1, Y1&gt; {
    fn mixup&lt;X2, Y2&gt;(self, other: Point&lt;X2, Y2&gt;) -&gt; Point&lt;X1, Y2&gt; {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}</code></pre>
<p>컴파일러가 알아서 실제 인스턴스로 바꿔주기 때문에 런타임에선 굉장히 빨라짐</p>
<h1 id="traits-defining-shared-behavior">Traits: Defining Shared Behavior</h1>
<p>trait은 어떤 특정 타입이 가질 수 있고 다른 타입과 공유 가능한 기능이다.
trait 선언하기</p>
<pre><code class="language-rust">pub trait Summary {
    fn summarize(&amp;self) -&gt; String;
    fn summarize_default(&amp;self) -&gt; String {
        String::from(&quot;(Read more...)&quot;)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&amp;self) -&gt; String {
        format!(&quot;{}: {}&quot;, self.username, self.content)
    }
}

impl Summary for Tweet {} // default 사용할 때</code></pre>
<p>이 trait을 구현하는 각 타입이 이 메소드의 바디를 구현해야 함
trait은 외부 타입에는 구현할 수 없음 orphan rule</p>
<pre><code class="language-rust">pub fn notify(item: &amp;impl Summary) {
    println!(&quot;Breaking news! {}&quot;, item.summarize());
}

pub fn notify&lt;T: Summary&gt;(item: &amp;T) {
    println!(&quot;Breaking news! {}&quot;, item.summarize());
} // 함수에 제네릭 사용하기


//여러 trait 바운드
pub fn notify(item: &amp;(impl Summary + Display)) {}
pub fn notify&lt;T: Summary + Display&gt;(item: &amp;T) {}


// trait 너무 많으면 where안에 쓰도록
fn some_function&lt;T, U&gt;(t: &amp;T, u: &amp;U) -&gt; i32
    where T: Display + Clone,
          U: Clone + Debug
{
</code></pre>
<p>item은 Summary trait을 구현하는 어떤 타입이든지 가ㅡㅇ</p>
<pre><code class="language-rust">// 해당 trait을 구현하는 타입으로 리턴
fn returns_summarizable() -&gt; impl Summary {
    Tweet {
        username: String::from(&quot;horse_ebooks&quot;),
        content: String::from(
            &quot;of course, as you probably already know, people&quot;,
        ),
        reply: false,
        retweet: false,
    }
}</code></pre>
<h1 id="validating-references-with-lifetimes">Validating References with Lifetimes</h1>
<pre><code class="language-rust">&amp;i32        // a reference
&amp;&#39;a i32     // a reference with an explicit lifetime
&amp;&#39;a mut i32 // a mutable reference with an explicit lifetime</code></pre>
<p> the returned reference will be valid as long as both the parameters are valid. This is the relationship between lifetimes of the parameters and the return value. </p>
<pre><code class="language-rust"> fn longest&lt;&#39;a&gt;(x: &amp;&#39;a str, y: &amp;&#39;a str) -&gt; &amp;&#39;a str {
    if x.len() &gt; y.len() {
        x
    } else {
        y
    }
}</code></pre>
<p>it means that the lifetime of the reference returned by the longest function is the same as the smaller of the lifetimes of the references passed in. These relationships are what we want Rust to use when analyzing this code.</p>
<p> we’re specifying that the borrow checker should reject any values that don’t adhere to these constraints. Note that the longest function doesn’t need to know exactly how long x and y will live, only that some scope can be substituted for &#39;a that will satisfy this signature.</p>
<pre><code class="language-rust">
struct ImportantExcerpt&lt;&#39;a&gt; {
    part: &amp;&#39;a str,
}

fn main() {
    let novel = String::from(&quot;Call me Ishmael. Some years ago...&quot;);
    let first_sentence = novel.split(&#39;.&#39;).next().expect(&quot;Could not find a &#39;.&#39;&quot;);
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}</code></pre>
<p>lifetime elision rules : if your code fits these cases, you don’t need to write the lifetimes explicitly</p>
<p>Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.</p>
<p>The compiler uses three rules to figure out the lifetimes of the references when there aren’t explicit annotations. The first rule applies to input lifetimes, and the second and third rules apply to output lifetimes. If the compiler gets to the end of the three rules and there are still references for which it can’t figure out lifetimes, the compiler will stop with an error. These rules apply to fn definitions as well as impl blocks.</p>
<p>The first rule is that the compiler assigns a lifetime parameter to each parameter that’s a reference. In other words, a function with one parameter gets one lifetime parameter: fn foo&lt;&#39;a&gt;(x: &amp;&#39;a i32); a function with two parameters gets two separate lifetime parameters: fn foo&lt;&#39;a, &#39;b&gt;(x: &amp;&#39;a i32, y: &amp;&#39;b i32); and so on.</p>
<p>The second rule is that, if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo&lt;&#39;a&gt;(x: &amp;&#39;a i32) -&gt; &amp;&#39;a i32.</p>
<p>The third rule is that, if there are multiple input lifetime parameters, but one of them is &amp;self or &amp;mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters. This third rule makes methods much nicer to read and write because fewer symbols are necessary.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[5. Start a private network]]></title>
            <link>https://velog.io/@myungha_cho/5.-Start-a-private-network</link>
            <guid>https://velog.io/@myungha_cho/5.-Start-a-private-network</guid>
            <pubDate>Tue, 24 May 2022 01:01:06 GMT</pubDate>
            <description><![CDATA[<p>이 튜토리얼은 <strong>권한을 가진 private validator</strong>들이 있는 작고 독립적인 블록체인 네트워크를 시작하는 방법을 다룸</p>
<p>모든 블록체인은 성공적으로 블록을 만들고 다음 블록으로 넘어가기 위해 네트워크의 모든 노드들이 메시지와 그 메시지들의 순서에 합의해야 함
각 블록은 특정 시점의 데이터 상태를 나타내고 그 상태에 대한 노드들의 동의를 <strong>합의</strong>라고 함
합의에 도달하기 위해 여러 알고리즘들이 사용됨:</p>
<ul>
<li>PoW : 검증자 노드들이 블록을 체인에 추가하기 위해 하는 연산 작업에 의해 </li>
<li>PoS : 암호화폐를 네트워크에 스테이킹한 양에 기반해 블록을 추가할 수 있는 검증자들을 선정</li>
<li>PoA : 검증자로 활동하는 인증된 계정들이 합의. 인증된 계정과 연관된 노드들이 블록을 만들 권한 가짐</li>
</ul>
<p>Substrate 노드 템플릿은 PoA를 사용하고 이를 <strong>authority round</strong> 또는 <strong>Aura</strong>합의라고 부름
Aura 합의 프로토콜은 round-robin 방식으로 블록을 생성하는 권한 있는 계정 리스트-<strong>authorities</strong>-에서 순환하도록 블록 생성을 제한함
*round robin : 프로세스들 사이에 우선순위를 두지 않고, 순서대로 할당</p>
<p>이 튜토리얼에서 실제로 이 합의 모델이 어떻게 작동하는지 알아볼 텐데, 먼저 노드 템플릿에 미리 정의된 계정들을 사용한 다음 인증된 집합에 새로운 권한을 추가해볼 것임</p>
<hr>
<h1 id="시작하기-전에">시작하기 전에</h1>
<ul>
<li>Rust와 Rust toolchain 설치</li>
<li>튜토리얼 1번 완료하고 Substrate node template 다운</li>
<li>CLI로 개발하는 것에 익숙하다고 가정</li>
<li>블록체인과 스마트 컨트랙트 플랫폼에 익숙하다고 가정</li>
</ul>
<hr>
<h1 id="튜토리얼-목표">튜토리얼 목표</h1>
<ul>
<li>미리 정의된 계정들을 사용해서 peer 블록체인 노드 시작하기</li>
<li>네트워크 권한으로 사용하기 위해 key pair 생성하기</li>
<li>커스텀 체인 명세 파일 만들기</li>
<li>private이면서 두 개의 노드를 갖는 블록체인 시작하기</li>
</ul>
<hr>
<h1 id="미리-정의된-계정들로-블록체인-시작">미리 정의된 계정들로 블록체인 시작</h1>
<p>private Substrate 네트워크를 시작하기 위해 key를 생성하기 전,<code>local</code>이라 불리는 미리 정의된 네트워크 명세를 사용해서 근본 원리를 배우고 미리 정의된 사용자 계정으로 실행해볼 수 있음</p>
<p>여기에서는 하나의 로컬 컴퓨터에서 <code>alice</code> 와 <code>bob</code>이라는 미리 정의된 계정들을 사용해서 두 개의 Substrate 노드들을 실행시킴으로써 private network를 시뮬레이션 할 것임</p>
<h2 id="첫-번째-블록체인-노드-시작">첫 번째 블록체인 노드 시작</h2>
<ol>
<li><p>터미널 열기</p>
</li>
<li><p>Substrate node template을 컴파일한 루트 디렉토리로 가기</p>
</li>
<li><p>다음 명령어를 실행해서 이전 체인 데이터 버리기</p>
</li>
</ol>
<pre><code class="language-bash">./target/release/node-template purge-chain --base-path /tmp/alice --chain local</code></pre>
<p>다음 프롬프트가 뜨면</p>
<pre><code class="language-bash">Are you sure to remove &quot;/tmp/alice/chains/local_testnet/db&quot;? [y/N]:</code></pre>
<ol start="4">
<li><code>y</code> 입력</li>
</ol>
<p>새 네트워크 시작할 땐 이전 체인 데이터 항상 지워야 함</p>
<ol start="5">
<li>다음 명령어를 실행해서 Alice 계정으로 로컬 블록체인 네트워크 시작
```bash
./target/release/node-template \</li>
</ol>
<p>--base-path /tmp/alice <br>--chain local <br>--alice <br>--port 30333 <br>--ws-port 9945 <br>--rpc-port 9933 <br>--node-key 0000000000000000000000000000000000000000000000000000000000000001 <br>--telemetry-url &quot;wss://telemetry.polkadot.io/submit/ 0&quot; <br>--validator</p>
<pre><code>
&gt; **CLI 옵션 알아보기**
- `--base-path` : 이 체인과 관련된 모든 데이터를 저장하는 디렉토리
- `--chain local`    : 사용할 체인 명세. 유효한 미리 정의된 체인 명세는  `local`, `development`, `staging`을 포함
- `--alice` : 노드의 keystore에 `Alice` 계정의 미리 정의된 키들을 추가. 이 설정으로 `Alice` 계정이 블록 생성과 finalization에 사용됨
- `--port 30333` : peer-to-peer (`p2p`) 트래픽을 수신할 포트 번호. 네으퉈크 시뮬레이션을 위해 같은 물리적 컴퓨터에서 두 개의 노드를 실행시키기 때문에, 적어도 하나의 계정에서는 다른 포트 번호를 명시해줘야 함
- `--ws-port 9945` : WebSocket 트래픽을 수신하기 위한 포트번호. 디폴트는  `9944`지만 이 튜토리얼에서는 `9945` 사용.
- `--rpc-port 9933`    : RPC 트래픽 수신하기 위한 포트번호 명시. 디폴트는 `9933`.
- `--node-key &lt;key&gt;` : `libp2p` 네트워킹에 사용할 Ed25519 비밀 키 명시. 개발과 테스트를 위해서만 이 옵션 사용해야 함
- `--telemetry-url`    : 원격 측정 데이터를 어디로 보낼지 명시. 이 튜토리얼에서는 Parity가 누구나 사용할 수 있도록 호스팅하는 서버에 보낼 것
- `--validator`    : 이 노드가 네트워크에서 블록 생성과 finalization에 참여한다는 것 명시
`./target/release/node-template --help` 명령어로 더 알아볼 수 있음

&gt; **표시되는 노드 메시지 리뷰**
노드가 성공적으로 시작했다면, 터미널에 네트워크 작동을 설명하는 메시지가 표시될 것
- `🔨 Initializing Genesis block/state (state: 0xea47…9ba8, header-hash: 0x9d07…7cce)` : 노드가 사용하는 **genesis block**을 식별. 다음 노드를 시작하면, 이 값이 동일한 것을 확인할 수 있음

- `🏷 Local node identity is: 12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp` : 유일하게 이 노드를 식별하는 문자열. 이 문자열은 `alice` 계정을 사용해서 노드를 시작할 때 사용된 `--node-key`에 의해 결정됨. 두 번째 노드를 시작할 때 이 노드를 연결하기 위해 이 문자열을 사용할 것

- `2021-03-10 17:34:37 💤 Idle (0 peers), best: #0 (0x9d07…7cce), finalized #0 (0x9d07…7cce), ⬇ 0 ⬆ 0` : 네트워크에 다른 노드가 없고 어떤 블록도 생성되고 있지 않음을 나타냄. 블록 생성을 시작하기 전에 반드시 다른 노드가 참여해야만 함

## 노드 정보 보기 위해 프론트엔드 추가

Polkadot-JS application을 사용해 노드 동작을 보기 위해:

1. 웹브라우저 열기

2. Polkadot-JS Explorer로 가기

Polkadot-JS Explorer 링크는 로컬 노드에 연결하기 위해 `rpc` URL 인자를 사용

&gt; 어떤 브라우저들은 로컬 노드에 연결하는 것을 막는 광고 차단 기능이 있음. 만약 로컬 노드에 연결하는 것에 문제가 있다면, 광고 차단이 활성화되어있는지 확인하고 비활성화 하거나 다른 브라우저에서 실행해보길!

1. 상단 왼쪽 코너에 있는 네트워크 아이콘 클릭
2. 사용가능한 네트워크 리스트에서 **DEVELOPMENT** 클릭
3. 커스텀 엔드포인트가 로컬호스트와 WebSocket 트래픽 수신을 위해 명시한 포트번호로 설정되어 있는지 확인. 다른 네트워크, 노드, 엔드포인트에 연결할 때도 하나의 Polkadot-JS application을 사용할 수 있음

## 블록체인 네트워크에 두 번째 노드 추가

이제 `alice` 계정 키를 사용해서 시작한 노드가 작동하니까, `bob` 계정을 사용해 네트워크에 또다른 노드를 추가할 수 있음
이미 돌아가는 네트워크에 참여하기 때문에, 새로운 노드가 참여할 때 이 네트워크를 식별하는 데 돌아가는 노드를 사용할 수 있음. 

돌아가는 블록체인에 노드를 추가하기 위해서:

1. **새** 터미널 열기

2. Substrate node template을 컴파일한 루트 디렉토리로 가기

3. 다음 커맨드로 이전 체인 데이터를 지우기
```bash
./target/release/node-template purge-chain --base-path /tmp/bob --chain local -y</code></pre><ol start="4">
<li>다음 커맨드를 실행해서 <code>bob</code>계정을 사용해서 두 번째 로컬 블록체인 노드를 시작</li>
</ol>
<pre><code class="language-bash">./target/release/node-template \
--base-path /tmp/bob \
--chain local \
--bob \
--port 30334 \
--ws-port 9946 \
--rpc-port 9934 \
--telemetry-url &quot;wss://telemetry.polkadot.io/submit/ 0&quot; \
--validator \
--bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp</code></pre>
<p>이 커맨드와 앨리스 시작할 때 썼던 커맨드 사이의 차이점에 주목</p>
<ul>
<li>두 개의 노드가 하나의 같은 컴퓨터에서 돌아가기 때문에,<code>--base-path</code>, <code>--port</code>, <code>--ws-port</code>, <code>--rpc-port</code> 옵션에 다른 값을 명시해줘야 함</li>
</ul>
<p>-<code>--bootnodes</code> 옵션을 사용해 <code>alice</code>에 의해 시작된 노드를 하나의 부트 노드로 명시</p>
<p><code>--bootnodes</code> 옵션은</p>
<ul>
<li><code>ip4</code> : 이 노드의 IP주소가 IPv4형식</li>
<li><code>127.0.0.1</code> : 돌아갈 노드의 IP 주소 명시(지금은 <code>localhost</code>)</li>
<li><code>tcp</code> : peer-to-peer 소통을 위한 프로토콜</li>
<li><code>30333</code> : peer-to-peer 소통을 위한 포트번호</li>
<li><code>12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp</code> : 이 네트워크와 소통하기 위해 돌아가고 있는 노드를 식별함. 이 경우 <code>alice</code> 계정으로 시작한 노드의 식별자임</li>
</ul>
<hr>
<h1 id="블록들이-생성되고-finalized되는-것-검증">블록들이 생성되고 finalized되는 것 검증</h1>
<p>After you start the second node, the nodes should connect to each other as peers and start producing blocks.</p>
<p>블록들이 finalized 되고 있는지 검증하기 위해:</p>
<ol>
<li>첫번째 노드 터미널에 다음같은 줄이 뜨는지 확인:</li>
</ol>
<pre><code class="language-bash">2021-03-10 17:47:32  🔍 Discovered new external address for our node: /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp
2021-03-10 17:47:32  🔍 Discovered new external address for our node: /ip4/&lt;your computer&#39;s LAN IP&gt;/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp
2021-03-10 17:47:33  💤 Idle (1 peers), best: #0 (0x9d07…7cce), finalized #0 (0x9d07…7cce), ⬇ 1.0kiB/s ⬆ 1.0kiB/s
2021-03-10 17:47:36  🙌 Starting consensus session on top of parent 0x9d07d1757a9ca248e58141ce52a11fca37f71007dec16650b87a853f0d4c7cce
2021-03-10 17:47:36  🎁 Prepared block for proposing at 1 [hash: 0x727826a5e6fba9a13af11422d4677b5f0743cc733c382232328e69fd307d1d2f; parent_hash: 0x9d07…7cce; extrinsics (1): [0x768a…a9e2]]
2021-03-10 17:47:36  🔖 Pre-sealed block for proposal at 1. Hash now 0x4841d8b2e62483fa4702b3ddcd1b603803842374dcdc1e9533ad407708b33dd8, previously 0x727826a5e6fba9a13af11422d4677b5f0743cc733c382232328e69fd307d1d2f.
2021-03-10 17:47:36  ✨ Imported #1 (0x4841…3dd8)
2021-03-10 17:47:36  ✨ Imported #1 (0xb241…2ae8)
2021-03-10 17:47:38  💤 Idle (1 peers), best: #1 (0x4841…3dd8), finalized #0 (0x9d07…7cce), ⬇ 0.8kiB/s ⬆ 0.8kiB/s
2021-03-10 17:47:42  ♻️  Reorg on #1,0x4841…3dd8 to #2,0x8b6a…dce6, common ancestor #0,0x9d07…7cce
2021-03-10 17:47:42  ✨ Imported #2 (0x8b6a…dce6)
2021-03-10 17:47:43  💤 Idle (1 peers), best: #2 (0x8b6a…dce6), finalized #0 (0x9d07…7cce), ⬇ 0.8kiB/s ⬆ 0.7kiB/s
2021-03-10 17:47:48  🙌 Starting consensus session on top of parent 0x8b6a3ab2fe9891b1af008ea0d92dae9bc84cfa5578231e81066d47928822dce6
2021-03-10 17:47:48  🎁 Prepared block for proposing at 3 [hash: 0xb887aef2097eff5869e38ccec0302bce372ad05ac2cdf9cc4725c38ec071fb7a; parent_hash: 0x8b6a…dce6; extrinsics (1): [0x82ac…2f20]]
2021-03-10 17:47:48  🔖 Pre-sealed block for proposal at 3. Hash now 0x34d608dd8be6b82bef4a7aaae1ec80930a5c4b8cf9bdc99013410e91544f3a2a, previously 0xb887aef2097eff5869e38ccec0302bce372ad05ac2cdf9cc4725c38ec071fb7a.
2021-03-10 17:47:48  ✨ Imported #3 (0x34d6…3a2a)
2021-03-10 17:47:48  💤 Idle (1 peers), best: #3 (0x34d6…3a2a), finalized #0 (0x9d07…7cce), ⬇ 0.7kiB/s ⬆ 0.8kiB/s
2021-03-10 17:47:53  💤 Idle (1 peers), best: #3 (0x34d6…3a2a), finalized #1 (0xb241…2ae8), ⬇ 0.6kiB/s ⬆ 0.7kiB/s
2021-03-10 17:47:54  ✨ Imported #4 (0x2b8a…fdc4)
2021-03-10 17:47:58  💤 Idle (1 peers), best: #4 (0x2b8a…fdc4), finalized #2 (0x8b6a…dce6), ⬇ 0.7kiB/s ⬆ 0.6kiB/s</code></pre>
<ol start="2">
<li><p>두 번째 노드를 시작한 터미널에서도 비슷한 출력값이 나오는지 확인</p>
</li>
<li><p>Polkadot-JS Explorer를 열고 네트워크가 블록 만들고 finalize 하는지 확인</p>
</li>
<li><p>Control-c  눌러서 두 노드 다 멈추기</p>
</li>
</ol>
<hr>
<h1 id="나만의-key-만들기">나만의 key 만들기</h1>
<h2 id="key-생성-옵션">Key 생성 옵션</h2>
<p><code>node-template</code> 서브커맨드인 <strong>Subkey</strong> 를 사용해서 키 쌍을 만들거나 다른 키 생성 유틸리티를 사용할 수도 있음
여기에선 Substrate node template과 <code>key</code> 서브 커맨드를 사용해서 로컬에 키를 생성하는 방법을 다룰 것</p>
<h2 id="노드-템플릿-사용해서-로컬-키-생성">노드 템플릿 사용해서 로컬 키 생성</h2>
<p>프로덕션 블록체인에서는 키를 만들 때 한 번도 인터넷에 연결된 적이 없는 분리된 컴퓨터를 사용해야 하고, 최소한 자신이 컨트롤할 수 없는 퍼블릭 또는 프라이빅 블록체인에서 사용할 키를 생성하기 전엔 인터넷에 연결하지 말아야 함</p>
<p>근데 여기선 노드 템플릿의 <code>key</code> 서브커맨드를 사용해서 로컬에 랜덤 키를 생성할 거라 인터넷에 연결되어 있어도 됨</p>
<p>노드 템플릿으로 키 생성하기 위해:</p>
<ol>
<li><p>터미널 열기</p>
</li>
<li><p>Substrate node template 컴파일한 루트 디렉토리로 가기</p>
</li>
<li><p>다음 커맨드 실행해서 랜덤 비밀 phrase와 키 생성</p>
</li>
</ol>
<pre><code class="language-bash">./target/release/node-template key generate --scheme Sr25519 --password-interactive</code></pre>
<ol start="4">
<li>만든 키에 대한 비밀번호 입력</li>
</ol>
<p>키를 생성하고 다음과 비슷한 출력값을 보여줘야 함</p>
<pre><code class="language-bash">Secret phrase:  pig giraffe ceiling enter weird liar orange decline behind total despair fly
Secret seed:       0x0087016ebbdcf03d1b7b2ad9a958e14a43f2351cd42f2f0a973771b90fb0112f
Public key (hex):  0x1a4cc824f6585859851f818e71ac63cf6fdc81018189809814677b2a4699cf45
Account ID:        0x1a4cc824f6585859851f818e71ac63cf6fdc81018189809814677b2a4699cf45
Public key (SS58): 5CfBuoHDvZ4fd8jkLQicNL8tgjnK8pVG9AiuJrsNrRAx6CNW
SS58 Address:      5CfBuoHDvZ4fd8jkLQicNL8tgjnK8pVG9AiuJrsNrRAx6CNW</code></pre>
<p><code>aura</code>를 사용해 블록들을 생성하는 한 노드에 대한 Sr25519 키를 만든 것임. 위에서 계정에 대한 Sr25519 공개 키는 </p>
<p><code>5CfBuoHDvZ4fd8jkLQicNL8tgjnK8pVG9AiuJrsNrRAx6CNW</code>임</p>
<ol start="5">
<li>Ed25519 서명 방법을 사용해서 키를 만들어내기 위해 방금 생성한 계정의 <strong>비밀 구문</strong>을 사용
다음과 같은 명령어 실행</li>
</ol>
<pre><code>./target/release/node-template key inspect --password-interactive --scheme Ed25519 &quot;pig giraffe ceiling enter weird liar orange decline behind total despair fly&quot;</code></pre><p><code>5CuqCGfwqhjGzSqz5mnq36tMe651mU9Ji8xQ4JRuUTvPcjVN</code>임</p>
<ol start="6">
<li>키 만드는 데 썼던 비밀번호 입력
다음과 비슷한 출력값 보일 것임<pre><code class="language-bash">Secret phrase `pig giraffe ceiling enter weird liar orange decline behind total despair fly` is account:
Secret seed:       0x0087016ebbdcf03d1b7b2ad9a958e14a43f2351cd42f2f0a973771b90fb0112f
Public key (hex):  0x2577ba03f47cdbea161851d737e41200e471cd7a31a5c88242a527837efc1e7b
Public key (SS58): 5CuqCGfwqhjGzSqz5mnq36tMe651mU9Ji8xQ4JRuUTvPcjVN
Account ID:        0x2577ba03f47cdbea161851d737e41200e471cd7a31a5c88242a527837efc1e7b
SS58 Address:      5CuqCGfwqhjGzSqz5mnq36tMe651mU9Ji8xQ4JRuUTvPcjVN</code></pre>
이제 <code>grandpa</code>를 사용해 하나의 노드에 블록을 finalize하는 Ed25519 키를 생성한 것임. 여기서 Ed25519 공개 키는</li>
</ol>
<p><code>5CuqCGfwqhjGzSqz5mnq36tMe651mU9Ji8xQ4JRuUTvPcjVN</code> 임</p>
<h2 id="두-번째-키-집합-생성">두 번째 키 집합 생성</h2>
<ul>
<li>미리 생성된 계정을 쓰거나</li>
<li>방금 했던 거 한 번 더 해서 로컬에 두 개의 키 쌍 만들거나</li>
<li>자식 키 쌍을 만들거나</li>
<li>다른 참여자들을 모집해서 
두 개의 계정을 사용할 수 있음</li>
</ul>
<p>이 튜토리얼에서 사용된 두 번째 키 쌍은 다음과 같음:</p>
<ul>
<li>Sr25519: 5EJPj83tJuJtTVE2v7B9ehfM7jNT44CBFaPWicvBwYyUKBS6 for <code>aura</code>.</li>
<li>Ed25519: 5FeJQsfmbbJLTH1pvehBxrZrT5kHvJFj84ZaY5LK7NU87gZS for <code>grandpa</code>.</li>
</ul>
<hr>
<h1 id="커스텀-체인-명세-만들기">커스텀 체인 명세 만들기</h1>
<p>블록체인에 사용할 키들을 생성했다면, 방금 만든 키들을 사용한 커스텀 <strong>체인 스펙</strong>을 만들고 이 커스텀 체인 스펙을 <strong>검증자</strong>라 불리는 신뢰받는 네트워크 참여자들에게 공유할 준비를 다 한 것임</p>
<p>다른 사람들을 네트워크의 참여자로 포함하고 싶으면, 그들이 자신만의 키를 가지고 있어야 하고, 그렇다면 <code>로컬</code> 체인 스펙을 대체할 커스텀 체인 스펙을 만들 수 있음</p>
<h2 id="존재하는-체인-명세-수정하기">존재하는 체인 명세 수정하기</h2>
<p>이전에는 노드들을 블록체인에 추가할 때 <code>--chain local</code> 옵션으로 <code>로컬</code> 체인 명세를 사용했었음<br>완전히 새로운 체인 명세를 사용하기보단, 기존에 사용했던 것을 수정할 것임</p>
<ol>
<li>터미널 창 열기</li>
<li>Substrate node template 컴파일한 디렉토리로 가기</li>
<li>다음 커맨드 실행해서 로컬 체인 스펙을 <code>customSpec.json</code> 이라는 이름으로 export하기</li>
</ol>
<pre><code class="language-bash">./target/release/node-template build-spec --disable-default-bootnode --chain local &gt; customSpec.json</code></pre>
<p>텍스트 에디터로 <code>customSpec.json</code> 파일을 열면, <code>cargo build --release</code> 커맨드를 사용해 만들었던 런타임을 위한 Wasm 바이너리를 다음 큰 블롭들을 포함한 여러 필드들을 볼 수 있을 것임
전체 파일을 보기보단, 처음과 마지막의 몇 줄에서만 바꿔야 하는 몇몇 필드들을 보겠음</p>
<ol start="4">
<li><p>다음 커맨드로 <code>customSpec.json</code> 파일의 첫 번째 몇 필드들을 미리보기</p>
<pre><code class="language-bash">head customSpec.json</code></pre>
<p>출력값:</p>
<pre><code class="language-bash">{
&quot;name&quot;: &quot;Local Testnet&quot;,
&quot;id&quot;: &quot;local_testnet&quot;,
&quot;chainType&quot;: &quot;Local&quot;,
&quot;bootNodes&quot;: [],
&quot;telemetryEndpoints&quot;: null,
&quot;protocolId&quot;: null,
&quot;properties&quot;: null,
&quot;consensusEngine&quot;: null,
&quot;codeSubstitutes&quot;: {},</code></pre>
</li>
<li><p>다음 커맨드로 <code>customSpec.json</code> 파일의 마지막 몇 필드들을 미리보기</p>
<pre><code>tail -n 80 customSpec.json</code></pre><p>런타임에 사용되는 <code>sudo</code>와  <code>balances</code> 팔레트같은 여러 팔레트들의 디테일을 포함한 wasm 바이너리 필드들이 보일 것임</p>
</li>
<li><p>텍스트 에디터로 <code>customSpec.json</code> 파일 열기</p>
</li>
<li><p>이 체인 스펙이 커스텀 체인 스펙이라는 걸 식별하기 위해 <code>name</code> 필드를 수정</p>
</li>
</ol>
<pre><code class="language-json">&quot;name&quot;: &quot;My Custom Testnet&quot;,</code></pre>
<ol start="8">
<li><p>각 네트워크 참여자의 Sr25519 SS58 주소 키들을 추가함으로써 노드들의 블록 생성 권한을 명시하기 위해 <code>aura</code> 필드 수정</p>
<pre><code class="language-json">&quot;aura&quot;: {
 &quot;authorities&quot;: [
   &quot;5CfBuoHDvZ4fd8jkLQicNL8tgjnK8pVG9AiuJrsNrRAx6CNW&quot;,
   &quot;5EJPj83tJuJtTVE2v7B9ehfM7jNT44CBFaPWicvBwYyUKBS6&quot;
 ]
},</code></pre>
</li>
<li><p>각 네트워크 참여자의 Ed25519 SS58 주소 키들을 추가함으로써 노드들의 블록 finalize 권한을 명시하기 위해 <code>grandpa</code> 필드 수정</p>
<pre><code class="language-json">&quot;grandpa&quot;: {
 &quot;authorities&quot;: [
   [
     &quot;5CuqCGfwqhjGzSqz5mnq36tMe651mU9Ji8xQ4JRuUTvPcjVN&quot;,
     1
   ],
   [
     &quot;5FeJQsfmbbJLTH1pvehBxrZrT5kHvJFj84ZaY5LK7NU87gZS&quot;,
     1
   ]
 ]
},</code></pre>
<p><code>grandpa</code>에는 <code>authorities</code> 필드에 두 개의 데이터 값이 있다는 것에 주목.
첫 번째 값은 주소 키. 두 번째 값은 <strong>weighted votes</strong> 를 지원하는 데 사용되는 값. 여기서 각 검증자는 1투표의 무게를 가짐</p>
</li>
<li><p>바꾼 거 저장파고 파일 닫기</p>
</li>
</ol>
<h2 id="검증자-추가">검증자 추가</h2>
<p><code>aura</code>와 <code>grandpa</code> 부분을 수정하면 체인 스펙에서 권한 주소들을 추가하거나 바꿀 수 있음
원하는 만큼 많은 검증자들을 추가하기 위해 이렇게 할 것임</p>
<ul>
<li><p><code>aura</code>에 <strong>Sr25519</strong> 주소들을 포함하도록 수정</p>
</li>
<li><p><code>grandpa</code>에 <strong>Ed25519</strong> 주소들과 투표 무게를 포함하도록 수정</p>
</li>
</ul>
<p>각 검증자에 고유한 키를 사용해야 함. 만약 같은 키를 사용하면, 충돌 나는 블록 생성할 것임</p>
<h2 id="raw-형식을-사용해서-체인-명세-바꾸기">raw 형식을 사용해서 체인 명세 바꾸기</h2>
<p>체인 스펙 준비한 다음, 사용하기 전에 raw 스펙으로 바꿔어야 함 raw 체인 스펙은 그냥 체인 스펙과 같은 정보뿐만 아니라 이 노드가 로컬 스토리지에 갖고 있는 데이터를 참조하기 위해 사용하는 스토리지 키를 인코딩한 것을 가지고 있음
raw 체인 스펙을 배포해야 각 노드가 적절한 스토리지 키를 사용해 데이터를 저장한다는 것을 확신할 수 있음</p>
<ol>
<li><p>터미널 창 열기</p>
</li>
<li><p>Substrate node template을 컴파일한 곳으로 가기</p>
</li>
<li><p>다음 커맨드를 실행해 <code>customSpec.json</code> 체인 스펙을 <code>customSpecRaw.json</code> 라는 이름의 raw 형식으로 바꾸기</p>
<pre><code class="language-bash">./target/release/node-template build-spec --chain=customSpec.json --raw --disable-default-bootnode &gt; customSpecRaw.json</code></pre>
</li>
</ol>
<h2 id="다른-사람들과-체인-명세-공유하기">다른 사람들과 체인 명세 공유하기</h2>
<p>다른 참여다즐에게 공유한 프라이빗 블록체인 네트워크를 만든다면 도직 한 사람만 체인 스펙을 만들고 그 스펙의 raw 버전을 공유하도록 확신해야 함 모두가 똑같은 raw 파일을 사용해야 한다는 뜻</p>
<p>러스트 컴파일러가 결정적으로 재생산가능하지 않은 최적화된 웹어셈블리 바이너리를 만들기 때문에, wasm 런타임을 만드는 각 사람들ㅇ르 약간 다른 wasm 블롭을 갖고 있음. 결정적임을 확신하기 위해, 모든 블록체인 네트워크 참여자들을 반드시 같은 raw 체인 스펙을 사용해야만 함</p>
<hr>
<h1 id="private-network-시작하기">private network 시작하기</h1>
<p>After you distribute the custom chain specification to all network participants, you&#39;re ready to launch your own private blockchain. The steps are similar to the steps you followed in Start the blockchain using predefined accounts. If you follow the steps in this tutorial, however, you can add multiple computers to your network.</p>
<p>To continue, verify the following:</p>
<ul>
<li>You have generated or collected the account keys for at least two authority accounts.</li>
<li>You have updated your custom chain specification to include the keys for block production (<code>aura</code>) and block finalization (<code>grandpa</code>).</li>
<li>You have converted your custom chain specification to raw format and distributed the raw chain specification to the nodes participating in the private network.</li>
</ul>
<p>If you have completed these steps, you are ready to start the first node in the private blockchain.</p>
<h2 id="첫-번째-노드-시작">첫 번째 노드 시작</h2>
<p>프라이빗 블록체인 네트워크의 첫 번째 참여자로서, <strong>bootnode</strong>라 불리는 첫 번째 노드를 시작할 책임이 있음</p>
<ol>
<li><p>터미널을 열고</p>
</li>
<li><p>Substrate node template을 컴파일한 디렉토리로 가기</p>
</li>
<li><p>다음과 같은 커맨드를 실해앻서 커스텀 체인 스펙으로 첫 번째 노드 시작하기</p>
</li>
</ol>
<pre><code class="language-bash">./target/release/node-template \
--base-path /tmp/node01 \
--chain ./customSpecRaw.json \
--port 30333 \
--ws-port 9945 \
--rpc-port 9933 \
--telemetry-url &quot;wss://telemetry.polkadot.io/submit/ 0&quot; \
--validator \
--rpc-methods Unsafe \
--name MyNode01</code></pre>
<ul>
<li><code>--base-path</code> : 첫 번째 노드와 연관된 커스텀 위치 명시</li>
<li><code>--chain</code> : 커스텀 체인 스펙 명시</li>
<li><code>--rpc-methods Unsafe</code> : 이 블록체인이 프로덕션 세팅을 사용하지 않고 있기 때문에 안전하지 않은 커뮤니케이션 모드를 사용할 수 있도록 해줌</li>
<li><code>--name</code> : telemetry UI에 사람이 읽을 수 있는 이름을 붙이기 위한 옵션</li>
</ul>
<p>미리 정의된 계정을 사용하지 않고 있기 때문에 별도로 지금 사용하고 있는 키를 키스토어에 추가해야 함</p>
<h2 id="노드-작동에-대한-정보-보기">노드 작동에 대한 정보 보기</h2>
<p>로컬 노드 시작하면 수행 중인 동작에 대해 터미널에 로그가 뜰 것임</p>
<ul>
<li><code>(state: 0x2bde…8f66, header-hash: 0x6c78…37de)</code> : 제네시스 블록이 초기화되고 있음</li>
<li><code>12D3KooWLmrYDLoNTyTYtRdDyZLWDe1paxzxTw5RgjmHLfzW96SX</code> : <em>로컬 노드 신원</em>을 명시</li>
<li>이 노드에 사용된 <strong>IP address</strong> 가 로컬 호스트 <code>127.0.0.1</code>임을 명시</li>
</ul>
<h2 id="keystore에-key-추가">keystore에 key 추가</h2>
<p>첫 번째 노드를 시작해도 어떤 블록들도 아직 생성되지 않고 있다. 
다음으로 네트워크에 각 노드의 두 가지 탑입의 키들을 키스토어에 추가할 것이다.</p>
<p>각 노드에 대해:</p>
<p>블록 생성을 가능하게 하기 위해 <code>aura</code> 권한 키 추가
블록 finalize를 가능하게 하기 위해 <code>grandpa</code> 권한 키 추가</p>
<p>키를 키스토어에 추가하기 위해 로컬에서 생성한 비밀 키를 삽입하는 <code>key</code> 서브커맨드 사용할 수 있음</p>
<ol>
<li><p>터미널 열고</p>
</li>
<li><p>Substrate node template 컴파일한 디렉토리로 가서</p>
</li>
<li><p>다음과 비슷한 커맨드로  <code>kye</code> 서브커맨드로 생성된 비밀 키를 <code>aura</code>에 추가</p>
</li>
</ol>
<pre><code class="language-bash">./target/release/node-template key insert --base-path /tmp/node01 \
--chain customSpecRaw.json \
--scheme Sr25519 \
--suri &lt;your-secret-seed&gt; \
--password-interactive \
--key-type aura</code></pre>
<p><code>&lt;your-secret-seed&gt;</code> 를 노드 템플릿을 사용해 생성한 첫 번째 키 페어의 비밀 구문이나 비밀 시드로 교체</p>
<ol start="4">
<li><p>키 만들 때 썼던 비밀번호 입력</p>
</li>
<li><p>다음 커맨드를 실행해서 key 서브커맨드로 만든 <code>grandpa</code> 비밀 키 추가
```bash
./target/release/node-template key insert --base-path /tmp/node01 \</p>
</li>
</ol>
<p>--chain customSpecRaw.json <br>--scheme Ed25519 <br>--suri <your-secret-key> <br>--password-interactive <br>--key-type gran</p>
<pre><code>
6. 키 만들 때 쓴 비밀번호 입력

7. 다음 커맨드로 `node01`의 키가 키스토어에 있는지 확인
```bash
ls /tmp/node01/chains/local_testnet/keystore</code></pre><p>다음과 비슷한 출력 보여줄 것임</p>
<pre><code class="language-bash">617572611441ddcb22724420b87ee295c6d47c5adff0ce598c87d3c749b776ba9a647f04
6772616e1441ddcb22724420b87ee295c6d47c5adff0ce598c87d3c749b776ba9a647f04</code></pre>
<h2 id="다른-참여자들을-참가하게-해주기">다른 참여자들을 참가하게 해주기</h2>
<p><code>--bootnodes</code>와 <code>--validator</code> 옵션으로 다른 검증자들이 네트워크에 참여할 수 있게 해줄 수 있음</p>
<p>프라이빗 네트워크에서 두 번째 검증자를 추가하기 위해:</p>
<ol>
<li><p>터미널 열고</p>
</li>
<li><p>Substrate node template 컴파일한 디렉토리로 가서</p>
</li>
<li><p>다음 커맨드로 두 번째 블록체인 노드 시작:
```bash
./target/release/node-template \</p>
</li>
</ol>
<p>--base-path /tmp/node02 <br>--chain ./customSpecRaw.json <br>--port 30334 <br>--ws-port 9946 <br>--rpc-port 9934 <br>--telemetry-url &quot;wss://telemetry.polkadot.io/submit/ 0&quot; <br>--validator <br>--rpc-methods Unsafe <br>--name MyNode02 <br>--bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWLmrYDLoNTyTYtRdDyZLWDe1paxzxTw5RgjmHLfzW96SX <br>--password-interactive</p>
<pre><code>커맨드에 올바른`bootnode` 식별자를 썼는지 확인

이 노드가 그 프라이빗 네트워크의 검증자란 것을 명시하기 위해 `base-path`, `name` ,`validator` 옵션을 사용했다는 것 주목
그리고 모든 검증자들은 반드시 동일한 체인 스펙을 사용해야 함

4. 다음과 같은 커맨드 실행해서 `key` 로 만들어낸 비밀 키 `aura`에 추가:
```bash
./target/release/node-template key insert --base-path /tmp/node02 \
--chain customSpecRaw.json \
--scheme Sr25519 \
--suri &lt;second-participant-secret-seed&gt; \
--password-interactive \
--key-type aura</code></pre><p><code>&lt;second-participant-secret-seed&gt;</code>를 두 번째 키 쌍을 만들 때 얻은 비밀 구문이나 비밀 시드로 교체 
aura 키 타입은 블록 생성을 위해 필요함</p>
<ol start="5">
<li><p>이 키를 만들 때 쓴 비번 입력</p>
</li>
<li><p>다음과 같은 커맨드 실행해서 <code>key</code> 로 만들어낸 비밀 키를 로컬 키스토어에 저장하도록 <code>grandpa</code>에 추가:</p>
</li>
</ol>
<pre><code class="language-bash">./target/release/node-template key insert --base-path /tmp/node02 \
--chain customSpecRaw.json \
--scheme Ed25519 \
--suri &lt;second-participant-secret-seed&gt; \
--password-interactive \
--key-type gran</code></pre>
<p><code>&lt;second-participant-secret-seed&gt;</code>를 두 번째 키 페어를 생성할 때 얻은 비밀 구문이나 비밀 시드로 교체. <code>gran</code> 키 타입은 블록 finalize에 필요함</p>
<p>블록 finalizatiion은 각 노드의 키스토어에 그들의 키를 추가하기 위해 최소한 2/3 만큼의 검증자가 필요함
체인 스펙에서 두 명의 검증자로 설정되었기 때문에, 블록 finalization은 두 번째 노드가 키를 추가한 다음에 시작됨</p>
<ol start="7">
<li><p>키를 만들 때 사용한 비번 입력</p>
</li>
<li><p>다음 커맨드로 <code>node02</code>의 키가 키스토어에 있는지 확인: </p>
</li>
</ol>
<pre><code class="language-bash">ls /tmp/node02/chains/local_testnet/keystore</code></pre>
<p>다음과 비슷한 출력값 확인:</p>
<pre><code class="language-bash">617572611a4cc824f6585859851f818e71ac63cf6fdc81018189809814677b2a4699cf45
6772616e1a4cc824f6585859851f818e71ac63cf6fdc81018189809814677b2a4699cf45</code></pre>
<p><code>grandpa</code> 키를 추가한 다음에 Substrate 노드를 재시작해줘야 하므로 블록들이 finalized되는 것을 보기 전에 반드시 껐다가 재시작해줘야 함</p>
<ol start="9">
<li><p>Control-c 눌러서 끄고</p>
</li>
<li><p>다음 커맨드로 두 번째 노드 재시작:
```bash
./target/release/node-template \</p>
</li>
</ol>
<p>--base-path /tmp/node02 <br>--chain ./customSpecRaw.json <br>--port 30334 <br>--ws-port 9946 <br>--rpc-port 9934 <br>--telemetry-url &quot;wss://telemetry.polkadot.io/submit/ 0&quot; <br>--validator <br>--rpc-methods Unsafe <br>--name MyNode02 <br>--bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWLmrYDLoNTyTYtRdDyZLWDe1paxzxTw5RgjmHLfzW96SX <br>--password-interactive</p>
<pre><code>`/tmp/node01`와 `/tmp/node02`에 있는 각각의 키스토어에 두 노드의 키를 모두 추가한 후에 재시작하면, 같은 제네시스 블록과 상태 루트 해시를 볼 수 있을 것임

그리고 각 노드는 하나의 피어를 가지고 (`1 peers`), 블록 제안을 하고(`best: #2 (0xe111…c084)`), 몇 초 뒤에 새 블록이 두 개의 노드에서 모두 finalized되는 것을 확인할 수 있음

---


# 다음 단계
지금까지 신뢰하는 참여자들로 프라이빗 블록체인을 시작하는 법을 배웠음
- 어떻게 동료 블록체인 노드를 시작하고 멈추는지
- 나만의 비밀 키 쌍을 어떻게 만드는지
- 내가 생성한 키들을 사용하는 커스텀 체인 스펙을 어떻게 만드는지
- 나의 커스텀 체인 스펙을 사용하는 프라이빗 네트워크에 어떻게 검증자를 추가하는지

더 알아보면 좋을 것들:
- [Executor](https://docs.substrate.io/v3/advanced/executor/) - 체인 스펙에 대한  중요한 컴포넌트인 웹 어셈블리 런타임에 대한 정보
- [Accounts](https://wiki.polkadot.network/docs/learn-accounts), [Key management](https://wiki.polkadot.network/docs/learn-account-generation) - 키 생성과 스토리지 옵션에 대한 정보
- [Cryptography](https://docs.substrate.io/v3/advanced/cryptography/) : 다른 키를 사용해서 서명하는 방법에 대한 정보</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[6. Start a Permissioned Network]]></title>
            <link>https://velog.io/@myungha_cho/6.-Start-a-Permissioned-Network</link>
            <guid>https://velog.io/@myungha_cho/6.-Start-a-Permissioned-Network</guid>
            <pubDate>Tue, 24 May 2022 00:38:39 GMT</pubDate>
            <description><![CDATA[<h1 id="소개">소개</h1>
<p><code>node-authorization</code> 팔레트를 사용해 Substrate에서 허가형 네트워크를 구성하는 방법에 대해 다룰 것</p>
<p>퍼블릭 블록체인 : 모두가 노드를 운영함으로써 네트워크에 참여 가능
허가형 블록체인 : 권한있는 노드들만 블록 검증하거나 거래 전파시킬 수 있음</p>
<p>Proof of Existence (2번 튜토리얼)을 완료했고 Substrate의 P2P 네트워킹에 익숙하다(Private Network Tutorial 5번 튜토리얼을 완료했다)고 가정할 것임</p>
<hr>
<h1 id="여기서-할-것">여기서 할 것</h1>
<ol>
<li>노트 템플릿에<code>node-authorization</code> 팔레트 추가</li>
<li>여러 노드들을 시작하고 새로운 노드들이 참여할 수 있도록 권한 주기</li>
</ol>
<hr>
<h1 id="학습-목표">학습 목표</h1>
<ul>
<li><code>node-authorization</code> 팔레트를 런타임에서 사용하는 방법 배우기</li>
<li>여러 노드들로 구성된 허가형 네트워크 만드는 법 배우기</li>
</ul>
<hr>
<h1 id="node-authorization팔레트-추가"><code>node-authorization</code>팔레트 추가</h1>
<p><code>node-authorization</code> 팔레트는 Substrate의 FRAME에 내장된 팔레트로, 허가형 네트워크를 위한 노드 설정을 관리
각 노드는<code>Vec&lt;u8&gt;</code> 타입의 <strong>PeerId</strong>에 의해 식별됨
각 노드를 claim한 <strong>AccountId</strong>가 PeerId를 소유함</p>
<p>이 팔레트에서 네트워크에 참여하고 싶은 노드들에게 권한을 주는 두 가지 방법</p>
<ol>
<li><p>그 사이에 어떤 연결이 허가되었는지 잘 알려진 노드들의 집합에 참여한다. 그러기 위해 거버넌스(또는 Sudo)에 의해 허가받아야 함 -&gt; 뭔 말???</p>
</li>
<li><p>특정 노드로부터 짝 연결을 요청하기. 잘 알려진 노드일 수도, 일반 노드일 수도 있음</p>
</li>
</ol>
<p><strong>PeerId</strong>와 연결된 노드는 반드시 하나의 <strong>주인</strong>을 가져야 함
잘 알려진 노드의 주인은 그 노드를 추가할 때 명시됨
만약 일바 노드라면, 어떤 사용자든지 해당 노드의 주인으로써 PeerId를 등록할 수 있음
가짜 등록을 막기 위해 노드 관리자는 노드를 시작하기 전에 등록해야 하고, 따라서 뒤에 그 네트워크에 등록하는 누구에게나 PeerId를 드러냄</p>
<p>노드의 주인은 자기의 노드에 대해 연결을 추가하고 삭제할 수 있음
정확히 말하자면, 잘 알려진 노드들 사이의 연결은 변경할 수 없고, 언제나 서로 연결될 수 있음. 
대신, 잘 알려진 노드와 일반 노드 사이 또는 두 개의 일반 노드와 서브 노드들 사이의 연결을 조절할 수 있음</p>
<p>The <code>node-authorization</code> 팔레트는 노드의 연결을 설정하기 위해 offchain worker 시스템을 통합함
offchain worker가 권한이 없는 노드들에게 기본적으로 사용불가능하도록 정확한 CLI 플래그로 offchain worker를 사용해야 함</p>
<blockquote>
<p><strong>Off-chain worker</strong> : 외부 세상과 소통하기 위한 확장된 API들에 대한 접근 권한을 가짐</p>
</blockquote>
<ul>
<li>연산 결과를 발표하기 위해 체인에 거래를 제출하는 능력</li>
<li>worker가 외부 서비스로부터 데이터를 가져오고 접근할 수 있게 해 주는 완전기능 HTTP client</li>
<li>선언문이나 거래를 검증하고 사인하기 위한 지역 keystore에 대한 접근권한</li>
<li>모든 off-chain worker 사이에 공유되는 지역 key-value 데이터베이스</li>
<li>랜덤 숫자 생성을 위한 안전한 지역적 엔트로피 소스</li>
<li>노드의 정확한 지역 시간에 대한 접근 권한</li>
<li>일을 쉬고 재개하는 능력
off-chain worker의 결과물은 일반 거래 검증 대상이 아님. 어떤 정보가 체인에 들어갈지 결정하기 위해 검증 메커니즘을 반드시 구현해야 함</li>
</ul>
<h2 id="노드-템플릿-빌드">노드 템플릿 빌드</h2>
<ol>
<li>노드 템플릿 클론받기<pre><code class="language-bash">git clone https://github.com/substrate-developer-hub/substrate-node-template
git checkout latest</code></pre>
</li>
<li>빌드하기</li>
</ol>
<pre><code class="language-bash">cd substrate-node-template/
cargo build --release</code></pre>
<ol start="3">
<li>좋아하는 에디터로 열기</li>
</ol>
<h2 id="node-authorization-팔레트-추가"><code>node-authorization</code> 팔레트 추가</h2>
<ol>
<li>런타임 dependencies에 팔레트 추가</li>
</ol>
<p><strong>runtime/Cargo.toml</strong></p>
<pre><code class="language-bash">pallet-node-authorization = { default-features = false, git = &quot;https://github.com/paritytech/substrate.git&quot;, branch = &quot;polkadot-v0.9.20&quot; }

[features]
default = [&#39;std&#39;]
std = [
    &#39;pallet-node-authorization/std&#39;,
]</code></pre>
<p>간단한 블록체인에서 거버넌스를 시뮬레이션해야 하므로 <code>sudo</code> 관리자 규칙을 만들어서 <code>EnsureRoot</code>에 대한 팔레트의 인터페이스를 설정할 것</p>
<p><strong>runtime/src/lib.rs</strong></p>
<pre><code class="language-rust">use frame_system::EnsureRoot;

parameter_types! {
    pub const MaxWellKnownNodes: u32 = 8;
    pub const MaxPeerIdLength: u32 = 128;
}

impl pallet_node_authorization::Config for Runtime {
    type Event = Event;
    type MaxWellKnownNodes = MaxWellKnownNodes;
    type MaxPeerIdLength = MaxPeerIdLength;
    type AddOrigin = EnsureRoot&lt;AccountId&gt;;
    type RemoveOrigin = EnsureRoot&lt;AccountId&gt;;
    type SwapOrigin = EnsureRoot&lt;AccountId&gt;;
    type ResetOrigin = EnsureRoot&lt;AccountId&gt;;
    type WeightInfo = ();
}</code></pre>
<p><code>construct_runtime</code> 매크로에 우리의 팔레트를 추가하기</p>
<p><strong>runtime/src/lib.rs</strong></p>
<pre><code class="language-rust">construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        NodeAuthorization: pallet_node_authorization,
    }
);</code></pre>
<h2 id="팔레트에-genesis-storage-추가">팔레트에 genesis storage 추가</h2>
<p><strong>node/Cargo.toml</strong></p>
<pre><code class="language-bash">[dependencies]
bs58 = &quot;0.4.0&quot;</code></pre>
<p><code>PeerId</code>는 bs58 형식으로 인코딩되어 있기 때문에, 해독해서 바이트 얻어내려면 <strong>bs58</strong> 라이브러리 추가</p>
<hr>
<p><strong>node/src/chain_spec.rs</strong></p>
<pre><code class="language-rust">use sp_core::OpaquePeerId;
use node_template_runtime::NodeAuthorizationConfig; </code></pre>
<p>적절한 genesis storage와 필요한 의존성 추가</p>
<pre><code class="language-rust">fn testnet_genesis(
    wasm_binary: &amp;[u8],
    initial_authorities: Vec&lt;(AuraId, GrandpaId)&gt;,
    root_key: AccountId,
    endowed_accounts: Vec&lt;AccountId&gt;,
    _enable_println: bool,
) -&gt; GenesisConfig {
        node_authorization: NodeAuthorizationConfig {
            nodes: vec![
                (
                    OpaquePeerId(bs58::decode(&quot;12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2&quot;).into_vec().unwrap()),
                    endowed_accounts[0].clone()
                ),
                (
                    OpaquePeerId(bs58::decode(&quot;12D3KooWQYV9dGMFoRzNStwpXztXaBUjtPqi6aU76ZgUriHhKust&quot;).into_vec().unwrap()),
                    endowed_accounts[1].clone()
                ),
            ],
        },
}</code></pre>
<p>헬퍼 함수 testnet_genesis()에 genesis 설정 추가</p>
<p><code>NodeAuthorizationConfig</code>는 <code>nodes</code>라는 튜플의 벡터인 property를 가짐
튜플의 첫 번째 요소는 <code>OpaquePeerId</code> 이고 이걸 사람이 읽을 수 있는 형식에서 byte로 바꾸기 위해 <code>bs58::decode</code> 사용
튜플의 두 번째 요소는 <code>AccountId</code> 이고, 이 노드의 주인을 나타내며, 설명을 위해 제공된 <strong>Alice와 Bob</strong> 계정을 사용할 것</p>
<p> <code>12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2</code>가 어디서 나왔는지 궁금할 텐데, 위의 사람이 읽을 수 있는 <code>PeerId</code>를 생성하기 위해 <strong>subkey</strong>(CLI 툴)를 사용할 수 있음</p>
<pre><code class="language-bash">subkey generate-node-key</code></pre>
<p>실행 결과는 다음과 같음:</p>
<pre><code class="language-bash">12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2 // this is PeerId.
c12b6d18942f5ee8528c8e2baf4e147b5c5c18710926ea492d09cbd9f6c9f82a // This is node-key.</code></pre>
<p>이제 허가형 네트워크 시작할 준비 끝</p>
<hr>
<h1 id="허가형-네트워크-시작하기">허가형 네트워크 시작하기</h1>
<p>허가형 체인을 어떻게 시작하고 새 노드를 추가하는지 알아볼 것임</p>
<p>먼저 다 잘 컴파일 되는지 확인:</p>
<pre><code class="language-bash">cargo build --release</code></pre>
<p>4개의 노드를 시작할 것 : 3개의 잘 알려진 노드로 주인에게 허가받고 블록을 검증하는 것과, 선택된 잘 알려진 노드로부터 읽기 전용 권한만을 갖는 1개의 서브노드</p>
<h2 id="노드-키와-peerid-얻기">노드 키와 PeerID 얻기</h2>
<p>Alice의 잘 알려진 노드:</p>
<pre><code class="language-bash"># Node Key
c12b6d18942f5ee8528c8e2baf4e147b5c5c18710926ea492d09cbd9f6c9f82a

# Peer ID, generated from node key
12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2

# BS58 decoded Peer ID in hex:
0x0024080112201ce5f00ef6e89374afb625f1ae4c1546d31234e87e3c3f51a62b91dd6bfa57df</code></pre>
<p>Bob의 잘 알려진 노드</p>
<pre><code class="language-bash"># Node Key
6ce3be907dbcabf20a9a5a60a712b4256a54196000a8ed4050d352bc113f8c58

# Peer ID, generated from node key
12D3KooWQYV9dGMFoRzNStwpXztXaBUjtPqi6aU76ZgUriHhKust

# BS58 decoded Peer ID in hex:
0x002408011220dacde7714d8551f674b8bb4b54239383c76a2b286fa436e93b2b7eb226bf4de7</code></pre>
<p>Charlie의 일반 노드</p>
<pre><code class="language-bash"># Node Key
3a9d5b35b9fb4c42aafadeca046f6bf56107bd2579687f069b42646684b94d9e

# Peer ID, generated from node key
12D3KooWJvyP3VJYymTqG7eH4PM5rN4T2agk5cdNCfNymAqwqcvZ

# BS58 decoded Peer ID in hex:
0x002408011220876a7b4984f98006dc8d666e28b60de307309835d775e7755cc770328cdacf2e</code></pre>
<p>Dave의 (Charlie에 대한) 서브노드</p>
<pre><code class="language-bash"># Node Key
a99331ff4f0e0a0434a6263da0a5823ea3afcfffe590c9f3014e6cf620f2b19a

# Peer ID, generated from node key
12D3KooWPHWFrfaJzxPnqnAYAoRUyAHHKqACmEycGTVmeVhQYuZN

# BS58 decoded Peer ID in hex:
0x002408011220c81bc1d7057a1511eb9496f056f6f53cdfe0e14c8bd5ffca47c70a8d76c1326d</code></pre>
<p>Alice와 Bob의 노드는 미리 genesis storage에 잘 알려진 노드로 설정해둠. 
앞으로 Charlie의 노드를 잘 알려진 노드로 추가하고 Charlie의 노드와 Dave의 노드 사이의 연결을 Dave의 노드를 잘 알려진 노드로 만들지 않으면서 추가할 것</p>
<h2 id="alice와-bob으로-네트워크-시작">Alice와 Bob으로 네트워크 시작</h2>
<p>먼저 Alice 노드 시작:</p>
<pre><code class="language-bash">./target/release/node-template \
--chain=local \
--base-path /tmp/validator1 \
--alice \
--node-key=c12b6d18942f5ee8528c8e2baf4e147b5c5c18710926ea492d09cbd9f6c9f82a \
--port 30333 \
--ws-port 9944</code></pre>
<p>여기서 <code>--node-key</code>를 사용해 네트워크 연결의 보안을 위해 사용할 키를 명시하고 있음
이 키는 위에 있는 사람이 읽을 수 있는 PeerId를 생성하는 데에도 내부적으로 사용됨</p>
<ul>
<li><code>--chain=local</code> : <code>--dev</code>랑은 다른 로컬 테스트넷을 위해 사용</li>
<li><code>--alice</code> : 노드에게 alice라는 이름을 붙여주면서 누가 주인이고 블록을 완성할 것인지에 대한 권한을 줌</li>
<li><code>--port</code> : P2P 연결을 위한 포트 할당</li>
<li><code>--ws-port</code> : Websocket 연결을 위한 수신 포트 할당</li>
</ul>
<p>Bob 노드 시작</p>
<pre><code class="language-bash"># In a new terminal, leave Alice running
./target/release/node-template \
--chain=local \
--base-path /tmp/validator2 \
--bob \
--node-key=6ce3be907dbcabf20a9a5a60a712b4256a54196000a8ed4050d352bc113f8c58 \
--port 30334 \
--ws-port 9945</code></pre>
<p>두 개의 노드 다 실행시키면 터미널 로그로 새로운 블록들이 생성되고 완료되는 걸 볼 수 있음
이제 polkadot.js app으로 잘 알려진 노드들을 확인해볼 것
로컬 노드 중 하나를 <code>127.0.0.1:9944</code> 나 <code>127.0.0.1:9945</code>로 바꾸는 거 잊지 말기</p>
<p><strong>Developer page</strong>로 가서,<strong>Chain State sub-tab</strong>에서 <code>nodeAuthorization</code> 팔레트의 <code>wellKnownNodes</code> storage에 저장된 데이터 확인
Alice와 Bob 노드의 peer id가 0x가 붙은 16진수 형식으로 보일 것임</p>
<p>스토리지에 We can also check the owner of one node by querying the storage <code>owners</code> 스토리지에 노드의 peer id를 입력값으로 쿼리 날려서 주인의 계정 주소를 얻음으로써 노드의 주인을 확인할 수 있음</p>
<h2 id="또다른-잘-알려진-노드-추가">또다른 잘 알려진 노드 추가</h2>
<p>Charlie 노드 시작</p>
<pre><code class="language-bash">./target/release/node-template \
--chain=local \
--base-path /tmp/validator3 \
--name charlie  \
--node-key=3a9d5b35b9fb4c42aafadeca046f6bf56107bd2579687f069b42646684b94d9e \
--port 30335 \
--ws-port=9946 \
--offchain-worker always</code></pre>
<p>시작한 뒤에도 이 노드에 연결된 peer가 없는 것을 볼 수 있을 것임
이건 허가형 네트워크이기 때문에 연결되기 위해선 허가를 받아야 함!
Alice와 Bob은 처음에 <code>chain_spec.rs</code>에서 미리 설정해뒀음</p>
<p>거버넌스를 위해 <code>sudo</code> 팔레트를 사용하고 있어서 노드를 추가하기 위해<code>node-authorization</code> 팔레트가 제공하는<code>add_well_known_node</code> dispatch를 sudo 호출할 수 있다는 걸 기억해라</p>
<p><strong>Developer page</strong>의 <strong>Sudo tab</strong>으로 가서 Charlie를 주인으로 해서 Charlie의 16진수 peer id를 넣고 <code>nodeAuthorization</code> 의 <code>add_well_known_node</code> 을호출
이 호출에 대해 Alice가 유효한 sudo origin이라는 거에 주목해라</p>
<p>이 거래가 블록에 포함된 뒤, Charlie의 노드가 Alice와 Bob의 노드에 연결되고 블록을 맞춰 나가는 걸 불 수 있을 것임
세 노드들이 서로를 찾을 수 있는 이유는 mDNS discovery 메커니즘이 로컬 네트워크에 기본 설정으로 사용가능하기 때문</p>
<p>이제 모든 블록들을 같이 검증하는 3개의 잘 알려진 노드들이 있음</p>
<h2 id="dave를-charlie의-서브노드로-추가">Dave를 Charlie의 서브노드로 추가</h2>
<p>이제 Dave의 노드를 잘 알려진 노드가 아니라 Charlie의 &quot;서브 노드&quot;로 추가할 것
Dave는 네트워크에 접근하기 위해 오직 Charlie에게만 연결할 수 있음
보안 기능 때문임: Charlie는 어떤 연결된 서브노드에 대해 혼자 책임을 짐
제거될 때 David에 대한 접근 권한은 한 곳에서만 하면 됨</p>
<p>Dave의 노드 시작</p>
<pre><code class="language-bash">./target/release/node-template \
--chain=local \
--base-path /tmp/validator4 \
--name dave \
--node-key=a99331ff4f0e0a0434a6263da0a5823ea3afcfffe590c9f3014e6cf620f2b19a \
--port 30336 \
--ws-port 9947 \
--offchain-worker always</code></pre>
<p>시작하면, 가능한 연결이 없을 것임
먼저, Charlie가 Dave의 노드로부터 연결을 허용하기 위해 노드 설정해줘야 함</p>
<p><strong>Developer Extrinsics page</strong>에 가서 Charlie 계정으로 <code>addConnections</code> extrinsic 호출
peerId는 Charlie 노드의 16진수 peer id임.
connections는 Charlie의 노드에 대해 허용된 노드들의 리스트인데, 우리는 Dave 노드만 추가할 것임</p>
<p>이제 Chalie의 노드로부터 연결을 허락받기 위해 Dave가 노드 설정해야 함
그 전에 너무 늦기 않았기를 바라면서 Dave의 노드를 먼저 등록해야 함!</p>
<p>비슷하게, Dave는 Charlie 노드로부터 연결 추가할 수 있음</p>
<p>이제 Dave가 블록들을 따라잡고 있고, 오직 Chalie만을 peer로 갖는 걸 볼 수 있음! Chalie와 바로 연결되지 않는 경우에는 Dave의 노드를 다시 시작하면 됨</p>
<p><code>remove_well_known_node</code>이나 <code>remove_connections</code> 등의 extrinsic으로도 놀아보기</p>
<hr>
<h1 id="다음-단계">다음 단계</h1>
<p><a href="https://docs.substrate.io/tutorials/v3/private-network/">Private Network Tutorial</a> 완료하기
<a href="https://docs.substrate.io/v3/tools/subkey/">Subkey tool</a> 더 알아보기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 만들어보기]]></title>
            <link>https://velog.io/@myungha_cho/Build-your-own-React</link>
            <guid>https://velog.io/@myungha_cho/Build-your-own-React</guid>
            <pubDate>Tue, 24 May 2022 00:22:33 GMT</pubDate>
            <description><![CDATA[<p><a href="https://pomb.us/build-your-own-react/">https://pomb.us/build-your-own-react/</a></p>
<h1 id="0-복습">0. 복습</h1>
<p>React
JSX
DOM element</p>
<pre><code class="language-javascript">const element = &lt;h1 title=&quot;foo&quot;&gt;Hello&lt;/h1&gt;
const container = document.getElementById(&quot;root&quot;)
ReactDOM.render(element, container)</code></pre>
<p>JSX는 유효한 바닐라 자바스크립트 문법이 아니기 때문에 바벨과 같은 빌드 도구를 사용해 JS 문법으로 바꾼다.
태그 안에 있는 코드를 createElement()에 태그 이름과 props, children을 인자로 넘겨줌으로써 바꾼다.
React.createElement()함수는 인자들로부터 객체 하나를 만드는데, 주요하게 type과 props라는 두 가지의 properties를 살펴보겠다</p>
<p>type은 우리가 만들고 싶어 하는 DOM 노드의 타입을 명시한 문자열로, 태그 이름이다.</p>
<p>props는 또 다른 객체로써, JSX attributes로부터 모든 key와  value들을 가지고 있다. 그 중의 특별한 property로 children이 있는데, 문자열 또는 다른 element들의 배열이다.</p>
<p>ReactDOM.render()는 어떻게 바꿀 수 있을까?
먼저 element의 type을 사용해 노드를 하나 만들고 모든 props를 해당 노드에게 할당해주면 된다
그런 다음, children을 위한 노드들을 만드는데, 여기서는 문자열만 가지므로 textNode를 사용해 노드 하나를 만들겠다.
이때, 이 노드가 {nodeValue: &#39;hello&#39;}라는 값을 갖고 있는 것처럼 nodeValue를 설정해준 것에 주목</p>
<p>마지막으로 textNode를 h1에 추가하고 h1을 container에 추가한다</p>
<pre><code class="language-javascript">const element = {
  type: &quot;h1&quot;,
  props: {
    title: &quot;foo&quot;,
    children: &quot;Hello&quot;,
  },
}

const container = document.getElementById(&quot;root&quot;)

const node = document.createElement(element.type)
node[&quot;title&quot;] = element.props.title

const text = document.createTextNode(&quot;&quot;)
text[&quot;nodeValue&quot;] = element.props.children

node.appendChild(text)
container.appendChild(node)</code></pre>
<h1 id="1-createelement">1. createElement()</h1>
<p>앞에서 봤듯이, element는 type과 props를 가지고 있는 object일 뿐이다. 따라서 createElement()함수가 해야 할 일도 object를 만들기만 하면 된다</p>
<pre><code class="language-javascript">function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children,
    },
  }
}
</code></pre>
<p>예를 들어, </p>
<ul>
<li>createElement(&quot;div&quot;)는 {type: &quot;div&quot;, props: {children:<code>[]</code>}}을 리턴하고 - createElement(&quot;div&quot;, null, a)는 {type: &quot;div&quot;, props: {children: <code>[a]</code>}}를 리턴하고, </li>
<li>createElement(&quot;div&quot;, null, a, b)는 {type: &quot;div&quot;, props: {children:<code>[a, b]</code>}}를 리턴한다</li>
</ul>
<pre><code class="language-javascript">function createTextElement(text) {
  return {
    type: &quot;TEXT_ELEMENT&quot;,
    props: {
      nodeValue: text,
      children: [],
    },
  }
}</code></pre>
<p>children 배열은 문자열이나 숫자같은 원시값도 포함해야 한다. <em>그래서 객체가 아닌 것들을 감싸서 특별한 타입: TEXT_ELEMENT를 만들 것이다.</em>??? 먼말인데....</p>
<pre><code class="language-javascript">const Didact = {
  createElement,
}

/** @jsx Didact.createElement */
const element = (
  &lt;div id=&quot;foo&quot;&gt;
    &lt;a&gt;bar&lt;/a&gt;
    &lt;b /&gt;
  &lt;/div&gt;
)</code></pre>
<p>우리 코드에 이름 붙이고 바벨이 리액트 말고 내 꺼 쓰게 하는 주석 추가함</p>
<h1 id="2-render">2. render()</h1>
<p>ReactDOM.render() 대체하는 함수 만들어보기~~
일단 DOM에 element 추가하는 기능만 넣을것임</p>
<pre><code class="language-javascript">function render(element, container) {
  const dom =
    element.type == &quot;TEXT_ELEMENT&quot;
      ? document.createTextNode(&quot;&quot;)
      : document.createElement(element.type)

  const isProperty = key =&gt; key !== &quot;children&quot;
  Object.keys(element.props)
    .filter(isProperty)
    .forEach(name =&gt; {
      dom[name] = element.props[name]
    })

  element.props.children.forEach(child =&gt;
    render(child, dom)
  )

  container.appendChild(dom)
}</code></pre>
<p>재귀적으로 children에 대해 render 호출해주고~
타입이 text_element인 경우에는 textnode 만들어주기
<em>isProperty 부분은 element prop을 그 노드에 할당하는 것??? 뭔말이야...</em></p>
<h1 id="3-concurrnet-모드">3. Concurrnet 모드</h1>
<p>만약에 element tree가 넘 깊다면, 메인 스레드를 계속 블락하고 있을 것임
렌더 함수의 재귀 호출 부분을 최적화해야 함</p>
<p>렌더링 하는 부분 작게 쪼개서 각 부분 완료하면 브라우저가 중간에 끼어들 수 있도록 할 것임</p>
<p>루프를 만들기 위해 requestIdleCallback을 사용할 것임, 메인 스레드가 놀고 있을 때 콜백함수 실행하는 아이임</p>
<pre><code class="language-javascript">
let nextUnitOfWork = null

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork &amp;&amp; !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() &lt; 1
  }
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(nextUnitOfWork) {
  // TODO
}

</code></pre>
<p>requestIdleCallback에 deadline 인자를 줄 수 있음  브라우저가 끼어들 때까지 텀을 얼마나 줄 것인지 설정하는 것임</p>
<p>먼저 초기 unitOfWork를 설정해주고 그 work 수행하면서 다음 unitOfWork를 리턴해주는 performUnitOfWork함수를 작성할거심</p>
<h1 id="4-fibers">4. Fibers</h1>
<p>작업 단위를 정리하기 위해 fiber tree 자료구조가 필요함</p>
<p>각 element당 하나의 fiber를 둘 거고, 각 fiber아 작업 단위가 될 것임</p>
<p>Didact.render(
  <div>
    <h1>
      <p />
      <a />
    </h1>
    <h2 />
  </div>,
  container
)</p>
<p>만약 위와 같은 element tree가 있다면</p>
<p>먼저 root fiber를 만들고 netUnitOfWork를 만듦
나머지 작업은 performUnitOfWork()함수에서 할 거고, 이 함수에서 각 fiber마다</p>
<ul>
<li>element를 DOM에 추가</li>
<li>그 element의 child를 위해 fiber 생성</li>
<li>nextUnitOfWork 선택</li>
</ul>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/95630ecd-f1bc-4077-a223-171490bc53ff/image.png" alt=""></p>
<p>이 자료구조의 목표 중 하나는 다음 작업 단위를 쉽게 찾는 것임
그래서 각 fiber가 첫 번째 자식 -&gt; 다음 형제 element -&gt; 부모 element로 연결되어 있는 것</p>
<p>fiber 작업을 끝내면 다음 작업 단위가 될 child가 있게 됨
위의 예시에서, div 작업 끝내면 다음 작업 단위는 h1 fiber가 됨</p>
<p>만약 해당 fiber의 child가 없다면, sibling으로 가고, 얘도 없으면 uncle로 감: uncle은 slbling의 parent임, uncle도 없으면 sibling의 parent 찾거나 root 찾을 때까지 위로 감</p>
<p>performUnitOfWork() 동작 과정</p>
<p>먼저 render 함수에서 nextUnitOfWork를 fiber tree의 root로 설정</p>
<p>브라우저가 준비되면, workLoop()를 호출하고 root부터 작업 시작</p>
<p>먼저 새 노드를 만들고 DOM에 추가, 부모의 fiber.dom 필드에 새로 만든 DOM 노드 넣기</p>
<p>그리고 각 child에 대해 새 fiber 생성</p>
<p>만든 fiber들을 fiber tree에 추가</p>
<p>child -&gt; sibling -&gt; uncle 순으로 다음 작업 단위 찾기</p>
<h1 id="5-render와-commit-단계">5. Render와 Commit 단계</h1>
<p>불완전하게 렌더링하는 것을 막기 위해 노드 만듥 바로 렌더링하는 것이 아니라 fiber tree의 root를 계속해서 추적하기(wipRoot라고 부를것임)</p>
<p>렌더링이 다 끝나면, 즉 다음 작업 단위가 없으면, 전체 fiber tree를 DOM에 추가</p>
<p>이걸 commitRoot()에서 할 것임. 여기에선 재귀적으로 모든 노드들을 dom에 추가함</p>
<pre><code class="language-javascript">function commitRoot() {
  commitWork(wipRoot.child)
  wipRoot = null
}
​
function commitWork(fiber) {
  if (!fiber) {
    return
  }
  const domParent = fiber.parent.dom
  domParent.appendChild(fiber.dom)
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}</code></pre>
<h1 id="6-수정">6. 수정</h1>
<p>update나 delete는 어떻게 할까?</p>
<p>render()함수에서 받은 element들을 마지막으로 DOM에 commit된 fiber tree와 비교해서 변경사항 찾아냄</p>
<p>그래서 마지막으로 DOM에 추가한 fiber tree를 추적해야 함 이걸 currentRoot라고 할 것임</p>
<p>그리고 모든 fiber에 alternate 필드를 추가할 것임. 이 필드가 이전 커밋 단계에서 DOM에 추가한 예전 fiber를 가리키고 있을 것</p>
<p>performUnitOfWork함수 내부에 recondileChildren()함수 추가. 여기서 예전 fiber를 새 노드로 바꿀 것임</p>
<p>예전 fiber의 children과 수정하고 싶은 노드의 배열을 동시에 순회할 것
만약 배열과 연결 리스트를 순회하는 데 필요한 모든 보일러플레이트들을 무시한다면 제일 중요한 oldFiber와 element를 놓칠 것<em><del>~ ???뭔말이냐</del></em></p>
<p>element는 우리가 DOM에 렌더링하고 싶어하는 노드이고 oldFiber는 우리가 이전에 렌더링한 것임</p>
<p>DOM에 변화가 있는지 확인하기 위해 이 두 개를 비교해야 함</p>
<p>비교하기 위해 type을 사용할 건데</p>
<ul>
<li>만약 old fiber와 새 element가 같은 type이라면, DOM 노드는 그대로 두고 props만 업데이트</li>
<li>만약 type이 다르고 새 element가 있다면, 새 DOM 노드 만들어야 함</li>
<li>만약 type다른데 old fiber만 있다면, 이 노드 지워야 함</li>
</ul>
<p>old fiber와 element가 타입이 같으면, old fiber로부터 props와 DOM 노드를 유지하는 새 fiber를 만듦</p>
<p>그리고 fiber에 새 필드 추가할건데, effectTag임</p>
<p>만약 새로운 DOM 노드를 만드는 경우라면, 새 fiber에 PLACEMENT라는 effectTag를 넣어서 만듦</p>
<p>노드 삭제하는 경우에는 삭제할 노드의 effectTag에 DELETION 넣어줌. 나중에 얘는 렌더링 안 해도 된다는 정보를 유지하기 위해 삭제할 노드들의 배열 있어야 함</p>
<p>그리고 DOM에 변화 반영할 때 삭제할 노드들은 뺌</p>
<p>commitWork()에서 effectTag처리하기
만약 fiber가 PLACEMENT를 갖고 있으면 예전처럼 그냥 DOM에 추가하면 됨
만약 DELETION을 갖고 있으면 child 삭제
만약 UPDATE라면 props를 업데이트해줌</p>
<p>updateDOM()에서 갱신 처리할것임
old fiber에 있던 props를 새로운 fiber와 비교해서 없어진 것은 제거하고 바뀌거나 새로 생긴 것들을 넣어줌</p>
<p>갱신해야 하는 특별한 종류의 props는 event listner인데, prop 이름이 on으로 시작하는 것들은 좀 다르게 처리할것임</p>
<p>만약 event handler가 바뀌었으면 그거를 노드에서 삭제하고 새로운 핸들러를 넣어줌</p>
<h1 id="7-함수형-컴포넌트">7. 함수형 컴포넌트</h1>
<p>다음으로 할 것은 함수형 컴포넌트 지원하는 것</p>
<pre><code class="language-javascript">/** @jsx Didact.createElement */
function App(props) {
  return &lt;h1&gt;Hi {props.name}&lt;/h1&gt;
}
const element = &lt;App name=&quot;foo&quot; /&gt;
const container = document.getElementById(&quot;root&quot;)
Didact.render(element, container)</code></pre>
<p>이 예시를 js로 바꾸면</p>
<pre><code class="language-javascript">function App(props) {
  return Didact.createElement(
    &quot;h1&quot;,
    null,
    &quot;Hi &quot;,
    props.name
  )
}
const element = Didact.createElement(App, {
  name: &quot;foo&quot;,
})</code></pre>
<p>이렇게 될 것</p>
<p>함수형 컴포넌트들은 두 가지 면에서 좀 다른데,</p>
<ul>
<li>함수형 컴포넌트의 fiber는 DOM 노드를 안 가짐</li>
<li>children들이 props에서 오는 것이 아니라 함수 실행해서 얻어짐</li>
</ul>
<p>그래서 먼저 fiber type이 함수인지 확인하고, 이에 따라 update 함수 달라짐</p>
<p>updateHostComponent()에서는 위에서 했던 그대로 할 것임
updateFunctionComponent()에서는 children 얻기 위해 실행할것임</p>
<pre><code class="language-javascript">function updateFunctionComponent(fiber) {
  const children = [fiber.type(fiber.props)]
  reconcileChildren(fiber, children)
}</code></pre>
<p>여기서 fiber.type이 App 함수이고, 이걸 실행하면 <code>&lt;h1&gt;</code>을 얻게 됨. child 얻는 방법만 다르고 나머지는 똑같음</p>
<p>이제 commitChange()를 수정할 것임</p>
<p>이제 DOM 없는 fiber가 있으므로 두 가지 바꿔야 함</p>
<p>먼저, DOM노드의 부모를 찾기 위해 DOM 노드가 있는 fiber를 찾을 때까지 fiber tree의 위로 올라가야 함</p>
<p>그리고 노드를 삭제할 때도 DOM 노드의 child를 찾을 때까지 계속 내려가야 함 
<del>??? 모르겠땅!!!!
~</del></p>
<h1 id="8-hooks">8. hooks</h1>
<p>마지막!
함수형 컴포넌트가 있으므로 state를 추가해볼까</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD & RTL]]></title>
            <link>https://velog.io/@myungha_cho/TDD-RTL</link>
            <guid>https://velog.io/@myungha_cho/TDD-RTL</guid>
            <pubDate>Sat, 21 May 2022 06:52:39 GMT</pubDate>
            <description><![CDATA[<h1 id="tdd">TDD</h1>
<p>Test Driven Development 테스트 주도 개발
:작은 단위의 테스트 케이스를 작성하고 테스트를 통과하는 코드를 추가하면서 기능 구현</p>
<hr>
<h2 id="개발-주기">개발 주기</h2>
<p>write failing test: 실패하는 테스트 코드 작성</p>
<p>make test pass: 테스트 코드를 성공시키기 위한 실제 코드 작성</p>
<p>refactor: 중복 코드 제거, 일반화 등의 리팩토링</p>
<p><img src="/d/1CxZjd2BlG1w?f=0" alt="tdd.PNG"></p>
<ul>
<li>불필요한 설계를 피하고 정확한 요구 사항에 집중 가능</li>
<li>코드를 두 번 짜야 하고 계속해서 테스트를 하면서 고쳐야 하기 때문에
개발 속도가 느려짐</li>
</ul>
<hr>
<ul>
<li>implementation driven test 구현 주도 테스트
앱이 어떻게 구현되는지에 대해 테스트</li>
</ul>
<ul>
<li><strong>behavior driven test 행위 주도 테스트</strong>
사용자 경험 위주로 테스트</li>
</ul>
<pre><code> &lt;h2 class=&quot;title&quot;&gt;제목&lt;/h2&gt;
</code></pre><ul>
<li><p><code>h2</code>, <code>title</code> vs <code>제목</code></p>
<hr>
</li>
</ul>
<h1 id="rtl">RTL</h1>
<p>react testing library</p>
<p>virtual DOM이 아니라 실제 브라우저 DOM을 기준으로 테스트를 작성
-&gt; 어떤 react 컴포넌트를 사용하는지는 의미 없고 사용자 브라우저에서 렌더링하는 콘텐츠에 관심</p>
<hr>
<h1 id="설치">설치</h1>
<p>yarn create react-app your-project-name / yarn add --dev jest (개발 의존성 옵션)</p>
<p>yarn add --dev @testing-library/react @testing-library/jest-dom @testing-library/user-event</p>
<hr>
<h1 id="환경-설정">환경 설정</h1>
<pre><code class="language-javascript">
// testConfig.js



// 각 테스트가 끝나면 다음 테스트를 위해 dom에 렌더링해둔 내용 지우기
 import &quot;@testing-library/react/cleanup-after-each&quot;;



// jest-dom이 제공하는 기능 test runner에게 인식 
import &quot;@testing-library/jest-dom/extend-expect&quot;;

</code></pre>
<hr>
<h1 id="실행">실행</h1>
<p><code>yarn test</code></p>
<hr>
<ol>
<li>DOM에 컴포넌트를 렌더링해주는 <code>render()</code></li>
</ol>
<p>Render</p>
<p><img src="/d/4D8SZL4cwQ0S?f=0" alt="image.png"></p>
<hr>
<ol start="2">
<li>DOM에서 특정 영역을 선택하기 위한 <code>쿼리</code> 함수들</li>
</ol>
<p>getBy : error 
queryBy : null
findBy : error &amp;&amp; await</p>
<hr>
<p>LabelText &gt; PlaceholderText &gt; Text &gt; DisplayValue &gt; AltText &gt; Title &gt; Role &gt; TestId</p>
<p><img src="/d/3t2fYP4ZWOq3?f=0" alt="image.png"></p>
<hr>
<ol start="3">
<li>특정 이벤트를 발생시켜주는 <code>fireEvent/userEvent</code>객체 </li>
</ol>
<p><img src="/d/1SvQhB0YDFSC?f=0" alt="image.png"></p>
<hr>
<ol start="4">
<li>Expect 
<a href="https://jestjs.io/docs/expect">https://jestjs.io/docs/expect</a></li>
</ol>
<p><img src="/d/11FHcF1fzM1l?f=0" alt="image.png"></p>
<p><del>apollo graphql 로 받아오는 데이터를 가짜로 받아오는 방법 <code>MockedProvider</code></del></p>
<hr>
<pre><code class="language-javascript">//LoginPage.test.js

import {fireEvent, render, screen, cleanup} from &#39;@testing-library/react&#39;;
import LoginPage from &#39;./LoginPage&#39;;
import userEvent from &#39;@testing-library/user-event&#39;

// 1. 화면에 잘 그려지는가?
describe(&quot;Rendering test&quot;, () =&gt; {
  afterEach(cleanup)
  it(&#39;rendering test&#39;, () =&gt; {

    render(&lt;LoginPage /&gt;);
    const title = screen.getByText(&#39;Log in Page&#39;);

    // const { getByText } = render(&lt;Example /&gt;);
    // const title = getByText(&#39;Log in Page&#39;);

    expect(title).toBeInTheDocument();

    const id = screen.getByLabelText(&#39;ID&#39;);
    const pw = screen.getByLabelText(&#39;PW&#39;);
    expect(id).toBeInTheDocument();
    expect(pw).toBeInTheDocument();


    const button = screen.getByTestId(&#39;login-button&#39;);
    expect(button).toBeInTheDocument();
  })

});

describe(&#39;testing click event&#39;, () =&gt; {
  afterEach(cleanup)
  it(&quot;login fail event&quot;, () =&gt; {
    render(&lt;LoginPage /&gt;);

    const id = screen.getByLabelText(&#39;ID&#39;);
    const pw = screen.getByLabelText(&#39;PW&#39;);
    const button = screen.getByTestId(&#39;login-button&#39;);

    expect(button).toBeDisabled();

    userEvent.type(id, &#39;admin&#39;)
    userEvent.type(pw, &#39;0000&#39;)

    // fireEvent.change(id, {target: {value: &#39;admin&#39;}})
    // fireEvent.change(pw, {target: {value: &#39;0000&#39;}})

    userEvent.click(button)

    expect(id).toHaveValue(undefined)
    expect(pw).toHaveValue(undefined)

  })

  it(&quot;login success event&quot;, () =&gt; {
    render(&lt;LoginPage /&gt;);

    const id = screen.getByLabelText(&#39;ID&#39;);
    const pw = screen.getByLabelText(&#39;PW&#39;);
    const button = screen.getByTestId(&#39;login-button&#39;);

    expect(button).toBeDisabled();

    userEvent.type(id, &#39;admin&#39;)
    userEvent.type(pw, &#39;1234&#39;)

    expect(button).toBeEnabled();

    userEvent.click(button)
    expect(screen.getByText(&#39;Hello, admin!&#39;)).toBeInTheDocument()
  })
})</code></pre>
<hr>
<h1 id="참고">참고</h1>
<p><a href="https://wooaoe.tistory.com/33">https://wooaoe.tistory.com/33</a>
<a href="https://www.daleseo.com/react-testing-library/">https://www.daleseo.com/react-testing-library/</a>
<a href="https://testing-library.com/docs/dom-testing-library/cheatsheet/">https://testing-library.com/docs/dom-testing-library/cheatsheet/</a> <a href="https://fullmoon1344.tistory.com/162">https://fullmoon1344.tistory.com/162</a>
<a href="https://tecoble.techcourse.co.kr/post/2021-10-22-react-testing-library/">https://tecoble.techcourse.co.kr/post/2021-10-22-react-testing-library/</a>
<a href="https://learn-react-test.vlpt.us/#/">https://learn-react-test.vlpt.us/#/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useRef]]></title>
            <link>https://velog.io/@myungha_cho/useRef</link>
            <guid>https://velog.io/@myungha_cho/useRef</guid>
            <pubDate>Sat, 21 May 2022 06:52:04 GMT</pubDate>
            <description><![CDATA[<h1 id="createref-useref의-차이">createRef, useRef의 차이</h1>
<p>특정 dom을 잡아야 할때 ref를 사용합니다. ref를 쓸 때, createRef와 useRef 두 방식으로 ref를 만들 수 있습니다.</p>
<h1 id="class-컴포넌트">class 컴포넌트</h1>
<p>class형 컴포넌트에서는 아래와 같은 예제로 createRef를 통해 ref를 사용합니다.
class App extends React.Component {
  componentDidMount() {
    this.divRef = React.createRef();
  }
  render() {
    return (
      <div>
        <div id="divR" ref={this.divRef}>
          App, here
        </div>
      </div>
    );
  }
}</p>
<h1 id="함수형-컴포넌트">함수형 컴포넌트</h1>
<p>함수형 컴포넌트에서는 useRef를 사용합니다.
import React, { useRef } from &quot;react&quot;;</p>
<p>const App = () =&gt; {
  const divRef = React.useRef();
  const valueRef = React.useRef(90);
  return (
    <div>
      값 : {valueRef.current}
      <div id="divR" ref={divRef}>
        App, here
      </div>
      &lt;button onClick={() =&gt; (valueRef.current = 88)}&gt; 증가 </button>
    </div>
  );
};</p>
<p>export default App;</p>
<p>위 코드를 실행하면 valueRef값이 88로 바뀌지 않는 다는 것을 알 수 있습니다.
만약 반응형으로 바뀌도록 의도한다면, 일부러 useState를 실행하면 됩니다.
import React, { useState, useRef } from &quot;react&quot;;</p>
<p>const App = () =&gt; {
  const divRef = React.useRef();
  const valueRef = React.useRef(90);
  const [, setState] = useState();
  return (
    <div>
      값 : {valueRef.current}
      <div id="divR" ref={divRef}>
        App, here
      </div>
      &lt;button onClick={() =&gt; ((valueRef.current = 88), setState({}))}&gt;
        증가
      </button>
    </div>
  );
};</p>
<p>export default App;</p>
<p>그러나 createRef를 함수형 컴포넌트에서 사용할 경우 문제점이 있습니다. 예를 들어 함수형 컴포넌트에서 createRef를 사용하면
import React, { useState, createRef } from &quot;react&quot;;</p>
<p>const App = () =&gt; {
  const valueRef = createRef();
  const [, setState] = useState();
  return (
    <div>
      값 : {valueRef.current}
      &lt;button onClick={() =&gt; ((valueRef.current = 88), setState({}))}&gt;
        증가
      </button>
    </div>
  );
};</p>
<p>export default App;</p>
<p>렌더링의 문제는 없지만, 증가 버튼을 클릭해도 이전 과 같이 88로 바뀌지 않는 다는 것을 볼 수 있습니다.
이유는 App 컴포넌트가 setState에 의해 리렌더링 되면 App 컴포넌트를 초기화 하고 다시 만듭니다. 그에 따라 createRef도 다시 실행되는데, 이 때 createRef에 의해 만들어진 값은 무조건 null로 다시 초기화 됩니다.
react의 createRef 코드 (opens new window)를 참고하세요
그래서 함수혐 컴포넌트의 수명 내내 ref의 current 값을 유지하기 위해 useRef hook이 만들어진 것입니다.
useRef로 만들어진 값은 함수가 리렌더링 되어도 ref가 null로 초기화 되지 않습니다. (ref 값이 다시 재생성되지 않음)</p>
<h1 id="결론">결론</h1>
<p>class형 컴포넌트에서 ref를 잡아야하는 경우 React.createRef를 사용한다.
함수형 컴포넌트의 경우 React.createRef와 React.useRef 둘다 사용가능 하지만 React.createRef를 사용할 경우 리렌더링 될때마다 ref 값이 초기화되어 원하는 값을 얻지 못할 것이다. 그러니 useRef를 사용한다.</p>
<p>Ref란?
특정 노드나 컴포넌트에 레퍼런스 값을 만들어주는 것입니다. 때문에 Ref를 통해서 노드나 리액트 요소에 접근하여 값을 얻어낼 수 있습니다.
레퍼런스를 만들기 위해선 리액트 내장함수인 createRef와 리액트 훅인 useRef() 가 있는데요. createRef는 클래스형 컴포넌트 useRef()는 함수형 컴포넌트에서 사용합니다.
먼저 클래스형 컴포넌트의 createRef의 코드를 살펴보겠습니다.
클래스형 컴포넌트 코드 살펴보기
import React, { useState } from &quot;react&quot;;</p>
<p>import React, { Component } from &quot;react&quot;;</p>
<p>class App extends Component {
  state = {
    text: [],
  };</p>
<p>  //레퍼런스를 만들기 위한 리액트 내장함수
  //레퍼런스명 = React.createRef();
  inputRef = React.createRef();
  handleAddText = (e) =&gt; {
    e.preventDefault();</p>
<pre><code>//해당 레퍼런스(여기서는 input)의 현재 밸류값 가져오기
const newText = [this.inputRef.current.value, ...this.state.text];
this.setState({ text: newText });</code></pre><p>  };
  render() {
    return (
      <div>
        <form onSubmit={this.handleAddText}></p>
<pre><code>    //가져오고 싶은 요소에 레퍼런스 속성 및 레퍼런스명 지정
      &lt;input ref={this.inputRef} /&gt;
      &lt;button&gt;버튼&lt;/button&gt;
    &lt;/form&gt;
    &lt;ul&gt;
      {this.state.text.map((item) =&gt; {
        return &lt;li&gt;{item}&lt;/li&gt;;
      })}
    &lt;/ul&gt;
  &lt;/div&gt;
);</code></pre><p>  }
}
export default App;</p>
<h1 id="함수형-컴포넌트-코드-살펴보기">함수형 컴포넌트 코드 살펴보기</h1>
<p>import React, { useState, useRef } from &quot;react&quot;;</p>
<p>function App() {
  const [text, setText] = useState([]);
  const inputRef = useRef();
  const handleAddText = (e) =&gt; {
    e.preventDefault();
    setText([inputRef.current.value, ...text]);
  };</p>
<p>  return (
    <div>
      <form onSubmit={handleAddText}>
        <input ref={inputRef} />
        <button>버튼</button>
      </form>
      <ul>
        {text.map((item) =&gt; {
          return <li>{item}</li>;
        })}
      </ul>
    </div>
  );
}</p>
<p>export default App;</p>
<p>이런식으로 같은 결과물인 클래스형과 함수형 컴포넌트 각자의 코드를 구성해보았습니다.
먼저, createRef라는 리액트 내장 함수를 이용하여 ref의 명을 만들어줍니다.(함수형에선 useRef라는 리액트 훅 이용)
둘째, 접근하고 싶은 돔 요소에 ref속성을 넣어준 후 첫번째에 만들어준 ref명을 넣어줍니다.
셋째, inputRef.current.value 이런식으로 그 ref에 현재 접근한 것의 value값을 가져오는 식을 적어주면 해당 input의 value값을 가져 옵니다.</p>
<h1 id="createref와-useref의-차이점">createRef와 useRef의 차이점</h1>
<p>클래스형 컴포넌트는 인스턴스를 생성 후 render 코드 블록 쪽만 리랜더링후 다시 실행 합니다. 하지만 함수형 컴포넌트는 함수 블록 안에 있는 모든 것을 리랜더링시 마다 다시 실행합니다.
때문에, 함수형 컴포넌트에 createRef를 사용 할 시 ref 값이 초기화 되어서 원하는 값을 얻지 못하기 떄문에, 리액트 훅인 useRef를 사용하는 것입니다.
리액트 훅을 사용하면 useState 값을 리랜더링하면 내부적으로 기억하듯이 useRef도 내부적으로 ref값을 기억하여줍니다.</p>
<p>이렇게 ref는 돔요소나 리액트 컴포넌트등에 접근하여 원하는 값을 가져올 수 있게 해주는 고마운 존재입니다.
감사합니다
useRef
Hooks에서만 실행 가능
ref를 만들고 ref의 초기값을 할당함
빈값으로 시작하지 않으므로 빈값 체크할 필요가 없음
createRef
함수 컴포넌트, 클래스 컴포넌트 둘다 사용
ref만 만듬
값을 가져다가 쓸 때 빈값 체크가 필요함</p>
<ol>
<li>변경 가능한 값
우선 React에서 Ref는 Reference의 줄임말이고 DOM 요소를 참조한다고 해서 붙인 이름 같네요. useRef()는 인자하나를 초기값으로 받고 reference를 return합니다. reference는 current라는 특별한 프로퍼티를 가진 객체입니다.
const reference = useRef(initialValue);</li>
</ol>
<p>reference.current; // current reference value</p>
<p>refer.current는 레퍼런스의 값에 접근하고 refer.current = newValue는 레퍼런스의 값을 업데이트 합니다.
import { useRef } from &#39;react&#39;;</p>
<p>function MyComponent() {
  const reference = useRef(initialValue);</p>
<p>  const someHandler = () =&gt; {
    // Access reference value:
    const value = reference.current;</p>
<pre><code>// Update reference value:
reference.current = newValue;</code></pre><p>  };</p>
<p>  // ...
}</p>
<p>여기서 두가지 규칙을 기억해야 합니다.
레퍼런스의 값은 유지 됩니다. 컴포넌트 리렌더링이 된다 하더라도.
레퍼런스의 값의 업데이트는 컴포넌트 리렌더링을 트리거하지 않습니다.
간단한 예제
import { useRef } from &#39;react&#39;;</p>
<p>function LogButtonClicks() {
  const countRef = useRef(0);</p>
<p>  const handle = () =&gt; {
    countRef.current++;
    console.log(<code>Clicked ${countRef.current} times</code>);
  };</p>
<p>  console.log(&#39;I rendered!&#39;);</p>
<p>  return <button onClick={handle}>Click me</button>;
}</p>
<p>버튼을 클릭하더라도 I rendered 로그는 나타나지 않고(규칙2), Clicked 1 times에서 값은 계속 증가합니다.(규칙1)
Ref와 State의 차이는?
Ref와 state의 차이를 위해 코드를 써 봅니다.
import { useState } from &#39;react&#39;;</p>
<p>function LogButtonClicks() {
  const [count, setCount] = useState(0);</p>
<p>  const handle = () =&gt; {
    const updatedCount = count + 1;
    console.log(<code>Clicked ${updatedCount} times</code>);
    setCount(updatedCount);
  };</p>
<p>  console.log(&#39;I rendered!&#39;);</p>
<p>  return <button onClick={handle}>Click me</button>;
}</p>
<p>state의 update가 있기에 클릭을 할 때마다 I rendered! 로그를 보실 수 있습니다. 해당 코드를 바탕으로 알 수 있는 state와 ref의 두가지 주요한 차이점을 알 수 있습니다.
state를 업데이트하는 것은 컴포넌틑 리렌더링을 트리거하지만 ref는 아닙니다.
상태 업데이트는 비동기적(상태 변수는 렌더링 한 후 업데이트 됨)이지만 참조는 동기적(업데이트된 값은 즉시 사용될 수 있음)입니다.
2. DOM 요소 접근
아래 3단계 과정은 useRef를 이용해 DOM 요소에 접근하는 예제입니다.
useRef를 이용해 elementRef 선언.
elementRef를 div에 할당
컴포넌트가 마운트 되고나서 엘리먼트의 ref속성을 divElement에 할당
import { useRef, useEffect } from &#39;react&#39;;</p>
<p>function AccessingElement() {
  const elementRef = useRef();</p>
<p>   useEffect(() =&gt; {
    const divElement = elementRef.current;
  }, []);</p>
<p>  return (
    <div ref={elementRef}>
      I&#39;m an element
    </div>
  );
}</p>
<p>2-1 사용 사례 : 인풋 포커싱
예를 들어, 컴포넌트가 마운트되고 인풋 필드에대한 포커싱이 필요하다면 DOM 요소들에 대한 접근이 필요합니다.
사용법은 간단합니다.
import { useRef, useEffect } from &#39;react&#39;;</p>
<p>function InputFocus() {
  const inputRef = useRef();</p>
<p>  useEffect(() =&gt; {
    inputRef.current.focus();
  }, []);</p>
<p>  return (
    <input 
      ref={inputRef} 
      type="text" 
    />
  );
}</p>
<p>inputRef를 선언하고, input 태그에 할당한다.
그리고 React는 마운트 후 inputRef.current를 입력 요소로 설정합니다.
그럼 이제 실질적으로 inputRef.current.focus()를 이용해 포커싱을 줄 수 있습니다.
&#39;초기 렌더링 상태에서는 Ref는 null 이다&#39;
import { useRef, useEffect } from &#39;react&#39;;</p>
<p>function InputFocus() {
  const inputRef = useRef();</p>
<p>  useEffect(() =&gt; {
    // Logs <code>HTMLInputElement</code> 
    console.log(inputRef.current);</p>
<pre><code>inputRef.current.focus();</code></pre><p>  }, []);</p>
<p>  // Logs <code>undefined</code> during initial rendering
  console.log(inputRef.current);</p>
<p>  return <input ref={inputRef} type="text" />;
}</p>
<p>초기 렌더링 중에는 아직까지 컴포넌트는 생성된 구조가 없다. 그렇기 때문에 초기 렌더링 중에 inputRef.current가 undefined로 나타납니다.
useEffect 단계에서는 DOM이 렌더되고나서 콜백을 호출한다. useEffect에서는 DOM이 보장되기 때문에 올바르게 inputRef.current에 액세스할 수 있다.
3. 참조 제한 업데이트
함수 컴포넌트의 함수 스코프는 output을 계산하거나 hook을 호출해야한다. 그래서 컴포넌트 함수의 즉각적인 범위 내에서 참조 업데이트(상태 업데이트 포함)를 수행해서는 안된다. 참조는 useEffect()의 콜백 내부 또는 핸들러(이벤트, 타이머)내부에서 업데이트 되어야한다.
import { useRef, useEffect } from &#39;react&#39;;</p>
<p>function MyComponent({ prop }) {
  const myRef = useRef(0);</p>
<p>  useEffect(() =&gt; {
    myRef.current++; // Good!</p>
<pre><code>setTimeout(() =&gt; {
  myRef.current++; // Good!
}, 1000);</code></pre><p>  }, []);</p>
<p>  const handler = () =&gt; {
    myRef.current++; // Good!
  };</p>
<p>  myRef.current++; // Bad!</p>
<p>  if (prop) {
    myRef.current++; // Bad!
  }</p>
<p>  return <button onClick={handler}>My button</button>;
}</p>
<p>4.정리
useRef()는 참조를 생성한다. const reference = useRef(initialValue)는 초기값과 함께 특별한 객체를 생성한다. 참조 객체는 current프로퍼티를 가지고 있다. refer.current를 읽거나 업데이트할 수 있다.
참조 값은 컴포넌트 리렌더링과 별개로 영구적이다.
state의 업데이트와 다르게 참조 업데이트는 컴포넌트 리렌더링을 트리거하지 않는다.
참조는 DOM에 접근할 수 있다. <div ref={reference}>Element</div>처럼 할당하고 reference.current를 이용해 접근할 수 있다.
[React] useRef, ref 란 무엇일까?
React에서 제공하는 hook, useRef에 대해서 알아보자. (전체적인 코드는 작성하지 않음, 해당 git 링크에서 clone 받아서 확인 가능)
useRef
useRef는 무엇이고 왜 사용할까? 자세히 들어가면 한도 끝도 없고, 그 정도까지 알 필요도 없다(심화는 천천히). 간단하게만 알아보자. ref는 크게 2가지로 사용이 된다.</p>
<ol>
<li><p>DOM에 접근한다.</p>
</li>
<li><p>렌더링을 일으키지 않고 값을 변경시킨다.
우선 위 2가지를 알아보자.</p>
</li>
<li><p>DOM에 접근한다.
JavaScript에서 DOM에 접근할 때 사용하는 여러가지 방법이 있다. DOM에 접근하는 대표적인 방법이 Document.querySelector(), Document.getElementById()가 있다. 이러한 것들을 대신 ref을 이용하여 DOM에 접근할 수 있다. 간단하게 예시를 들어보자. 아래 코드는 버튼을 클릭하였을 때, input 창에 focus를 하는 코드이다.
return (</p>
 <div>
   <input ref={textRef} />
   <button onClick={() => textRef.current.focus()}>
     input에 포커스하기
   </button>
 </div>
);
결과를 보면 알 수 있듯이 input 창에 포커싱이 된다.
</li>
<li><p>렌더링을 일으키지 않고 값을 변경시킨다.
렌더링을 일으키지 않고 값을 변화시킬 수 있다? 이게 무슨 말일까? 이전 시간에 알아본 state는 값이 변할 때마다 setState를 해주어서 렌더링이 되는 것을 확인하였다. 그러나 ref는 state와 다르게 값이 변하게 되어도 렌더링이 일어나지 않는다. 간단하게 구현하여 확인해보자.
const Ref = () =&gt; {
const textRef = useRef(null);</p>
<p>console.log(&quot;rendering&quot;);</p>
<p>return (</p>
 <div>
   <input ref={textRef} />
   <button onClick={() => console.log("input 값 : ", textRef.current.value)}>
     값 확인하기
   </button>
 </div>
);
};


</li>
</ol>
<p>위 gif를 보면 알 수 있듯이 input에 값을 계속 변화를 주지만 렌더링은 일어나지 않는다. 그럼 저 값을 가지고 오고 싶으면 어떻게 해야할까? 바로 ref.current를 이용하여 DOM에 접근하여 값을 가지고 오는 것이다. 사실 1번 DOM에 접근할 수 있다와 연관이 있는 내용이다.
Controlled(제어) 컴포넌트, Uncontrolled(비제어) 컴포넌트
React에는 제어 컴포넌트와 비제어 컴포넌트가 있다. 간단하게 2개의 차이만 말하자면 제어 컴포넌트는 state를 사용한 것이고, 비제어 컴포넌트는 ref를 사용한 것이다. 2개의 차이점은 분명히 존재한다. React에서는 제어 컴포넌트를 지향하고 있다. 그러나 state를 사용하는 경우 복잡한 form을 구현할 때, 렌더링이 많이 일어나 성능상 느려지는 경우가 발생할 수 있다. 따라서 제어, 비제어 컴포넌트를 상황에 맞게 잘 사용해야 한다. 해당 부분은 아래 걸어둔 React 공식 홈페이지 링크를 읽어보도록 하자.</p>
<ul>
<li>제어 컴포넌트</li>
<li>비제어 컴포넌트</li>
</ul>
<p>React 라이브러리는 ref를 왜 만들었을까?
이 글은 해당 질문을 중심으로 ref와 연관된 아래 개념들을 정리하는 글이다.
01. DOM API(querySelector, getElementById)
02. Mutable(변하기 쉬움)과 Immutable(불변성)
03. Declarative(선언형)과 Imperative(명령형)
React에서 Ref
Vanilla Javascript에서는 DOM 객체에 접근하기 위해 querySelector나 getElementById API를 사용해야 한다.
반면, React는 아래와 같은 이유로 DOM API를 이용한 컴포넌트 제어 방식을 권장하지 않는다.
01. React를 이용한 웹 소프트웨어에서 데이터는 State로 조작되기에 DOM API와 혼합해서 데이터 및 조작을 할 경우 디버깅이 어려워지고, 유지보수가 어려운 코드가 된다.
02. map 메소드를 이용해 렌더링 되는 리스트 형태의 Element는 같은 ID를 가지기에 특정 DOM 객체를 querySelector, getElementById로 판별하기 어렵다.
따라서, React는 DOM API를 이용한 컴포넌트 제어 대신 ref라는 기능을 제공한다. ref를 통해 DOM 객체에 대한 직접적인 참조 주소를 반환 받아 HTML DOM 메소드 및 속성을 이용할 수 있다.
[그림 01] HTML DOM 속성에 접근하는 ref의 예시. clientHeight는 HTML DOM의 주요 속성이다.
React에서는 ref기능을 이용하기 위해 React.createRef() 와 useRef() 를 제공한다.
이 둘을 간략하게 구분하면 Class 컴포넌트에서는 createRef()를 사용하고 Functional 컴포넌트에서는 useRef()를 사용한다.
이 둘에 대한 차이는 잠시 미뤄두고 각 기능은 다음과 같이 사용한다.
[그림 02] Class 컴포넌트에서 React.createRef()를 써서 ref를 만드는 예시. current라는 속성으로 값을 조회함
이때, 중요한 것은 createRef()와 useRef()자체는 Mutable Object를 만드는 기능이라는 점이다.
ref는 componentDidMount나 useEffect가 실행되기 전에 할당됩니다.
컴포넌트가 마운트 될 때 할당되며, 언마운트 될 때 Null을 할당합니다.
즉, React Component의 인스턴스나, React DOM의 ref 속성에 해당 ref 참조(this.ref)를 선언하지 않으면 그저 Mutable Object일 뿐이다.
이를 확인하기 위해 createRef의 실제 구현체를 확인해보자.
[그림 03] React.createRef()의 실제 구현체. Object가 Reference 타입의 자료형임을 이용했다.
실제로 createRef()의 구현체를 확인하면 Javascript의 Object가 Reference(참조) 타입의 변수라는 것을 이용한 간단한 코드로 구현된 것을 확인할 수 있다.
즉, createRef()나 useRef()로 생성한 참조주소를 JSX의 ref 속성으로 선언해주어야 해당 컴포넌트 혹은 DOM Element에 대한 참조를 얻을 수 있다.
createRef와 useRef
앞서 Class 컴포넌트에서는 createRef()를 Functional 컴포넌트에서는 useRef()를 쓴다고 언급했다. 이 둘의 차이는 무엇일까?
이 둘의 차이를 이해하기 위해서는 Class 컴포넌트와 Functional 컴포넌트의 리렌더링 차이를 알아야 한다.
글의 진행을 위해 간단하게 설명하면 Class 컴포넌트는 인스턴스를 생성해 render 메소드를 호출하는 방식으로 리렌더링을 한다면, Functional 컴포넌트는 해당 함수를 리렌더링 마다 다시 실행한다.
위와 같은 차이는 Functional 컴포넌트에서 createRef() 메소드를 이용하기 어렵게 한다. 아래 예시 코드를 보자.
[그림 04] Functional 컴포넌트에 createRef를 쓸 경우 Component가 렌더링 될 때마다 ref가 생성된다.
createRef()는 앞서 확인한 것과 같이 그저 Mutable Object를 생성하는 기능이기에 Functional 컴포넌트에서 리렌더링 될 때마다 새로운 Mutable Object를 생성하게 된다.
즉, ref를 이용해 Mutable Object를 만들어도 state가 변경되면 createRef()가 다시 호출되며 새로운 ref를 반환한다.
[그림 05] useRef의 구현체로 별도의 Dispatcher에서 해당 컴포넌트의 Hook을 관리함
이와 같은 문제를 해결하기 위한 방법으로 React에서는 useRef()라는 기능을 제공한다.
useRef는 Hook의 Dispatcher를 통해서 동작하는데 내부적으로 mountRef와 updateRef 를 이용해 동작하는 것을 확인할 수 있다.
[그림 06] useRef의 내부 동작을 담당하는 mountRef와 updateRef의 구현체
mountRef와 updateRef 구현체를 통해 두 가지 사실을 알 수 있다.
첫번째, mountRef는 현재의 Hook에 대한 정보를 보관하는 Object의 memoizedState라는 속성에 ref를 저장하는 방식으로 구현됐다.
두번째, updateRef는 컴포넌트가 업데이트 되어 리 렌더링이 발생할 때 memoizedState를 반환하는 방식으로 구현됐다.
useImperativeHandle와 ForwardRef
React는 기본적으로 Declarative(선언형) 패러다임을 따른다.
즉, 뷰의 동작에 대해 하나 씩 명시하는 것이 아닌 state나 props의 변경에 따라 변하는 뷰를 선언하는 방식으로 개발된다.
하지만, Ref 기능의 경우 기능을 조작하기 위해 Imperative(명령형) 패러다임을 따른다. 아래의 코드를 보자.
[그림 07] Imperative(명령형) 패러다임을 따르는 ref 기능
특정 동작에 대한 메소드를 구현하고 Imperative(명령형) 패러다임으로 개발하는 것을 확인할 수 있다.
ref를 통한 이런 기능은 Class 컴포넌트에서는 기본적으로 동작하지만 내부 메소드를 정의할 수 없는 Functional 컴포넌트에서는 별도의 기능이 필요하다.
React에서는 Functional 컴포넌트에서 ref를 통한 접근을 위해forwardRef()와 useImperativeHandle() 를 제공한다.
[그림 08] useImperativeHandle과 forwardRef를 이용한 명령형 프로그래밍
forwardRef() 는 Functional 컴포넌트에 ref 속성을 이용할 수 있도록 하는 기능으로 해당 함수로 Wrapping 된 컴포넌트는 위와 같이 ref를 매개변수로 받을 수 있게 된다.
React 내부 WorkTag를 확인하면 forwardRef()를 통해 생성된 컴포넌트는 일반 FunctionComponent 태그와 차별을 두는 ForwardRef 태그로 명시된다.
[그림 09] React 내부적으로 동작을 구분하기 위해 사용하는 work tag. ForwardRef를 통한 컴포넌트를 분리함
useImperativeHandle()는 이름 처럼 Imperative(명령형) 동작에 대한 Handler를 제공한다.
즉, ref.current의 속성으로 접근할 수 있는 메소드에 대해 구현하는 부분이다. 이를 통해 부모 컴포넌트에서 자식 컴포넌트의 state를 조작할 수도 있고, 특정 애니메이션을 트리거 할 수도 있다.
결론
이 글을 통해 React에서 제공하는 ref 기능에 대해 다음과 같은 사실을 배울 수 있었다.
01. Ref는 DOM Element 객체나 React Component 인스턴스를 직접 조회할 수 있다.
02. useRef()나 createRef()는 current 속성을 가지는 Mutable Object를 반환한다.
03. Ref기능은 React의 기본 패러다임인 Declarative(선언형)이 아닌 Imperative(명령형) 패러다임의 기능이다.
이런 ref의 특성을 이용하면 특정 DOM의 API를 직접적으로 사용할 수 있고, 렌더링 없이 변경이 필요한 데이터를 관리할 수 있다.
또한, 자식의 state에 부모가 접근할 때나 state로 제어하지 않는 비제어 컴포넌트 를 사용할 때도 ref를 이용할 수 있다.
React를 통해 개발을 진행하다 보면, state와 props 구조의 규칙을 지키면서 개발하기 어려운 요구 사항이 생길 수 있다.
이 글을 통해 그런 요구사항에 대해 &quot;ref 기능을 이용한 색 다른 해결 방법이 있지 않을까?” 고민해 볼 수 있는 관점을 얻은 것 같다.
맺음말
처음 React Native로 앱 개발을 했을 때, 웹을 개발할 때 보다 ref를 많이 써서 당황한 적이 있다.
처음 React를 배울 때 ref는 비중이 높게 다뤄지지 않았기 때문에 자연스레 이 방법이 잘못된 것은 아닌지, 내가 잘 쓰고 있는지에 대한 고민을 많이 했다.
React Native 라이브러리에는 네이티브 기능을 이용하기 위해 ref를 많이 쓴다. 예를 들면 FlatList 의 특정 항목으로 스크롤 이동을 할 때 사용한다.
ref를 통한 조작이 익숙해질 때, 문득 “특정 UI 의 기능 명세가 확실하다면, useImperativeHandler 와 ForwardRef를 통해 UI의 동작 기능을 ref의 메소드로 관리하는 것이 컴포넌트의 모듈화에 더 유용하지 않을까?” 라는 생각이 들었다.
물론 이 방식이 React의 Declarative(선언형) 패러다임에 반하는 것이지만 패러다임에 고정된 생각보다는 사용성의 관점으로 고민이 되는 부분이었다.
혹시나 이 부분에 대한 이유나 혹은 이렇게 개발해 본 경험이 있는 React 개발자가 있다면, 댓글로 남겨주면 많은 사람들에게 도움이 될 것 같다.
useRef
useRef 는 단순 DOM 엘리먼트를 지정하는데만 사용되지 않는다.
useRef 를 통해 클래스의 멤버 변수와 비슷한 역할을 하게 만들 수 있다.
state 와 달리 값의 변화에 의한 리렌더링이 발생하지 않는다.
current 라는 속성을 통해 어느 값이든 보유할 수 있는 일종의 컨테이너 역할을 할 수 있다.
함수형 컴포넌트는 인스턴스로 생성되지 않는다. 즉, 컴포넌트의 고유한 값을 저장할 방법이 없어 useRef를 통해 일종의 멤버변수를 구현하는 것.
다음의 코드는 ref 를 사용하여 state의 이전 값 을 보관하는 prevMathScore 라는 변수를 만든 예제이다.
import React, { useState, useEffect, useRef } from &quot;react&quot;;</p>
<p>function Score() {
  const [mathScore, setMathScore] = useState(40);
  const prevMathScore = useRef(40);</p>
<p>  useEffect(() =&gt; {
    prevMathScore.current = mathScore;
  }, [mathScore]);</p>
<p>  const result = prevMathScore.current &lt; mathScore ? &quot;올랐네요&quot; : &quot;개판이네요&quot;;</p>
<p>  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; setMathScore(mathScore + 20)}&gt;시험보기</button>
      <span>{<code>성적이 ${result}!</code>}</span>
    &lt;/&gt;
  );
}</p>
<p>요약해보자면,
1.
state는 컴포넌트의 생명 주기와 밀접한 연관이 있는 요소이므로 렌더링과 관련 없는 값을 저장하기에는 적합치 않다.
2.
ref는
setTimeout, setInterval이 반환하는 ID 값
state의 이전 값
이 외 렌더링과 무관한 가변값
위와 같이 렌더링과 관련이 없는 값을 저장하기에 적합하다.</p>
<hr>
<p>출처:<a href="https://nukw0n-dev.tistory.com/14%5B%EC%B0%90%EC%9D%B4%EC%9D%98">https://nukw0n-dev.tistory.com/14[찐이의</a> 개발 연결구과]</p>
<p><a href="https://kyounghwan01.github.io/blog/React/useRef-createRef/#%E1%84%92%E1%85%A1%E1%86%B7%E1%84%89%E1%85%AE%E1%84%92%E1%85%A7%E1%86%BC-%E1%84%8F%E1%85%A5%E1%86%B7%E1%84%91%E1%85%A9%E1%84%82%E1%85%A5%E1%86%AB%E1%84%90%E1%85%B3">https://kyounghwan01.github.io/blog/React/useRef-createRef/#%E1%84%92%E1%85%A1%E1%86%B7%E1%84%89%E1%85%AE%E1%84%92%E1%85%A7%E1%86%BC-%E1%84%8F%E1%85%A5%E1%86%B7%E1%84%91%E1%85%A9%E1%84%82%E1%85%A5%E1%86%AB%E1%84%90%E1%85%B3</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[빌드]]></title>
            <link>https://velog.io/@myungha_cho/%EB%B9%8C%EB%93%9C</link>
            <guid>https://velog.io/@myungha_cho/%EB%B9%8C%EB%93%9C</guid>
            <pubDate>Sat, 21 May 2022 06:50:03 GMT</pubDate>
            <description><![CDATA[<h1 id="개발빌드배포">개발<del>”빌드”</del>배포</h1>
<p>개발자 모드로 프로젝트를 만들면 용량이 무겁다.</p>
<ul>
<li>성능 최적화</li>
<li>코드 난독화</li>
<li>특수 문법/라이브러리 지원(JSX, Typescript → VanilaJS)</li>
<li>호환성, 의존성 위해 브라우저마다 라이브러리/Js문법 downgrade(babel)</li>
</ul>
<p>ex) Redactor Build된 파일</p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/a5e8ce92-4dfc-4a3a-b9c7-21dd3667957b/image.png" alt=""></p>
<hr>
<h1 id="빌드-툴">빌드 툴</h1>
<p>‘주요(main)’ 모듈(‘진입점’ 역할을 하는 모듈)을 선택
‘주요’ 모듈에 의존하고 있는 모듈 분석을 시작으로 모듈 간의 의존 관계를 파악
모듈 전체를 한데 모아 하나의 큰 파일을 생성
이런 과정 중에 변형이나 최적화도 함께 수행</p>
<ul>
<li><p>도달 가능하지 않은 코드는 삭제됩니다.</p>
</li>
<li><p>내보내진 모듈 중 쓰임처가 없는 모듈을 삭제합니다(tree-shaking).</p>
</li>
<li><p>console, debugger 같은 개발 관련 코드를 삭제합니다.</p>
</li>
<li><p>최신 자바스크립트 문법이 사용된 경우 바벨(Babel)을 사용해 동일한 기능을 하는 낮은 버전의 스크립트로 변환합니다.!</p>
</li>
<li><p>공백 제거, 변수 이름 줄이기 등으로 산출물의 크기를 줄입니다.</p>
</li>
</ul>
<hr>
<h2 id="webpack">Webpack</h2>
<p>소스 코드와 node_modules 폴더의 전체를 묶고, 빌드한 다음 브라우저에 제공
webpack-dev-server: hot-reloading
웹팩을 사용할 경우 코드를 조금만 수정하더라도 npm run dev 또는 npx webpack을 통해 다시 리로딩을 해야 하는 것이 불편함. 조금씩 수정한건 바로 반영, 데이터 보존</p>
<hr>
<h2 id="esbuild">ESbuild</h2>
<p>노드 기반 번들러보다 10~100배 빠름
create-react-app이 제공하는 옵션 제공 안 함
hot-reload제공 안 함</p>
<hr>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/eacccd72-3eba-4b8e-8af3-6cd02a3ee8cd/image.png" alt=""></p>
<h2 id="snowpack">SnowPack</h2>
<p>Snowpack 개발 서버는 node_modules의 각 의존성마다 하나의 자바스크립트 파일로 묶고 해당 파일을 기본 자바스크립트 모듈로 변환한 다음 브라우저에 제공한다. 브라우저는 이 스크립트를 캐시하고 스크립트가 변경된 경우에만 다시 요청할 수 있다.</p>
<h2 id="vite">Vite</h2>
<p>Vite는 프로젝트의 모든 의존성을 단일 기본 자바스크립트 모듈로 사전 번들링을 진행한다. 그 후에 많은 캐시 된 HTTP 헤더와 함께 제공된다. 즉, 첫 페이지 로드 후 가져온 의존성을 컴파일, 제공 또는 요청하는 데 시간이 낭비되지 않는다. 또한 Vite는 명확한 에러 메시지들을 제공하고 문제를 해결하기 위해 정확한 코드 블록과 줄 번호를 보여준다.</p>
<h2 id="wmr">WMR</h2>
<p>Preact는 작고 가벼운 프로젝트를 수행하려는 경우에 좋다. hot-module-replacing 개발 서버와 최적화된 프로덕션 빌드를 포함, 별도의 구성이 필요 없으며 다운로드하는 데 몇 초밖에 걸리지 않는다.</p>
<hr>
<p>참고문헌</p>
<ul>
<li><a href="https://amkorousagi-money.tistory.com/entry/react-build">https://amkorousagi-money.tistory.com/entry/react-build</a></li>
<li><a href="https://ui.toast.com/weekly-pick/ko_20220127">https://ui.toast.com/weekly-pick/ko_20220127</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[
Typescript Clean Code]]></title>
            <link>https://velog.io/@myungha_cho/Typescript-Clean-Code</link>
            <guid>https://velog.io/@myungha_cho/Typescript-Clean-Code</guid>
            <pubDate>Sat, 21 May 2022 06:48:09 GMT</pubDate>
            <description><![CDATA[<p>Variables</p>
<ol>
<li>변수 이름은 구체적으로
Bad<pre><code>// 변수
const AHCageId = 1
</code></pre></li>
</ol>
<p>// 함수
const isBiggerThan = (a: number, b: number) =&gt; {
    return a &gt; b
}</p>
<pre><code>Good</code></pre><p>// 변수
const AdmissionHotelCageId = 1</p>
<p>// 함수
const isBiggerThan = (target: number, compareTarget: number) =&gt; {
    return a &gt; b
}</p>
<pre><code>2. 유형이 같다면 동일 명칭으로
Bad</code></pre><p>const getInitAData = () =&gt; {}
const generateInitBData = () =&gt; {}
const makeInitCData = () =&gt; {}</p>
<pre><code>Good</code></pre><p>const getInitAData = () =&gt; {}
const getInitBData = () =&gt; {}
const getInitCData = () =&gt; {}</p>
<p>// or</p>
<p>const generateInitAData = () =&gt; {}
const generateInitBData = () =&gt; {}
const generateInitCData = () =&gt; {}</p>
<p>// or</p>
<p>const makeInitAData = () =&gt; {}
const makeInitBData = () =&gt; {}
const makeInitCData = () =&gt; {}</p>
<pre><code>3. 상수에도 의미 부여하기</code></pre><p>Bad
setTimeout(someFunc, 86400000)</p>
<p>// and</p>
<p>.my_className {
  position: relative;
  left: 270px;
}</p>
<pre><code>Good</code></pre><p>const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000
setTimeout(someFunc, MILLISECONDS_IN_A_DAY)</p>
<p>// and</p>
<p>.my_className {
  position: relative;
  left: calc(300px - 20px - 10px);
}
// padding, margin 등 계산한 값</p>
<pre><code>4. Index 접근 하지 말기</code></pre><p>Bad
const userNameMap = new Map&lt;number, string&gt;()</p>
<p>for (const data of userNameMap){
  const id = data[0]
  const name = data[1]
}
Good
const userNameMap = new Map&lt;number, string&gt;()</p>
<p>for (const [id, name] of userNameMap){
  // ...
}</p>
<pre><code>5. destructuring 쓰기
Bad</code></pre><p>type Props = {
  onClose?: () =&gt; void
  pet?: GQLGetPet
}
export const PacsAddModal = (props: Props) =&gt; { ... }</p>
<pre><code>Good</code></pre><p>type Props = {
  onClose?: () =&gt; void
  pet?: GQLGetPet
}
export const PacsAddModal = ({ onClose, pet }: Props) =&gt; { ... }</p>
<pre><code>6. 의미 중복 지양하기
Bad</code></pre><p>type User = {
  userId: number
  userName: string
}</p>
<pre><code>Good</code></pre><p>type User = {
  id: number
  name: string
}</p>
<pre><code>7. default value 쓰기(short circuiting)
Bad</code></pre><p>const showPrettyName = (name?: string) =&gt; {
  return <div>{name ?? &#39;2름&#39;}</div>
}</p>
<pre><code>Good</code></pre><p>const showPrettyName =(name?: string = &#39;2름&#39;) =&gt; {
  return <div>{name}</div>
}</p>
<pre><code>8. undefined와 0 확실히 구분하기 (보통 number type)
Bad</code></pre><p>export function isSafeNumberWithConstraints(
  value: number,
  options?: {
    minValue?: number
    maxValue?: number
  },
) {
  // limit max
  if (options?.maxValue &amp;&amp; value &gt; options.maxValue) return false</p>
<p>  // limit min
  if (options?.minValue &amp;&amp; value &lt; options.minValue) return false</p>
<p>  return true
}</p>
<pre><code>Good</code></pre><p>export function isSafeNumberWithConstraints(
  value: number,
  options?: {
    minValue?: number
    maxValue?: number
  },
) {
  // limit max
  if (options?.maxValue !== undefined &amp;&amp; value &gt; options.maxValue) return false</p>
<p>  // limit min
  if (options?.minValue !== undefined &amp;&amp; value &lt; options.minValue) return false</p>
<p>  return true
}</p>
<pre><code>9. enum 및 switch 사용하기
Bad</code></pre><p>type MyVar = {
  id: number
  status: number
}</p>
<p>const myVar = {
  id: 1
  status: 2
}</p>
<p>// ...</p>
<p>if (myVar.status === 1){
  // ...
} else if(myVar.status === 2){
  // ...
}</p>
<pre><code>Good</code></pre><p>enum EMyVarStatus {
  A = 1,
  B = 2,
  C = 3
}</p>
<p>type MyVar = {
  id: number
  status: EMyVarStatus
}</p>
<p>const myVar = {
  id: 1
  status: EMyVarStatus.B
}</p>
<p>// ...</p>
<p>// 1
const a = new Map&lt;MyEnum, string&gt;([
    [MyEnum.A, () =&gt; {}],
    [MyEnum.B, () =&gt; {}]
])</p>
<p>// 2
switch (myVar.status){
  case EMyVarStatus.A:
    // ...
  case EMyVarStatus.B:
    // ...
  default:
    // ...
}</p>
<pre><code>Function
함수
1. 매개변수 3개 이상은 object로 (+destructuring)
Bad</code></pre><p>const sumOfLengthOfRectangle 
  = (width: number, height: number, depth: number) =&gt; {
    return width + height + depth
}</p>
<pre><code>Good</code></pre><p>type RectangleSide = {
  width: number
  height: number
  depth: number
}</p>
<p>const sumOfLengthOfRectangle
 = ({width, height, depth}: RectangleSide) =&gt; {
  return width + height + depth
}</p>
<pre><code>2. 함수 단일책임 시키기(SRP) (=== 선언형)
Bad</code></pre><p>const emailClients = (clients: Client[]) =&gt; {
  clients.forEach((client) =&gt; {
    const clientRecord = database.lookup(client)
    if (clientRecord.isActive()) {
      email(client)
    }
  });
}</p>
<p>// or</p>
<p>const getChildrenAndGetSumOfAge = () =&gt; {
  const children = database.get(&#39;children&#39;)</p>
<p>  return sumOf(children.map((child) =&gt; child.age))
}</p>
<pre><code>Good</code></pre><p>const emailClients = (clients: Client[]) =&gt; {
  clients.filter(isActiveClient).forEach(email)
}</p>
<p>function isActiveClient(client: Client) {
  const clientRecord = database.lookup(client)
  return clientRecord.isActive()
}</p>
<p>// or</p>
<p>const getTotalAgeOfChildren = () =&gt; {
    return sumOf(getChildrenAges())
}</p>
<p>const getChildrenAges = () =&gt; {
  return database.get(&#39;children&#39;).map((child) =&gt; child.age)
}</p>
<pre><code>3. 코드 중복 제거하기
설명 귀찮음

4. 함수 인자에 _(underscore) 붙이기
Bad</code></pre><p>const someFunc = (child: Child, age: number) =&gt; {
  // ...
}</p>
<p>// or</p>
<p>const childrenAges = myChildren.map((child) =&gt; {
  return child.age
})</p>
<pre><code>Good</code></pre><p>const someFunc = (_child: Child, _age: number) =&gt; {
  // ...
}</p>
<p>// or</p>
<p>const childrenAges = myChildren.map((_child) =&gt; {
  return _child.age
})
⇒ 항상은 아니다.</p>
<pre><code>Bad</code></pre><p>const someFunc = ({_child, _age}: {_child: Child, _age: number}) =&gt; {
  // ...
}</p>
<p>// or</p>
<p>const someFunc = ({_child: child, _age: age}
  : {_child: Child, _age: number}) =&gt; {
    // ...
}</p>
<pre><code>Good</code></pre><p>const someFunc = ({child, age}: {child: Child, age: number}) =&gt; {
  // ...
}</p>
<pre><code>5. 함수 인자로 플래그처럼 쓰지 말기
Bad</code></pre><p>const createFile = (name: string, temp: boolean) =&gt; {
  if (temp) {
    fs.create(<code>./temp/${name}</code>)
  } else {
    fs.create(name)
  }
}</p>
<pre><code>Good</code></pre><p>const createTempFile = (name: string) =&gt; {
  createFile(<code>./temp/${name}</code>)
}</p>
<p>const createFile = (name: string) =&gt; {
  fs.create(name)
}
⇒ 하지만 이벤트 자체가 명확하지 않으면, 플래그 구분이 어딘가에는 결국 들어간다.</p>
<p>(이벤트 자체가 명확한 경우)</p>
<pre><code>
Bad</code></pre><p>const handleButtonClick = (_myVar?: number) =&gt; () =&gt; {
  if(_myVar !== undefined){
    console.log(_myVar)
  }else{
    console.log(&#39;no!&#39;)
  }
}</p>
<p>// ...</p>
<p>&lt;button
  onClick={handleButtonClick(5)}</p>
<blockquote>
</blockquote>
<p>  버튼1
</buttn></p>
<p>&lt;button
  onClick={handleButtonClick()}</p>
<blockquote>
</blockquote>
<p>  버튼2
</buttn>
Good
const handleNumberButtonClick = (_myVar: number) =&gt; () =&gt; {
    console.log(_myVar)
}</p>
<p>const handleButtonClick = () =&gt; {
    console.log(&#39;no!&#39;)
}</p>
<p>// ...</p>
<p>&lt;button
  onClick={handleNumberButtonClick(5)}</p>
<blockquote>
</blockquote>
<p>  버튼1
</buttn></p>
<p>&lt;button
  onClick={handleButtonClick}</p>
<blockquote>
</blockquote>
<p>  버튼2
</buttn></p>
<pre><code>6. Side Effect 피하기(순수 함수) 1
Bad</code></pre><p>export const SomeComponent = ({someA, someB}: Props) =&gt; {
  const [count, setCount] = useState<number>(0)</p>
<p>  const processSomething = () =&gt; {
    console.log(count, someA)
  }</p>
<p>  const handleButtonClick = () =&gt; {
        processSomething()
  }</p>
<p>  return (
        ...
  )
}</p>
<pre><code>Good</code></pre><p>export const SomeComponent = ({someA, someB}: Props) =&gt; {
  const [count, setCount] = useState<number>(0)</p>
<p>  const processSomething = (_count: number, _someA: number) =&gt; {
    console.log(_count, _someA)
  }</p>
<p>  const handleButtonClick = () =&gt; {
        processSomething(count, someA)
  }</p>
<p>  return (
        ...
  )
}</p>
<pre><code>⇒ React 이벤트 핸들러에는 굳이..?

React에서 완벽한 순수함수를 하려면?</code></pre><p>export const SomeComponent = ({someA, someB}: Props) =&gt; {
  const [count, setCount] = useState<number>(0)</p>
<p>  const processSomething = (_count: number, _someA: number) =&gt; {
    console.log(_count, _someA)
  }</p>
<p>  const handleButtonClick = (_count: number, _someA: number) =&gt; () =&gt; {
        processSomething(_count, _someA)
  }</p>
<p>  // 어차피 props 써야함
  return (
        &lt;button onClick={handleButtonClick(count, someA)}&gt;{...}</button>
  )
}</p>
<pre><code>7. Side Effect 피하기(순수 함수) 2
Bad</code></pre><p>const addItemToCart = (cart: CartItem[], item: Item): void =&gt; {
  cart.push({ item, date: Date.now() })
}</p>
<pre><code>Good</code></pre><p>const addItemToCart = (cart: CartItem[], item: Item): CartItem[] =&gt; {
  return cart.concat({ item, date: Date.now() })
}</p>
<pre><code>8. Immutable에 적극 활용하기
Bad</code></pre><p>let order = 0</p>
<p>if (condition1){
  order = 3
}else if(condition2){
  order = 1
}</p>
<p>console.log(order)</p>
<pre><code>Good</code></pre><p>const getOrderByCondition = () =&gt; {
    if (condition1){
        return 3
  } else if (condition2){
        return 1
    }</p>
<pre><code>return 0</code></pre><p>}</p>
<p>const order = getOrderByCondition()
console.log(order)</p>
<pre><code>9. 조건문 캡슐화 (선언형)
Bad</code></pre><p>if (subscription.isTrial || account.balance &gt; 0) {
  // ...
}</p>
<pre><code>Good</code></pre><p>const canActivateService = (_subscription: Subscription, _account: Account) =&gt; {
  return _subscription.isTrial || _account.balance &gt; 0
}</p>
<p>if (canActivateService(subscription, account)) {
  // ...
}</p>
<pre><code>10. 부정 조건문 피하기
Bad</code></pre><p>const isEmailNotUsed = (email: string): boolean =&gt; {
  // ...
}</p>
<p>if (isEmailNotUsed(email)) {
  // ...
}</p>
<pre><code>Good</code></pre><p>const isEmailUsed = (email): boolean =&gt; {
  // ...
}</p>
<p>if (!isEmailUsed(node)) {
  // ...
}</p>
<pre><code>11. 과한 최적화는 피하기
Bad</code></pre><p>const length = myList.length
for (let i = 0; i &lt; length; i++ ) { ... }</p>
<pre><code>Good</code></pre><p>for (let i = 0; i &lt; myList.length; i++ ) { ... }</p>
<p>```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[분산컴퓨팅/분산파일저장]]></title>
            <link>https://velog.io/@myungha_cho/%EB%B6%84%EC%82%B0%EC%BB%B4%ED%93%A8%ED%8C%85%EB%B6%84%EC%82%B0%ED%8C%8C%EC%9D%BC%EC%A0%80%EC%9E%A5</link>
            <guid>https://velog.io/@myungha_cho/%EB%B6%84%EC%82%B0%EC%BB%B4%ED%93%A8%ED%8C%85%EB%B6%84%EC%82%B0%ED%8C%8C%EC%9D%BC%EC%A0%80%EC%9E%A5</guid>
            <pubDate>Sat, 21 May 2022 06:34:09 GMT</pubDate>
            <description><![CDATA[<ol>
<li>P2P</li>
<li>BlockChain</li>
<li>IPFS</li>
<li>클라우스 환경에서의 분산컴퓨팅 </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Substrate tutorial#4]]></title>
            <link>https://velog.io/@myungha_cho/Substrate-tutorial4</link>
            <guid>https://velog.io/@myungha_cho/Substrate-tutorial4</guid>
            <pubDate>Tue, 17 May 2022 04:15:20 GMT</pubDate>
            <description><![CDATA[<p>This materials was written based on <a href="https://docs.substrate.io/tutorials/v3/kitties/pt1/">https://docs.substrate.io/tutorials/v3/kitties/pt1/</a> </p>
<p>git clone <a href="https://github.com/chomyungha51/substrate-kitties-node.git">https://github.com/chomyungha51/substrate-kitties-node.git</a></p>
<p>I left numbered comment for you not to switching windows between ppt and tutorial page:) Just uncomment it one by one!</p>
<p>Get substrate-front-end-template</p>
<p>git clone <a href="https://github.com/chomyungha51/substrate-kitties-front-template.git">https://github.com/chomyungha51/substrate-kitties-front-template.git</a>
cd substrate-kitties-front-template
yarn install</p>
<ol start="2">
<li>In a separate terminal, start an instance of node-kitties that you built in Part I </li>
</ol>
<p>./target/release/node-kitties --dev --tmp</p>
<ol start="3">
<li>Start the Front-end template</li>
</ol>
<p>yarn start</p>
<p><a href="https://docs.google.com/presentation/d/1zO5wb8omIb5n7_MnrU4BIbFJqlCuf_6gXEFeE4aQ_SY/edit?usp=sharing">https://docs.google.com/presentation/d/1zO5wb8omIb5n7_MnrU4BIbFJqlCuf_6gXEFeE4aQ_SY/edit?usp=sharing</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹소켓, 실시간 연동 최적화]]></title>
            <link>https://velog.io/@myungha_cho/Subscription-%EC%BF%BC%EB%A6%AC-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@myungha_cho/Subscription-%EC%BF%BC%EB%A6%AC-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Wed, 11 May 2022 01:21:21 GMT</pubDate>
            <description><![CDATA[<h1 id="http">HTTP</h1>
<p>http는 요청-응답 기반의 프로토콜</p>
<ul>
<li>연결: 3way-handshaking</li>
<li>종료: 4way-handshaking</li>
</ul>
<p><code>keep-alive</code>
: 설정한 시간 동안 계속해서 네트워크가 연결</p>
<h2 id="실시간으로-데이터를-받고-싶을-땐"><strong>실시간으로 데이터를 받고 싶을 땐?</strong></h2>
<ul>
<li><code>http polling</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/25c55a1f-913f-4659-b17a-a81568157a5e/image.png" alt=""></p>
<p>서버로 일정 주기 요청
데이터가 사용가능하거나 타임아웃인 경우에 응답
서버가 처리해야 하는 요청이 너무 많음</p>
<ul>
<li><code>http streaming</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/0b68c597-d367-428d-9d73-cf896ba53dab/image.png" alt=""></p>
<p>서버에 요청을 보내고 끊기지 않은 연결상태에서 끊임없이 데이터를 수신
클라이언트에서 서버로의 데이터 송신이 어려움
중간에 연결 끊기면 실시간 보장 못 함</p>
<hr>
<h1 id="websocket">WEBSOCKET</h1>
<p>서버와 클라이언트 간에 Socket Connection을 유지해서 양방향 통신
이벤트 유도 프로토콜</p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/6b43719c-029b-445c-95c8-29bf82bb61a2/image.png" alt=""></p>
<ul>
<li>Http 포트번호 80(ws) 또는 443(wss) 위에서 동작</li>
<li>업그레이드 헤더를 사용해 http에서 websocket으로 변경</li>
</ul>
<h2 id="연결-과정">연결 과정</h2>
<ul>
<li>요청
<img src="https://velog.velcdn.com/images/myungha_cho/post/9eb577c7-b937-4d08-bb5c-0a4f171f7de2/image.png" alt=""></li>
</ul>
<p>HTTP 1.1로 요청
웹 소켓 프로토콜로 업그레이드 요청
소켓 버전과 소켓 비밀키 정보 등을 포함</p>
<ul>
<li>응답
<img src="https://velog.velcdn.com/images/myungha_cho/post/5ac344da-9f46-471b-9411-f6ea6a1c9ca2/image.png" alt=""></li>
</ul>
<p>HTTP status 101 응답
일정 시간이 지나면 HTTP연결은 자동으로 끊어진다.</p>
<h2 id="데이터-전송-frame">데이터 전송 frame</h2>
<ul>
<li>헤더 + payload로 구성</li>
<li>UTF-8 인코딩</li>
</ul>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/a5972a0f-cde8-4d96-aa6d-21e5c6b06d0d/image.png" alt=""></p>
<p>800byte에서 수 kbyte의 헤더 크기를 가지고 있는 HTTP와 달리 WebSocket은 수 byte 수준으로 압축이 가능</p>
<h2 id="고려할-점">고려할 점</h2>
<ul>
<li>이벤트 유실(유저의 네트워크 환경) : 브로드캐스팅</li>
<li>연결이 끊기면 다시 알아서 연결 안 됨 서버 다운/연결 끊길 경우 대비한 로직</li>
<li>데이터 파싱: WebSocket은 주고 받는 데이터의 해독은 온전히 어플리케이션에 맡김, sub-protocols을 사용</li>
</ul>
<hr>
<h1 id="최적화">최적화</h1>
<ol>
<li><p>페이지네이션</p>
</li>
<li><p>레이지로딩</p>
</li>
<li><p>image/jpeg =&gt; image/webp
:webp는 JPG, PNG와 같이 이미지 포맷 형식 중 하나, 최고의 이미지 압축률</p>
</li>
<li><p>gzip같은 방식으로 이미지 압축해서 전송
: Accept-Encoding은 클라이언트 단에서 받아들일 수 있는 압축 방식, gzip은 콘텐츠 압축에 있어 원본 데이터 약 70%를 압축</p>
</li>
<li><p>Native reversed loop로 변경</p>
</li>
<li><p>setTimeout으로 부하가 큰 로직을 큐로 넘기기</p>
</li>
<li><p>연결 빈도 수 제한
메모리는 웹소켓을 통한 SEND 커맨드 같이 서버가 서비스 관련 메시지를 보낼 때 보다 SUBSCRIBE/UNSUBSCRIBE(이하 SUB/UNSUB)시 더 많이 사용됨
SUB/UNSUB가 자주 일어나게 되면 처리 속도가 밀림
최대 SUB/UNSUB 빈도 수를 2800TPS로 설정
적절함의 기준은 GC가 발생했을 때 요청 처리 속도가 현저하게 느려지지 않는 수준</p>
</li>
<li><p>Vanila JS 리팩토링</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/362576f5-95d7-47a1-994d-6a6236ea2ef9/image.png" alt=""></p>
<p>컴포넌트가 재활용성이 필요한 페이지나 동적 처리가 많은 스크립트 코드를 제외하곤, 기본적인 랜딩 페이지나 기본 동작 스크립트, 라이브러리들은 모두 리액트를 걷어내고 바닐라(Vanilla) 자바스크립트로 변경하여 위와 같은 성과를 얻을 수 있었다고 함</p>
<hr>
<h1 id="참고-문헌">참고 문헌</h1>
<p><a href="https://velog.io/@wldus9503/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-Network3.-WebSocket-%EC%9B%B9-%EC%86%8C%EC%BC%93%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C">https://velog.io/@wldus9503/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-Network3.-WebSocket-%EC%9B%B9-%EC%86%8C%EC%BC%93%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C</a>
<a href="https://velog.io/@oyeon/%EC%9B%B9-%EC%86%8C%EC%BC%93WebSocket">https://velog.io/@oyeon/%EC%9B%B9-%EC%86%8C%EC%BC%93WebSocket</a>
<a href="https://www.peterkimzz.com/websocket-vs-socket-io/">https://www.peterkimzz.com/websocket-vs-socket-io/</a>
<a href="https://helloinyong.tistory.com/295">https://helloinyong.tistory.com/295</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그인절차_20220421]]></title>
            <link>https://velog.io/@myungha_cho/%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%A0%88%EC%B0%A820220421</link>
            <guid>https://velog.io/@myungha_cho/%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%A0%88%EC%B0%A820220421</guid>
            <pubDate>Wed, 11 May 2022 00:12:40 GMT</pubDate>
            <description><![CDATA[<p>로그인
로그인을 하는 이유?
: 매 요청마다 아이디와 비밀번호를 전송(쿼리, 아이디와 비밀번호 노출 횟수 증가)하지 않고 사용자 정보(접근권한) 유지</p>
<p>사용자가 입력한 비밀번호 암호화
아이디와 비밀번호 해시값 데이터베이스에서 탐색
세션 기반 인증
: 서버가 관리</p>
<p>발급
서버 쪽 저장소에 세션 생성(key: 세션ID - value: 사용자 정보)
세션ID를 클라이언트에게 응답</p>
<p>사용
클라이언트는 서버에 요청할 때마다 세션ID도 전송
서버는 세션 ID로 사용자 식별</p>
<p>삭제
로그아웃하면, 서버는 해당 세션 삭제</p>
<p>장점
서버 보안이 (클라이언트보다) 더 좋고 세션 관리가 가능</p>
<p>단점
사용자들이 동시에 많이 접속하면 서버 저장소에 부하
저장소에 문제가 생겨서 세션들이 전부 날아가면, 로그인한 사용자들은 전부 다시 로그인해야 함
여러 대의 서버들로 운영되는 경우 세션을 공용 저장소에 저장해야 함</p>
<p>토큰 기반 인증
: 클라이언트가 관리</p>
<p>발급
서버가 사용자 정보와 비밀 키를 이용하여 토큰을 발급하고 클라이언트에게 응답
클라이언트 단의 저장소(쿠키, 로컬 스토리지, 세션 스토리지)에 저장</p>
<p>사용
클라이언트는 요청을 보낼 때마다 해당 토큰을 서버에 전송
서버는 토큰으로 사용자 식별</p>
<p>삭제
로그아웃하면, 클라이언트 저장소에서 해당 토큰 삭제</p>
<p>장점
서버 쪽 저장소의 부하를 신경 쓸 필요 없음</p>
<p>단점
토큰 자체에 로그인한 사용자의 상태 정보가 담겨 있음
한 번 발급한 토큰을 유효기간 지나기 전까지 무효화시킬 방법이 없음
-&gt; 해킹당했을 때 피해 더 큼</p>
<p>JWT(JSON Web Token)
토큰 기반 인증에서 사용하는 대표적인 토큰</p>
<p>헤더(Header)
페이로드(Payload)
서명(Signature)
각 부분을 Base64로 인코딩하고 이들을 마침표(.)를 이용하여 차례대로 연결
빨간색 부분이 헤더, 보라색 부분이 페이로드, 하늘색 부분이 서명</p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/a4d6fe0d-8702-4e68-9a8b-d112d1f3eb7e/image.png" alt=""></p>
<p>헤더 (Header)
토큰의 타입 ex) &#39;JWT&#39;
서명 계산 및 토큰 검증에 사용할 암호화 알고리즘 ex) HS256(SHA256), RSA
페이로드 (Payload)
로그인한 사용자의 상태 정보 ex) 누가 누구에게 발급한 토큰인지, 유효 기한, 사용자에게 공개하고자 하는 정보들은 무엇인지 등
서명 (Signature)
: 토큰의 무결성, 유효성을 검증하는 데 사용
(헤더와 페이로드를 Base64로 인코딩하고, 이 둘을 마침표(.)로 연결한 것을 서버의 비밀 키와 함께 암호화 알고리즘에 넣어서 얻은 값)</p>
<p>서버는 받은 토큰의 헤더와 페이로드로 서명 값을 계산하고, 계산해서 얻은 값과 받은 토큰에 있는 서명 필드가 동일한지 검사(무결성)
만약 동일하지 않다면 요청을 거부하고, 동일하다면 유효 기한을 검사
단점
JWT의 페이로드를 Base64로 디코딩만 하면 해당 사용자의 정보를 손쉽게 얻어낼 수 있음
이미 발급된 JWT는 무효화를 하기가 어려움 -&gt; 한 번 해킹당하면 유효기간 끝날 때까지 못 막음</p>
<p>리프레시 토큰
로그인에 성공하면, 두 개의 토큰을 발급</p>
<p>액세스 토큰: 사용자의 상태 정보 저장하는 토큰
리프레시 토큰: 액세스 토큰을 다시 발급받기 위해 서버에 전송하는 토큰</p>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/a2db0535-c227-4e75-856d-6b83602d5532/image.png" alt=""></p>
<p>액세스 토큰의 유효 기간을 짧게 유지(해커가 오래 쓰지 못하도록)
로그인한 사용자의 상태 정보를 서버가 관리 가능
동시 로그인 관리 가능: 동일한 리프레시 토큰을 공유
토큰을 어디에 저장할까?
(session storage -&gt; 브라우저 종료 시 초기화)</p>
<p>local storage -&gt; 반영구적
cookie -&gt; 브라우저에서 자동으로 보내는 값, 해커 코드 자동으로 실행되는 경우 : CSRF
<img src="https://velog.velcdn.com/images/myungha_cho/post/518e828e-a8a1-4bc1-b110-eda780b38696/image.png" alt=""></p>
<p>db : refresh token에 대한 hash table
참고문헌</p>
<p><a href="https://it-eldorado.tistory.com/164">https://it-eldorado.tistory.com/164</a>
<a href="https://it-eldorado.tistory.com/165">https://it-eldorado.tistory.com/165</a>
<a href="https://blog.lgcns.com/2687">https://blog.lgcns.com/2687</a>
<a href="https://programming-workspace.tistory.com/51">https://programming-workspace.tistory.com/51</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SubstrateKitties-2]]></title>
            <link>https://velog.io/@myungha_cho/SubstrateKitties-2</link>
            <guid>https://velog.io/@myungha_cho/SubstrateKitties-2</guid>
            <pubDate>Tue, 10 May 2022 05:09:00 GMT</pubDate>
            <description><![CDATA[<p>part 2</p>
<p>우리의 고양이 노드와 소통할 수 있는 커스텀 리액트 컴포넌트 만들기</p>
<ol>
<li>시작하기</li>
</ol>
<p>Substrate Front-end Template 설치하기</p>
<p>git clone <a href="https://github.com/substrate-developer-hub/substrate-front-end-template.git">https://github.com/substrate-developer-hub/substrate-front-end-template.git</a>
cd substrate-front-end-template
yarn install</p>
<p>폴더구조
substrate-front-end-template
|
+-- public
|   |
|   +-- assets              &lt;-- Kitty avatar PNG files
|
+-- src                     &lt;-- our React components
|   |
|   +-- <strong>tests</strong>
|   |
|   +-- config              &lt;-- where to specify our custom types
|   |
|   +-- substrate-lib       &lt;-- lib to give access to PolkadotJS API
|   |   |
|   |   +-- components      &lt;-- contains TxButton, used throughout our application
|   |
|   ...
...</p>
<p>다른 터미널을 열어서 Part1에서 만든 노드 시작하기</p>
<h1 id="launch-node-kitties-from-its-directory">Launch <code>node-kitties</code> from its directory.</h1>
<p>cd kitties/
./target/release/node-kitties --dev --tmp</p>
<p>프론트엔드 디렉토리로 돌아와서, 시작하기
yarn start</p>
<p>무엇을 만들까?
Substrate Front-end template은 폴카닷JS API와 RPC엔드포인트를 사용해 Substrate노드와 소통
그래서 스토리지 아이템도 읽고 dispatchable 함수를 호출해 extrinsic을 만들 수도 있음</p>
<ol>
<li>Kitties.js</li>
<li>KittyCards.js</li>
<li>KittyAvatar.js</li>
</ol>
<p>** 폴카닷 JS API 기초 공부하기
Basics and Metadata
RPC queries
Storage methods
Extrinsics methods
The Devhub substrate-frontend-template</p>
<ol start="2">
<li>커스컴 컴포넌트 만들기</li>
</ol>
<p>Kitties.js만들기
가장 top-level에 있는 컴포넌트로, Apps.js에 의해 렌더링될 것</p>
<p>src폴더에 Kitties.js파일 만들고 임포트</p>
<p>import React, { useEffect, useState } from &#39;react&#39;
import { Form, Grid } from &#39;semantic-ui-react&#39;</p>
<p>import { useSubstrateState } from &#39;./substrate-lib&#39;
import { TxButton } from &#39;./substrate-lib/components&#39;</p>
<p>import KittyCards from &#39;./KittyCards&#39;</p>
<p>폴카닷 JS API 인스턴스의 래퍼인 substrate-lib를 통해서 폴카닷 JS API를 사용할 것
계정 키를 가져올 수도 있음</p>
<p>const parseKitty = ({ dna, price, gender, owner }) =&gt; ({
  dna,
  price: price.toJSON(),
  gender: gender.toJSON(),
  owner: owner.toJSON(),
})</p>
<p>export default function Kitties(props) {
  const { api, keyring } = useSubstrateState()
  const [kittyIds, setKittyIds] = useState([])
  const [kitties, setKitties] = useState([])
  const [status, setStatus] = useState(&#39;&#39;)</p>
<p>parseKittiy() : 고양이 데이터를 가지고 있고 오브젝트를 리턴하는 함수
Kitties : 체인의 스토리지 아이템이 실시간으로 변하는 걸 감지하고 useEffect를 써서
우리의 다른 컴포넌트의 상태를 업데이트시켜줌</p>
<p>실시간으로 변화를 감지해야 하는 건</p>
<ul>
<li>고양이 양에 대한 스토리지 변화</li>
<li>고양이 오브젝트의 변화</li>
</ul>
<p>각각 subscription 함수를 만들 것임</p>
<ul>
<li>고양이 양: 우리의 고양이 팔레트 스토리지 아이템에 CountForKitties에 쿼리 날리는
api.query.substrateKitties.countForKitties를 쓸 것</li>
<li>모든 고양이 데이터를 api.query.substrateKitties.kitties메소드로 가져오고 parseKittiy()함수로
파싱할 것</li>
</ul>
<p>// Subscription function for kitty count
const subscribeCount = () =&gt; {
  let unsub = null
  const asyncFetch = async () =&gt; {
    unsub = await api.query.substrateKitties.countForKitties(
      async count =&gt; {
        // Fetch all kitty keys
        const entries = await api.query.substrateKitties.kitties.entries()
        const ids = entries.map(entry =&gt; entry[1].unwrap().dna)
        setKittyIds(ids)
      }
    )
  }
  asyncFetch()
  return () =&gt; {
    unsub &amp;&amp; unsub()
  }
}</p>
<p>// Subscription function to construct all kitty objects
const subscribeKitties = () =&gt; {
  let unsub = null
  const asyncFetch = async () =&gt; {
    unsub = await api.query.substrateKitties.kitties.multi(
      kittyIds,
      kitties =&gt; {
        const kittiesMap = kitties.map(kitty =&gt; parseKitty(kitty.unwrap()))
        setKitties(kittiesMap)
      }
    )
  }
  asyncFetch()
  return () =&gt; {
    // return the unsubscription cleanup function
    unsub &amp;&amp; unsub()
  }
}</p>
<p>asyncFetch에서 고양이 스토리지를 구독했다.
이 컴포넌트가 끝날 때, 구독 취소를 하고 싶다.
그래서 리턴함수로 정리해줌</p>
<p>리액트의 useEffect()로 subscribeCount와 subscribeKitties를 넘겨주면 됨</p>
<p>useEffect(subscribeCount, [api, keyring])
useEffect(subscribeKitties, [api, keyring, kittyIds])</p>
<ol start="2">
<li>src/KittyAvatar.js
고양이의 DNA와 PNG이미지를 맵핑시킬 것</li>
</ol>
<p>import React from &#39;react&#39;</p>
<p>// Generate an array [start, start + 1, ..., end] inclusively
const genArray = (start, end) =&gt;
  Array.from(Array(end - start + 1).keys()).map(v =&gt; v + start)</p>
<p>const IMAGES = {
  accessory: genArray(1, 20).map(
    n =&gt; <code>${process.env.PUBLIC_URL}/assets/KittyAvatar/accessorie_${n}.png</code>
  ),
  body: genArray(1, 15).map(
    n =&gt; <code>${process.env.PUBLIC_URL}/assets/KittyAvatar/body_${n}.png</code>
  ),
  eyes: genArray(1, 15).map(
    n =&gt; <code>${process.env.PUBLIC_URL}/assets/KittyAvatar/eyes_${n}.png</code>
  ),
  mouth: genArray(1, 10).map(
    n =&gt; <code>${process.env.PUBLIC_URL}/assets/KittyAvatar/mouth_${n}.png</code>
  ),
  fur: genArray(1, 10).map(
    n =&gt; <code>${process.env.PUBLIC_URL}/assets/KittyAvatar/fur_${n}.png</code>
  ),
}</p>
<p>const dnaToAttributes = dna =&gt; {
  const attribute = (index, type) =&gt;
    IMAGES[type][dna[index] % IMAGES[type].length]</p>
<p>  return {
    body: attribute(0, &#39;body&#39;),
    eyes: attribute(1, &#39;eyes&#39;),
    accessory: attribute(2, &#39;accessory&#39;),
    fur: attribute(3, &#39;fur&#39;),
    mouth: attribute(4, &#39;mouth&#39;),
  }
}</p>
<p>const KittyAvatar = props =&gt; {
  const outerStyle = { height: &#39;160px&#39;, position: &#39;relative&#39;, width: &#39;50%&#39; }
  const innerStyle = {
    height: &#39;150px&#39;,
    position: &#39;absolute&#39;,
    top: &#39;3%&#39;,
    left: &#39;50%&#39;,
  }
  const { dna } = props</p>
<p>  if (!dna) return null</p>
<p>  const cat = dnaToAttributes(dna)
  return (
    <div style={outerStyle}>
      <img alt="body" src={cat.body} style={innerStyle} />
      <img alt="fur" src={cat.fur} style={innerStyle} />
      <img alt="mouth" src={cat.mouth} style={innerStyle} />
      <img alt="eyes" src={cat.eyes} style={innerStyle} />
      <img alt="accessory" src={cat.accessory} style={innerStyle} />
    </div>
  )
}</p>
<p>export default KittyAvatar</p>
<p>KittyCards.js로부터 dna 하나만 전달받음</p>
<p>링크에 가서 PNG다운받고 public/assets/KittyAvatar라는 새 폴더를 만들어서 PNG들 붙여넣기</p>
<ol start="3">
<li>KittyCards.js 안에 transferModal 작성하기
KittyCards는 세 파트로 나눠질 것임</li>
</ol>
<ul>
<li>TransferModal, setPrice, buyPrice : TxButton컴포넌트를 사용한 모달 다이얼로그</li>
<li>KittyCard : KittyAvatar컴포넌트를 사용해서 고양이 아바타를 보여주는 카드</li>
<li>KittyCards : KittyCard를 그리드로 보여주는 컴ㅍ넌트</li>
</ul>
<p>먼저 src/KittyCards.js를 만들고 임포트</p>
<p>import React from &#39;react&#39;
import {
  Button,
  Card,
  Grid,
  Message,
  Modal,
  Form,
  Label,
} from &#39;semantic-ui-react&#39;
import KittyAvatar from &#39;./KittyAvatar&#39;
import { useSubstrateState } from &#39;./substrate-lib&#39;
import { TxButton } from &#39;./substrate-lib/components&#39;</p>
<p>먼저 TransferModal을 보자</p>
<p>Substrate Front-end Template이 TxButton이라는 컴포넌트를 갖고 있는데
노드와 상호작용하는 전송 버튼을 포함하는 유용한 방법이다</p>
<p>노드에 트랜잭션을 보내고 고양이 팔레트에 서명된 extrinsic을 유발하도록 해줌</p>
<p>transfer button
modal(kitty id, receiver address)
transfer, cancel button</p>
<p>세 파트로 구성됨</p>
<p>나머지도 비슷한 구성임</p>
<p>리액트 훅으로 props 추출하기
const TransferModal = props =&gt; {
 const { kitty, accountPair, setStatus } = props;
  const [open, setOpen] = React.useState(false);
  const [formValue, setFormValue] = React.useState({});
  const formChange = key =&gt; (ev, el) =&gt; {
    setFormValue({ ...formValue, [key]: el.value });
  };
const confirmAndClose = unsub =&gt; {
  setOpen(false)
  if (unsub &amp;&amp; typeof unsub === &#39;function&#39;) unsub()
}
return (
  &lt;Modal
    onClose={() =&gt; setOpen(false)}
    onOpen={() =&gt; setOpen(true)}
    open={open}
    trigger={
      <Button basic color="blue">
        Transfer
      </Button>
    }</p>
<blockquote>
</blockquote>
<pre><code>&lt;Modal.Header&gt;Kitty Transfer&lt;/Modal.Header&gt;
&lt;Modal.Content&gt;
  &lt;Form&gt;
    &lt;Form.Input fluid label=&quot;Kitty ID&quot; readOnly value={kitty.dna} /&gt;
    &lt;Form.Input
      fluid
      label=&quot;Receiver&quot;
      placeholder=&quot;Receiver Address&quot;
      onChange={formChange(&#39;target&#39;)}
    /&gt;
  &lt;/Form&gt;
&lt;/Modal.Content&gt;
&lt;Modal.Actions&gt;
  &lt;Button basic color=&quot;grey&quot; onClick={() =&gt; setOpen(false)}&gt;
    Cancel
  &lt;/Button&gt;
  &lt;TxButton
    label=&quot;Transfer&quot;
    type=&quot;SIGNED-TX&quot;
    setStatus={setStatus}
    onClick={confirmAndClose}
    attrs={{
      palletRpc: &#39;substrateKitties&#39;,
      callable: &#39;transfer&#39;,
      inputParams: [formValue.target, kitty.dna],
      paramFields: [true, true],
    }}
  /&gt;
&lt;/Modal.Actions&gt;</code></pre>  </Modal>
)


<ol start="4">
<li>KittyCard 작성하기
Kitty.js로부터 받은 props로 컴포넌트 렌더링</li>
</ol>
<p>Semantic UI Card컴포넌트 사용할 것임</p>
<p>// Use props
const KittyCard = props =&gt; {
  const { kitty, setStatus } = props
  const { dna = null, owner = null, gender = null, price = null } = kitty
  const displayDna = dna &amp;&amp; dna.toJSON()
  const { currentAccount } = useSubstrateState()
  const isSelf = currentAccount.address === kitty.owner</p>
<p>  return (
  <Card>
    {isSelf &amp;&amp; (
      <Label as="a" floating color="teal">
        Mine
      </Label>
    )}
    {/* Render the Kitty Avatar <em>/}
    <KittyAvatar dna={dna.toU8a()} />
    &lt;Card.Content&gt;
      {/</em> Display the Kitty DNA <em>/}
      &lt;Card.Meta style={{ fontSize: &#39;.9em&#39;, overflowWrap: &#39;break-word&#39; }}&gt;
        DNA: {displayDna}
      &lt;/Card.Meta&gt;
      {/</em> Display the Kitty Gender, Owner, and Price */}
      &lt;Card.Description&gt;
        &lt;p style={{ overflowWrap: &#39;break-word&#39; }}&gt;Gender: {gender}</p>
        &lt;p style={{ overflowWrap: &#39;break-word&#39; }}&gt;Owner: {owner}</p>
        &lt;p style={{ overflowWrap: &#39;break-word&#39; }}&gt;
          Price: {price || &#39;Not For Sale&#39;}
        </p>
      &lt;/Card.Description&gt;
    &lt;/Card.Content&gt;
    &lt;Card.Content extra style={{ textAlign: &#39;center&#39; }}&gt;
  {owner === currentAccount.address ? (
    &lt;&gt;
      <SetPrice kitty={kitty} setStatus={setStatus} />
      <TransferModal kitty={kitty} setStatus={setStatus} />
    &lt;/&gt;
  ) : (
    &lt;&gt;
      <BuyKitty kitty={kitty} setStatus={setStatus} />
    &lt;/&gt;
  )}
&lt;/Card.Content&gt;
</Card>
}
주인이 옮기는 경우에만 Transfer Modal과 SetPRice 컴포넌트가 뜨도록 해줌</p>
<ol start="5">
<li>KittyCards 쓰기</li>
</ol>
<ul>
<li>하나도 없으면 하나도 없으니까 하나 만들라는 메시지 보여주기</li>
<li>있으면 3열로 보여주기</li>
</ul>
<p>const KittyCards = props =&gt; {
  const { kitties, setStatus } = props</p>
<p>  if (kitties.length === 0) {
    return (
      <Message info>
        &lt;Message.Header&gt;
          No Kitty found here... Create one now!&nbsp;
          <span role="img" aria-label="point-down">
            👇
          </span>
        &lt;/Message.Header&gt;
      </Message>
    )
  }</p>
<p>  return (
    <Grid columns={3}>
      {kitties.map((kitty, i) =&gt; (
        &lt;Grid.Column key={<code>kitty-${i}</code>}&gt;
          <KittyCard kitty={kitty} setStatus={setStatus} />
        &lt;/Grid.Column&gt;
      ))}
    </Grid>
  )
}
export default KittyCards</p>
<p>SetPrice와 BuyKitty는 안 다룰것임 솔루션 봐라</p>
<ol start="6">
<li>Kitties.js로 돌아와서 완성하기</li>
</ol>
<p>return &lt;Grid.Column width={16}&gt;
  <h1>Kitties</h1>
  <KittyCards kitties={kitties} setStatus={setStatus}/></p>
<p>이거 복붙하고 : Grid안에 KittyCard 컴포넌트 넣음</p>
<Form style={{ margin: '1em 0' }}>
      <Form.Field style={{ textAlign: 'center' }}>
        <TxButton
          label='Create Kitty'
          type='SIGNED-TX'
          setStatus={setStatus}
          attrs={{
            palletRpc: 'substrateKitties',
            callable: 'createKitty',
            inputParams: [],
            paramFields: []
          }}
        />
      </Form.Field>
    </Form>
    <div style={{ overflowWrap: 'break-word' }}>{status}</div>
  </Grid.Column>;
}

<p>이거도 복붇하고 : TxButton 컴포넌트 렌더링하기 위해 form 사용</p>
<ol start="7">
<li>App.js 업데이트</li>
</ol>
<p>임포트문 추가
import Kitties from &#39;./Kitties&#39;</p>
<p>Container에 다음 줄 추가
&lt;Grid.Row&gt;
  <Kitties />
&lt;/Grid.Row&gt;</p>
<p>전체 정답 코드
<a href="https://github.com/substrate-developer-hub/substrate-front-end-template/blob/tutorials%2Fsolutions%2Fkitties/src/KittyCards.js">https://github.com/substrate-developer-hub/substrate-front-end-template/blob/tutorials%2Fsolutions%2Fkitties/src/KittyCards.js</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[튜토리얼#4 Substrate Kitties(1)]]></title>
            <link>https://velog.io/@myungha_cho/%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC4-Substrate-Kitties1</link>
            <guid>https://velog.io/@myungha_cho/%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC4-Substrate-Kitties1</guid>
            <pubDate>Mon, 09 May 2022 14:25:45 GMT</pubDate>
            <description><![CDATA[<p><del>러스트 코드 많다그래서 공부할 겸 이 파트 한다했는데...... 파트 1, 2로 나눌 정도로 분량 많을 줄이야...... 허허허</del></p>
<hr>
<h1 id="substrate-kitties-체인-만들기">Substrate Kitties 체인 만들기</h1>
<p><img src="https://velog.velcdn.com/images/myungha_cho/post/198848dd-ed66-4f27-83e1-f420bc5b6a90/image.png" alt=""></p>
<p>이 튜토리얼에서 Substrate kitties라 불리는 NFT를 생성하고 소유권을 다루는 블록체인을 만드는 법을 배울 겁니다. </p>
<p>파트 1에서는 당신이 만든 키티들과 상호작용할 수 있는 기능을 포함해서 키티 팔레트를 만드는 방법을 다룹니다. </p>
<p>파트 2에서는 파트 1에서 만든 Substarte Kitties 블록체인과 상호작용하도록 프론트엔드를 개발하는 방법을 다룹니다.</p>
<h2 id="1-튜토리얼-목표">1. 튜토리얼 목표</h2>
<ul>
<li>Substrate 노드를 만들고 실행시키는 기본 패턴을 배운다</li>
<li>커스텀 FRAME 팔렛을 작성하고 런타임에 통합한다.</li>
<li>스토리지 아이템들을 만들고 업데이트하는 법을 배운다</li>
<li>pallet extrinsic과 helper function을 작성한다</li>
<li>Substrate 노드를 커스텀 프론트엔드와 연결하기 위해 폴카닷JS API를 사용한다</li>
</ul>
<p><strong>시작하기 전에 설치할 것</strong>
<a href="https://docs.substrate.io/v3/getting-started/installation/">참고</a></p>
<p><strong>우리가 만드는 것</strong></p>
<ul>
<li>기존의 소스로부터 만들어지거나 기존 키티들을 길러서 생성</li>
<li>주인에 의해 설정된 가격에 팔림</li>
<li>주인에게서 다른 주인에게로 전송</li>
</ul>
<p><strong>우리가 다루지 않는 것</strong></p>
<ul>
<li>팔레트 테스트 코드</li>
<li>정확한 무게 값 사용하기</li>
</ul>
<h2 id="2-기본-세팅">2. 기본 세팅</h2>
<p>커스텀 팔렛을 설정하고 간단한 스토리지 아이템을 포함하는 섭스트레이트 노드 템플릿을 사용하는 기본적인 패턴을 다룹니다.</p>
<p><strong>당신의 템플릿 노드 설정하기</strong>
섭스트레이트 노드 템플릿에 네트워킹과 합희 계층이 포함되어 있기 때문에, 런타임과 팔렛의 로직을 만드는 것에만 집중하면 됩니다.</p>
<p>시작하기 위해서, 우리는 프로젝트 이름과 의존성을 설정해야 합니다. <strong>kickstart</strong>라는 CLI 도구로 쉽게 노드템플릿의 이름을 바꾸겠습니다.</p>
<ol>
<li><p>cargo install kickstart</p>
</li>
<li><p>프로젝트를 시작하고 싶은 루트 디렉토리에서 
kickstart <a href="https://github.com/sacha-l/kickstart-substrate">https://github.com/sacha-l/kickstart-substrate</a>
: 제일 최근의 노드 템플릿 복사본을 클론해오고 노드와 팔레트 이름 설정하게 함</p>
</li>
<li><p>kitties(node 폴더 안에 생김), kitties(pallet 폴더 안에 생김)</p>
</li>
<li><p>좋아하는 IDE로 kitties 디렉토리를 열고 이름을 kitties-tutorial로 바꿈
kickstart가 수정한 디렉토리들</p>
</li>
</ol>
<ul>
<li>node : 당신의 노드가 런타임과 RPC 클라이언트들과 상호작용하는 걸 허용하는 모든 로직을 포함</li>
<li>pallets : 모든 커스텀 팔렛들이 있음</li>
<li>runtime : 모든 팔렛들이 집계되고 체인의 런타임에서 구현됨</li>
</ul>
<p>5.runtime/src/lib.rs에서 커스텀 팔렛의 이름을 TemplateModule에서 SubstrateKitties로 수정</p>
<pre><code class="language-rust">construct_runtime!(
    // --snip
    {
        // --snip
        SubstrateKitties: pallet_kitties,
    }
);</code></pre>
<h3 id="pallet_kitties의-발판-작성"><strong>pallet_kitties의 발판 작성</strong></h3>
<p>kitties-tutorial           &lt;--  The name of our project directory
|
+-- node
|
+-- pallets
|   |
|   +-- kitties
|       |
|       +-- Cargo.toml
|       |
|       +-- src
|           |
|           +-- benchmarking.rs     &lt;-- Remove file
|           |
|           +-- lib.rs              &lt;-- Remove contents
|           |
|           +-- mock.rs             &lt;-- Remove file
|           |
|           +-- tests.rs            &lt;-- Remove file
|
+-- Cargo.toml</p>
<ol>
<li>pallets/kitties/src에 있는 benchmarking.rs, mock.rs, test.rs를 삭제하고 lib.rs의 내용을 지우기</li>
</ol>
<p>섭스트레이트에서 <strong>팔렛</strong>은 런타임 로직을 정의하는 데 사용되고, 우리는 섭스트레이트 키티들을 관리하는 모든 로직을 하나의 팔렛에 만들 것</p>
<p>모든 FRAME 팔렛은 </p>
<ul>
<li>frame_support와 frame_system의 의존성 집합</li>
<li>필요한 attribute macros(configuration trait, storage item, function call...)
를 가짐</li>
</ul>
<ol>
<li>pallet/kitties/src/lib.rs 파일의 내용을 다음 코드로 교체</li>
</ol>
<pre><code class="language-rust">#![cfg_attr(not(feature = &quot;std&quot;), 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&lt;T&gt;(_);

    /// 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&#39;s definition of an event.
        type Event: From&lt;Event&lt;Self&gt;&gt; + IsType&lt;&lt;Self as frame_system::Config&gt;::Event&gt;;

        /// The Currency handler for the Kitties pallet.
        type Currency: Currency&lt;Self::AccountId&gt;;

        // TODO Part II: Specify the custom types for our runtime.

    }

    // Errors.
    #[pallet::error]
    pub enum Error&lt;T&gt; {
        // TODO Part III
    }

    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event&lt;T: Config&gt; {
        // 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&#39;s genesis configuration.

    #[pallet::call]
    impl&lt;T: Config&gt; Pallet&lt;T&gt; {

        // 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&lt;T: Config&gt; Pallet&lt;T&gt; {
        // TODO Part III: helper functions for dispatchable functions

        // TODO: increment_nonce, random_hash, mint, transfer_from

    }
}</code></pre>
<ol start="2">
<li>우리의 팔렛에서 sp_io를 사용할 것이므로 Cargo.toml파일의 의존성에 이것이 추가되어있는지 확인</li>
</ol>
<pre><code class="language-rust">sp-io = { default-features = false, git = &quot;https://github.com/paritytech/substrate.git&quot;, branch = &quot;polkadot-v0.9.20&quot; }</code></pre>
<ol start="3">
<li>다음 커맨드로 팔렛 빌드해보기. 아직 Currency type을 구현하기 않았기 때문에 전체 체인은 빌드하지 않을 거임. 대신 팔렛의 에러가 있는지 없는지 확인하게 위해 빌드, unused import 에러는 무시해도 됨</li>
</ol>
<p>cargo build -p pallet-kitties</p>
<h3 id="storage-item-추가"><strong>storage item 추가</strong></h3>
<p>우리의 런타임에 변수를 저장하는 함수 로직을 추가해보자</p>
<p>storage macro에 의존하는 trait인 섭스트레이트 storage API에 있는 StorageValue를 사용할 것
우리가 선언하고 싶은 어떤 스토리지 아이템에 대해서 우리는 #[pallet::storage] 매크로를 먼저 써줘야 함</p>
<p>pallet/kitties/src/lib.rs 파일에서 Action 부분을 다음으로 교체</p>
<pre><code class="language-rust">#[pallet::storage]
#[pallet::getter(fn count_for_kitties)]
pub(super) type CountForKitties&lt;T: Config&gt; = StorageValue&lt;_, u64, ValueQuery&gt;;</code></pre>
<p>: 이것으로 총 존재하는 키티들의 개수를 우리의 팔렛에 추적하기 위한 스토리지 아이템을 생성</p>
<h3 id="currency-구현-추가"><strong>currency 구현 추가</strong></h3>
<p>우리의 노드를 만들기 전에, currency type을 팔렛 런타임 구현에 추가해야 함</p>
<p>runtime/src/lib.rs 에 다음 부분을 추가</p>
<pre><code class="language-rust">impl pallet_kitties::Config for Runtime {
    type Event = Event;
    type Currency = Balances; // &lt;-- Add this line
}</code></pre>
<p>이제 진짜로 노드를 빌드하고 에러가 없는지 확인하기
cargo build --release</p>
<h2 id="3-유일성-커스텀-타입-스토리지-맵">3. 유일성, 커스텀 타입, 스토리지 맵</h2>
<p>pallet/kitties/src/lib.rs 파일을 다음 코드로 교체</p>
<pre><code class="language-rust">#![cfg_attr(not(feature = &quot;std&quot;), 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 = &quot;std&quot;)]
    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&lt;T&gt;(_);

    /// 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&#39;s definition of an event.
        type Event: From&lt;Event&lt;Self&gt;&gt; + IsType&lt;&lt;Self as frame_system::Config&gt;::Event&gt;;

        /// The Currency handler for the Kitties pallet.
        type Currency: Currency&lt;Self::AccountId&gt;;

        // ACTION #5: Specify the type for Randomness we want to specify for runtime.

        // ACTION #9: Add MaxKittyOwned constant
    }

    // Errors.
    #[pallet::error]
    pub enum Error&lt;T&gt; {
        // TODO Part III
    }

    // Events.
    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event&lt;T: Config&gt; {
        // TODO Part III
    }

    #[pallet::storage]
    #[pallet::getter(fn count_for_kitties)]
    pub(super) type CountForKitties&lt;T: Config&gt; = StorageValue&lt;_, u64, ValueQuery&gt;;

    // ACTION #7: Remaining storage items.

    // TODO Part IV: Our pallet&#39;s genesis configuration.

    #[pallet::call]
    impl&lt;T: Config&gt; Pallet&lt;T&gt; {

        // 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&lt;T: Config&gt; Pallet&lt;T&gt; {

        // 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
    }
}</code></pre>
<p>serde를 팔렛의 Cargo.toml파일에 추가하기
맞는 버전 찾아서!!</p>
<p><strong>kitty struct 구조</strong></p>
<p>하나의 Kitty가 가지고 있어야 하는 정보</p>
<ul>
<li>dna : 고양이의 유니크한 속성인 dna를 식별하는데 사용되는 해시값. 새 고양이를 기르거나 다른 고양이 세대를 추적하는 데에도 사용됨 =&gt; [u8;16]</li>
<li>price : 주인이 설정한, 고양이를 사는 데 필요한 금액 =&gt; BalanceOf FRAME의 Currency trait에 있는 커스텀 타입</li>
<li>gender : Male or Female인 enum =&gt; Gender 타입 ! 곧 만들것임</li>
<li>owner : 한 명의 주인을 가리키는 계정 id</li>
</ul>
<p>구조체 선언하기 전에 BalanceOf와 AccountOf를 추가해줘야 함</p>
<p>Action1 부분을 다음으로 교체</p>
<pre><code class="language-rust">type AccountOf&lt;T&gt; = &lt;T as frame_system::Config&gt;::AccountId;
type BalanceOf&lt;T&gt; =
    &lt;&lt;T as Config&gt;::Currency as Currency&lt;&lt;T as frame_system::Config&gt;::AccountId&gt;&gt;::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&lt;T: Config&gt; {
    pub dna: [u8; 16],
    pub price: Option&lt;BalanceOf&lt;T&gt;&gt;,
    pub gender: Gender,
    pub owner: AccountOf&lt;T&gt;,
}</code></pre>
<p>이 파일 맨 위에 
use scale_info::TypeInfo;
이 줄 추가해서 우리의 구조체가 이 trait에 접근할 수 있도록 해주기</p>
<p><strong>Gender 커스텀 타입 작성</strong></p>
<ol>
<li><p>Male, Female 값을 갖는 enum 선언
Action item 2 부분 다음 코드로 교체</p>
<pre><code class="language-rust">#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = &quot;std&quot;, derive(Serialize, Deserialize))]
pub enum Gender {
 Male,
 Female,
}</code></pre>
<p>enum 선언하기 전에 dereive 매크로 써줘야 함
우리 런타임의 다른 타입들과 소통하려면 이렇게 감싸줘야 함?</p>
</li>
<li><p>kitty struct에 helper function 구현하기
Gender type을 리턴하고 랜덤으로 Gender enum 값을 고르기 위해 랜덤 함수를 사용하는 gen_gender라는 퍼블릭 함수를 만들자</p>
</li>
</ol>
<p>Action4를 다음 코드로 교체</p>
<pre><code class="language-rust">fn gen_gender() -&gt; Gender {
    let random = T::KittyRandomness::random(&amp;b&quot;gender&quot;[..]).0;
    match random.as_ref()[0] % 2 {
        0 =&gt; Gender::Male,
        _ =&gt; Gender::Female,
    }
}</code></pre>
<p><strong>on-chain 무작위성 구현</strong>
위에서 아직 정의하지 않은 KittyRandomness라는 걸 사용했음 이제 알아볼까
frame_support에 있는 Randomness trait을 사용할 것</p>
<ol>
<li>팔렛의 Configuration trait에서, Randomness trait에 바운드된 새로운 타입 정의
Randomness trait은 output과 blocknumber 제네릭을 교체하기 위해 인자로 구체화해주는 게 필요함
우리의 경우,  이 trait을 사용해서 함수듸 결과값이 Blake2 128-bit hash가 되길 원함
ACtion 5를 다음으고 교체<pre><code class="language-rust">type KittyRandomness: Randomness&lt;Self::Hash, Self::BlockNumber&gt;;</code></pre>
</li>
<li>런타임에서 실제 타입을 구체화하기
팔렛 설정할 때 새 타입 추가한 거에, 구체적인 타입을 설정하기 위해 우리 런타임을 설정해줘야 함
노드 템플릿이 이미 RandomnessCollectiveFilp 팔렛을 가지고 있기 때문에 runtime/src/lib.rs 팡리 안에 KittyRandomness 타입을 선언해주기만 하면 됨<pre><code class="language-rust">impl pallet_kitties::Config for Runtime {
 type Event = Event;
 type Currency = Balances;
 type KittyRandomness = RandomnessCollectiveFlip; // &lt;-- ACTION: add this line.
}</code></pre>
</li>
</ol>
<ol start="3">
<li>랜덤 DNA 생성하기
같은 블록에서 한 번 이상 이 함수를 호출할 때 다른 해시값을 얻기 위해 extrinsic__index를 사용할 것이고 blake_128도 사용할 것
ACtion6번째 줄을 다음으로 교체</li>
</ol>
<pre><code class="language-rust">fn gen_dna() -&gt; [u8; 16] {
    let payload = (
        T::KittyRandomness::random(&amp;b&quot;dna&quot;[..]).0,
        &lt;frame_system::Pallet&lt;T&gt;&gt;::extrinsic_index().unwrap_or_default(),
        &lt;frame_system::Pallet&lt;T&gt;&gt;::block_number(),
    );
    payload.using_encoded(blake2_128)
}</code></pre>
<p><strong>남은 스토리지 아이템들 작성</strong>
모든 고양이들을 쉽게 추적하기 위해서 유일한 아이디를 우리의 스토리지 아이템의 글로벌 키로 사용하는 로직을 표준화할 것임</p>
<p>새 고양이의 아이디는 항상 유일해야 함
해시값을 아이디로 갖고 그에 대한 고양이 오브젝트를 맵핑하는 Kitties 스토리지 아이템을 사용할 것임</p>
<p>Action 7을 교체</p>
<p>런타임은
Kitties라는 스토리지 맵으로 유일한 자산인 currency나 kitties</p>
<pre><code class="language-rust">#[pallet::storage]
#[pallet::getter(fn kitties)]
pub(super) type Kitties&lt;T: Config&gt; = StorageMap&lt;
    _,
    Twox64Concat,
    T::Hash,
    Kitty&lt;T&gt;,
&gt;;</code></pre>
<p>그 자산들의 소유권인 계정 아이디를 KittiesOwned라는 스토리지 맵으로 다룸</p>
<pre><code class="language-rust">#[pallet::storage]
#[pallet::getter(fn kitties_owned)]
/// Keeps track of what accounts own what Kitty.
pub(super) type KittiesOwned&lt;T: Config&gt; = StorageMap&lt;
    _,
    Twox64Concat,
    T::AccountId,
    BoundedVec&lt;T::Hash, T::MaxKittyOwned&gt;,
    ValueQuery,
&gt;;
</code></pre>
<p>FRAME이 제공하는 StorageMap으로 Kitty 구조체의 스토리지 인스턴스 생성할 것</p>
<p>ACtion 9를 다음 줄로 교체</p>
<pre><code class="language-rust">#[pallet::constant]
type MaxKittyOwned: Get&lt;u32&gt;;</code></pre>
<p>MaxKittyowned라는 타입을 config trait에 추가</p>
<pre><code class="language-rust">parameter_types! {              // &lt;- 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; // &lt;- add this line
}</code></pre>
<p>이 코드를 추가해라~~</p>
<p>컴파일 되는지 확인!
cargo build --release</p>
<h2 id="4-디스패쳐블-이벤트-에러">4. 디스패쳐블, 이벤트, 에러</h2>
<ul>
<li>create_kitty : 한 계정이 고양이 한 마리를 민팅하게 허락하는 dispatchable 또는 publicly callable함수</li>
<li>mint : 우리 팔렛의 스토리지 아이템을 업데이트하고 에러를 체크하는 create_kitty에 의해 호출되는 도우미 함수</li>
<li>Events: FRAME의 #[pallet::event] attribute 사용</li>
</ul>
<p><strong>Public and Private 함수들</strong>
최적화를 위해 복잡한 로직을 private 도우미 함수로 쪼갤 것
private함수는 다양한 dispatchavle함수들에 의해 호출 가능</p>
<ul>
<li><p>create_kitty는 dispatchable 함수 또는 extrinsic임: origin이 sign받았는지 확인, 사인한 계정으로 랜덤 해시 생성, 그걸로 새로운 고양이 오브젝트 만들고 private mint()함수 호출</p>
</li>
<li><p>mint는 private helper function: 그 고양이가 이미 존재하지 않는지 확인, 새 고양이 아이디로 스토리지 업데이트, 스토리지에 있는 새 총 고양이 개수와 새 주인의 계정 업데이트, 고양이가 성공적으로 만들어졌다ㅡㄴ 신호에 대한 이벤트 쌓기</p>
</li>
</ul>
<p><strong>create_kitty dispatchable 작성하기</strong></p>
<p>FRAME에서 dispatchable은 항상 같은 구조임</p>
<pre><code>모든 팔렛 dispatchables는 #[pallet::call] 매크로 아래에 있고, impl&lt;T: Config&gt;Pallet&lt;T&gt; {}로 선언되어야 함</code></pre><ul>
<li>weights : 팔렛::call 의 요구사항에 따라, 모든 디스패쳐블 함수들은 연관 무게를 가져야 함 무게는 실행 시간에 블록을 맞추려고 계산의 양에 대해 세이프가드를 제공하기 때문에 서브스트레이트에서 개발하는 데 중요한 부분임</li>
</ul>
<p>서브스트레이트의 weighting system은 개발자들이 연산 복잡도를 매 extrinsic에 대해 생각하도록 강제한다. 그래서 노드가 최악의 실행시간을 가정하게 하고 구체적인 블록 시간보다 더 오래 걸리는 extrinsic들과 네트워킹하는 것을 피하게 한다. 무게는 어떠한 사인된 extrinsic에 대해서도 수수료 시스템과도 내부적으로 연결되어 있음</p>
<p>pallets/kitties/src/lib.rs에서 action1을 다음으로 교체</p>
<pre><code class="language-rust">  #[pallet::weight(100)]
pub fn create_kitty(origin: OriginFor&lt;T&gt;) -&gt; DispatchResult {
    let sender = ensure_signed(origin)?; // &lt;- add this line
    let kitty_id = Self::mint(&amp;sender, None, None)?; // &lt;- add this line
    // Logging to the console
    log::info!(&quot;A kitty is born with ID: {:?}.&quot;, kitty_id); // &lt;- add this line

    // ACTION #4: Deposit `Created` event

    Ok(())
}                                                             </code></pre>
<p>로깅하기 위해서 맞는 버전의 log::info Cargo.toml파일에 추가하기</p>
<p><strong>mint() 함수 작성하기</strong></p>
<ul>
<li>owner: &amp;T::AccountId, 고양이 주인</li>
<li>dna: Option&lt;[u8;16]&gt;, 만약 None이면 랜덤 DNA 생성됨</li>
<li>gender: Option<Gender> <blockquote>
<p>Result&lt;T::Hash, Error&lt;\T&gt;&gt;를 리턴</p>
</blockquote>
</li>
</ul>
<p>Action 2부분을 다음으로 교체</p>
<pre><code class="language-rust">  // Helper to mint a Kitty.
pub fn mint(
    owner: &amp;T::AccountId,
    dna: Option&lt;[u8; 16]&gt;,
    gender: Option&lt;Gender&gt;,
) -&gt; Result&lt;T::Hash, Error&lt;T&gt;&gt; {
    let kitty = Kitty::&lt;T&gt; {
        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(&amp;kitty);

    // Performs this operation first as it may fail
    let new_cnt = Self::count_for_kitties().checked_add(1)
        .ok_or(&lt;Error&lt;T&gt;&gt;::CountForKittiesOverflow)?;

    // Check if the kitty does not already exist in our storage map
    ensure!(Self::kitties(&amp;kitty_id) == None, &lt;Error&lt;T&gt;&gt;::KittyExists);

    // Performs this operation first because as it may fail
    &lt;KittiesOwned&lt;T&gt;&gt;::try_mutate(&amp;owner, |kitty_vec| {
        kitty_vec.try_push(kitty_id)
    }).map_err(|_| &lt;Error&lt;T&gt;&gt;::ExceedMaxKittyOwned)?;

    &lt;Kitties&lt;T&gt;&gt;::insert(kitty_id, kitty);
    &lt;CountForKitties&lt;T&gt;&gt;::put(new_cnt);
    Ok(kitty_id)
}</code></pre>
<p>먼저 새 고양이 오브젝트를 생성
유일한 kitty_id를 고양이의 현재 특성에 기초한 해시 함수를 사용해 생성
CountForKitties를 스토리지 게터함수인 Self::count_for_kitties()를 사용해 증가시키고
check_add()함수로 오버플로우 확인
kitty_id가 이미 존재하지는 않는지 검사  </p>
<p>검사 다 통과하면 스토리지 아이템을 업데이트:
  try_mutate로 고양이 주인 벡터 업데이트
  섭스트레이트 스토리지 맵 API가 제공하는 insert 메소드로 실제 고양이 오브젝트를 저장하고 kitty_id와 연관시키기
  StorageValue API에 의해 제공된 put으로 최신 고양이 개수 저장</p>
<p><strong>pallet Events 구현하기</strong>
우리 팔렛은 함수 마지막에 이벤트도 발생시킬 수 있음
  FRAME의 #[pallet::event]를 통해서 쉽게 팔렛의 이벤트를 관리하고 선언할 수 있음, FRAME 매크로로 이벤트를 enum 선언처럼 할 수 있음</p>
<pre><code class="language-rust">  #[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event&lt;T: Config&gt;{
    /// A function succeeded. [time, day]
    Success(T::Time, T::Day),
}</code></pre>
<p>attribute macro인 #[pallet::generate_deposit(pub(super) fn deposit_event)]을 사용할 수 있음</p>
<p>  이를 통해 특정 이벤트를 적립할 수 있음, ㅇSelf::deposit_event(Event::Success(var_time, var_day));
  이런 패턴을 통해서!</p>
<p>우리 팔렛의 설정 trait Config 안에다 새로운 연관 타입인 Event를 추가해줘야 함
  그리고 runtime/src/lib.rs에도 추가해줘야 함
``rust<br>/// 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&#39;s definition of an event.
    type Event: From&lt;Event<Self>&gt; + IsType&lt;<Self as frame_system::Config>::Event&gt;;
    //--snip--//
}  </p>
<pre><code>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&lt;BalanceOf&lt;T&gt;&gt;),
/// 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&lt;T&gt;),</code></pre><p>Action 4를 다음으로 교체해서 완성해라
  Self::deposit_event(Event::Created(sender, kitty_id));</p>
<p><strong>에러 처리</strong>
FRAME을 통해 [#pallet::error]로 우리 팔렛에 에러를 구체화하고 팔렛 함수 안에서 사용할 수 있게 해줌
  #[pallet::error]아래에 FRAME 매크로를 사용해서 가능한 모든 에러를 Action5줄로 교체</p>
<pre><code class="language-rust">/// 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&#39;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,
</code></pre>
<p>다 잘 빌드되는지 확인
  cargo build --release</p>
<p><strong>Polkadat-JS Apps UI로 테스트하기</strong>
루트 디렉토리에서 다음 실행
./target/release/node-kitties --tmp --dev</p>
<p>Developer에서 extrinsc으로 가서 사인된 extrincic을 substrateKitties를 사용해서 사인하고 제출하는데, createKitty 디스패쳐블 호출해서 해라. 앨리스, 밥, 찰리의 계정으로 해봐라</p>
<p>  이벤트로 가서 Created찾고 블록 디테일 이벤트 실행된 거 확인가능해야 함</p>
<p>  chain state가서 substrateKitties팔렛 가서 Kitties(hash)Kitty 쿼리 실행해서 디테일 확인
  include option체크 안 한 상태에서만 다음 정보 볼 수 있음</p>
<h2 id="5-kittiy들과-상호작용하기">5. Kittiy들과 상호작용하기</h2>
<p><strong>고양이 가격 설정</strong></p>
<ul>
<li><p>고양이 주인 확인 : 스토리지에 있는 오브젝트들을 수정하는 함수들을 만들면서 오직 적절한 사용자들만 성공적으로 그 디스패쳐블 ㅅ함수들의 로직을 실행할 수 있는지를 확인해야 함</p>
<p>Action #1a에 붙여넣기
ensure!(Self::is_kitty_owner(&amp;kitty_id, &amp;sender)?, &lt;Error<T>&gt;::NotKittyOwner);</p>
<p>Action #1b에 붙여넣기
pub fn is_kitty_owner(kitty_id: &amp;T::Hash, acct: &amp;T::AccountId) -&gt; Result&lt;bool, Error<T>&gt; {
match Self::kitties(kitty_id) {</p>
<pre><code>Some(kitty) =&gt; Ok(kitty.owner == *acct),
None =&gt; Err(&lt;Error&lt;T&gt;&gt;::KittyNotExist)</code></pre><p>}
}</p>
</li>
<li><p>우리 가격 업데이트: 스토리지에서 고양이 오브젝트 가져와서 새 가격으로 업데이트하고 다시 스토리지에 저장
ACtion2다음으로 교체</p>
<pre><code class="language-rust">kitty.price = new_price.clone();
&lt;Kitties&lt;T&gt;&gt;::insert(&amp;kitty_id, kitty);
</code></pre>
</li>
<li><p>이벤트 적립하기: 다 성공적으로 이뤄지면 이벤트 적립 가능 ACtion 3 다음으로 교체</p>
<pre><code class="language-rust">// Deposit a &quot;PriceSet&quot; event.
Self::deposit_event(Event::PriceSet(sender, kitty_id, new_price));</code></pre>
</li>
</ul>
<p><strong>고양이 교환</strong>
  transfer(): 디스패쳐블 함수, publickly 호출가능하 다스패처블
  transfer_kitty_to() : private helper function. transfer사 호출, 고양이 교환할 때 모든 스토리지 업데이트 담당</p>
<p>  action 4</p>
<pre><code class="language-rust">  #[pallet::weight(100)]
pub fn transfer(
    origin: OriginFor&lt;T&gt;,
    to: T::AccountId,
    kitty_id: T::Hash
) -&gt; DispatchResult {
    let from = ensure_signed(origin)?;

    // Ensure the kitty exists and is called by the kitty owner
    ensure!(Self::is_kitty_owner(&amp;kitty_id, &amp;from)?, &lt;Error&lt;T&gt;&gt;::NotKittyOwner);

    // Verify the kitty is not transferring back to its owner.
    ensure!(from != to, &lt;Error&lt;T&gt;&gt;::TransferToSelf);

    // Verify the recipient has the capacity to receive one more kitty
    let to_owned = &lt;KittiesOwned&lt;T&gt;&gt;::get(&amp;to);
    ensure!((to_owned.len() as u32) &lt; T::MaxKittyOwned::get(), &lt;Error&lt;T&gt;&gt;::ExceedMaxKittyOwned);

    Self::transfer_kitty_to(&amp;kitty_id, &amp;to)?;

    Self::deposit_event(Event::Transferred(from, to, kitty_id));

    Ok(())
}</code></pre>
<ul>
<li><p>트랜잭션 사인됐나 확인하고</p>
</li>
<li><p>고양이 주인이 보냈나 확인하고
주인에게 보내는 거 아닌가 확인하고
받는 사람이 하나 이상의 고양이 가질 수 있는지 확인하고
transfer_kitty_to함수 실행</p>
<p>Action5</p>
<pre><code class="language-rust">#[transactional]
pub fn transfer_kitty_to(
kitty_id: &amp;T::Hash,
to: &amp;T::AccountId,
) -&gt; Result&lt;(), Error&lt;T&gt;&gt; {
let mut kitty = Self::kitties(&amp;kitty_id).ok_or(&lt;Error&lt;T&gt;&gt;::KittyNotExist)?;

let prev_owner = kitty.owner.clone();

// Remove `kitty_id` from the KittiesOwned vector of `prev_owner`
&lt;KittiesOwned&lt;T&gt;&gt;::try_mutate(&amp;prev_owner, |owned| {
    if let Some(ind) = owned.iter().position(|&amp;id| id == *kitty_id) {
        owned.swap_remove(ind);
        return Ok(());
    }
    Err(())
}).map_err(|_| &lt;Error&lt;T&gt;&gt;::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;

&lt;Kitties&lt;T&gt;&gt;::insert(kitty_id, kitty);

&lt;KittiesOwned&lt;T&gt;&gt;::try_mutate(to, |vec| {
    vec.try_push(*kitty_id)
}).map_err(|_| &lt;Error&lt;T&gt;&gt;::ExceedMaxKittyOwned)?;

Ok(())
}</code></pre>
<p>#[transactional]은 오직 ok를 리턴할 때만 스토리지 바꿀 수 있도록 해줌</p>
</li>
</ul>
<p><strong>고양이 구매</strong></p>
<p>그 고양이가 판매중인지 확인
  현재가격이 사용자의 예산에 있는지와 충분한 free balance를 가지고 있는지 확인
  ACtion 6 교체</p>
<pre><code class="language-rust">  // Check the kitty is for sale and the kitty ask price &lt;= bid_price
if let Some(ask_price) = kitty.price {
    ensure!(ask_price &lt;= bid_price, &lt;Error&lt;T&gt;&gt;::KittyBidPriceTooLow);
} else {
    Err(&lt;Error&lt;T&gt;&gt;::KittyNotForSale)?;
}

// Check the buyer has enough free balance
ensure!(T::Currency::free_balance(&amp;buyer) &gt;= bid_price, &lt;Error&lt;T&gt;&gt;::NotEnoughBalance);</code></pre>
<p> 고ㅑㅇ이 받을 공간 있나 확인</p>
<p>  Action7다음으로 교체</p>
<pre><code class="language-rust">  // Verify the buyer has the capacity to receive one more kitty
let to_owned = &lt;KittiesOwned&lt;T&gt;&gt;::get(&amp;buyer);
ensure!((to_owned.len() as u32) &lt; T::MaxKittyOwned::get(), &lt;Error&lt;T&gt;&gt;::ExceedMaxKittyOwned);

let seller = kitty.owner.clone();</code></pre>
<p>  Action 8
 // Transfer the amount from buyer to seller
T::Currency::transfer(&amp;buyer, &amp;seller, bid_price, ExistenceRequirement::KeepAlive)?;</p>
<p>// Transfer the kitty from seller to buyer
Self::transfer_kitty_to(&amp;kitty_id, &amp;buyer)?;</p>
<p>// Deposit relevant Event
Self::deposit_event(Event::Bought(buyer, seller, kitty_id, bid_price)); </p>
<p><strong>고양이 기르기</strong>
  ACtion 9
  let new_dna = Self::breed_dna(&amp;parent1, &amp;parent2)?;</p>
<p>  ACtino 10
  Self::mint(&amp;sender, Some(new_dna), None)?;</p>
<p><strong>제네시스 설정</strong>
  초기 상태 설정해주기
  FRAME&#39;s #[pallet::genesis_config] 사용
  제네시스 블록에 어떤 고양이 오브젝트 선언하도록 허용?</p>
<p>  ACtion 11
  // Our pallet&#39;s genesis configuration.
#[pallet::genesis_config]
pub struct GenesisConfig&lt;T: Config&gt; {
    pub kitties: Vec&lt;(T::AccountId, [u8; 16], Gender)&gt;,
}</p>
<p>// Required to implement default for GenesisConfig.
#[cfg(feature = &quot;std&quot;)]
impl&lt;T: Config&gt; Default for GenesisConfig<T> {
    fn default() -&gt; GenesisConfig<T> {
        GenesisConfig { kitties: vec![] }
    }
}</p>
<p>#[pallet::genesis_build]
impl&lt;T: Config&gt; GenesisBuild<T> for GenesisConfig<T> {
    fn build(&amp;self) {
        // When building a kitty from genesis config, we require the dna and gender to be supplied.
        for (acct, dna, gender) in &amp;self.kitties {
            let _ = &lt;Pallet<T>&gt;::mint(acct, Some(dna.clone()), Some(gender.clone()));
        }
    }
}</p>
<p>  node/src/chain_spec.rs로 가서 맨 위에 use node_kitties_runtime::SubstrateKittiesConfig; 추가하고 testnet_genesis함수에 추가
  substrate_kitties: SubstrateKittiesConfig {
    kitties: vec![],
},</p>
<p><strong>빌드, 실행, 상호작용하기</strong>
  cargo build --release
./target/release/node-kitties --dev --tmp</p>
<p>  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</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rust 7 ]]></title>
            <link>https://velog.io/@myungha_cho/Rust-7</link>
            <guid>https://velog.io/@myungha_cho/Rust-7</guid>
            <pubDate>Sun, 08 May 2022 11:33:29 GMT</pubDate>
            <description><![CDATA[<h1 id="packages-crates-modules">Packages, Crates, Modules</h1>
<h2 id="1-패키지와-크레이트">1. 패키지와 크레이트</h2>
<p>크레이트는 바이너리/라이브러리
패키지는 여러 기능을 제공하는 하나 이상의 크레이트
패키지는 최대 하나의 라이브러리 크레이트를 가질 수 있다.
패키지는 여러 바이너리 크레이트를 가질 수 있지만, 최소 하나의 바이너리 크레이트는 가지고 있어야 함</p>
<p>바이너리 1개만 존재 : src/main.rs
라이브러리 1개/바이너리 1개 : src/main.rs, src/lib.rs
라이브러리 1개/바이너리 여러 개 : src.bin/..., 
각 바이너리 크레이트들으 src/bin 디렉토리 안에 두여야 함</p>
<h2 id="2-모듈">2. 모듈</h2>
<p>모듈은 크레이트 안에서 코드를 그룹별로 정리하게 쉽게 해줌
각 항목들이 pulbic이어도 되는지, private이어야 하는지 정리 가능</p>
<p>mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}</p>
<pre><code>    fn seat_at_table() {}
}

mod serving {
    fn take_order() {}

    fn serve_order() {}

    fn take_payment() {}
}</code></pre><p>}</p>
<p>모듈 트리</p>
<p>crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment</p>
<h2 id="3-path">3. path</h2>
<p>모듈 트리에서 내가 원하는 항목 찾기 위해 path 경로를 사용한다.</p>
<ul>
<li>절대 경로 : crate에서 시작</li>
<li>상대 경로 : 이름으로 시작하는 건 상대경로 의미</li>
</ul>
<p>기본적으로 모든 아이템이 private 
부모 모듈에 ㅇㅆ는 항목들은 자식 모듈에 이쓴 private항목을 사용하지 못함</p>
<p>자식 모듈에 이쓴 항목들은 부모 모듈에 이쓴 항속 사용 ㅎ가능</p>
<p>자식 모듈에 이쓴 거를 공개하고 싶을 때 앞에 pub 키워드 붙임</p>
<p>mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}</p>
<p>pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();</p>
<pre><code>// Relative path
front_of_house::hosting::add_to_waitlist();</code></pre><p>}</p>
<p>super 키워드를 사용해서 상대 경로 쉽게 적을 수 있음</p>
<p>struct로 만들면 기본적으로 private이지만
enum으로 만들면 기본적으로 public이다</p>
<h2 id="4-use">4. use</h2>
<p>use 키워드로 경로 짧게 표현 가능
함수를 불러올 때는 부모에서부터 가졍ㅁ
struct, enum 다른ㄴ 아이템ㅇ들은절대경로로 가져옴
이름이 같은 항목이 있으면 부모 모듈로 가져옴</p>
<p>아니면 이름이 같으면 as로 다른 이름 붙여도 됨</p>
<p>pub use</p>
<p>mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}</p>
<p>pub use crate::front_of_house::hosting;</p>
<p>pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}</p>
<p>listing them in your package’s Cargo.toml file and using use to bring items from their crates into scope</p>
<p>짧은 경로로 내가 선언한 함수 호출 가능</p>
<p>use std::{cmp::Ordering, io};
use std::io::{self, Write};
use std::collections::*;</p>
<h2 id="5-다른-파일로-모듈-분리하기">5. 다른 파일로 모듈 분리하기</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rust 6]]></title>
            <link>https://velog.io/@myungha_cho/Rust-6</link>
            <guid>https://velog.io/@myungha_cho/Rust-6</guid>
            <pubDate>Sun, 08 May 2022 07:42:09 GMT</pubDate>
            <description><![CDATA[<ol start="6">
<li>Enums and Pattern Matching<h2 id="enum">enum</h2>
<pre><code class="language-rust">enum IpAddrKind {
 V4,
 V6,
}
</code></pre>
</li>
</ol>
<p>fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;</p>
<pre><code>route(IpAddrKind::V4);
route(IpAddrKind::V6);</code></pre><p>}</p>
<p>fn route(ip_kind: IpAddrKind) {}</p>
<pre><code>

```rust

struct IpAddr {
        kind: IpAddrKind,
        address: String,
    }

let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from(&quot;127.0.0.1&quot;),
};

let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from(&quot;::1&quot;),
};


fn main() {
    enum IpAddr {
        V4(String),
        V6(String),
    }

    let home = IpAddr::V4(String::from(&quot;127.0.0.1&quot;));

    let loopback = IpAddr::V6(String::from(&quot;::1&quot;));
}</code></pre><p>the name of each enum variant that we define also becomes a function that constructs an instance of the enum. That is, IpAddr::V4() is a function call that takes a String argument and returns an instance of the IpAddr type. We automatically get this constructor function defined as a result of defining the enum.</p>
<p>enum안에는 어떤 자료형이든지 넣을 수 있다</p>
<pre><code class="language-rust">enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {}
</code></pre>
<pre><code class="language-rust">struct QuitMessage; // unit struct
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct

fn main() {}
</code></pre>
<p>이렇게 struct로 선언할 수도 있지만, 그러면 enum처럼 이것들을 다 하나의 타입으로 받는 함수를 만들기가 쉽지가 않음</p>
<p>impl 키워드로 enum에도 메소드 정의가능(sturct와 공통점)</p>
<pre><code class="language-rust">fn main() {
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }

    impl Message {
        fn call(&amp;self) {
            // method body would be defined here
        }
    }

    let m = Message::Write(String::from(&quot;hello&quot;));
    m.call();
}
</code></pre>
<p><strong>Option Enum</strong>
표준라이브러리에 정의된 enum</p>
<pre><code class="language-rust">
fn main() {
    enum Option&lt;T&gt; {
        None,
        Some(T),
    }

    let some_number = Some(5);
    let some_string = Some(&quot;a string&quot;);

    let absent_number: Option&lt;i32&gt; = None;

}</code></pre>
<pre><code class="language-rust">fn main() {
    let x: i8 = 5;
    let y: Option&lt;i8&gt; = Some(5);

    let sum = x + y;
}</code></pre>
<p>In short, because Optio&lt;\T&gt; and T (where T can be any type) are different types, the compiler won’t let us use an Option<T> value as if it were definitely a valid value. For example, this code won’t compile because it’s trying to add an i8 to an Option<i8> i8과 Option<i8>은 서로 다른 타입으로 인식해서 컴파일 안 됨</p>
<h2 id="match">match</h2>
<p>  Patterns can be made up of literal values, variable names, wildcards, and many other things
  패턴에 따라 다른 코드블록 실행시켜줌</p>
<pre><code class="language-rust">  enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -&gt; u8 {
    match coin {
        Coin::Penny =&gt; 1,
        Coin::Nickel =&gt; 5,
        Coin::Dime =&gt; 10,
        Coin::Quarter(state) =&gt; {
            println!(&quot;State quarter from {:?}!&quot;, state);
            25
        }
    }

}

fn main() {}
</code></pre>
<p>  러스트에서 match는 모든 가능한 경우를 다 다뤄줘야 함</p>
<p>  fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 =&gt; add_fancy_hat(),
        7 =&gt; remove_fancy_hat(),
        other =&gt; move_player(other),
    }</p>
<pre><code>  match dice_roll {
    3 =&gt; add_fancy_hat(),
    7 =&gt; remove_fancy_hat(),
    _ =&gt; reroll(),
       _ =&gt; (),
}

fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}</code></pre><p>}</p>
<p>  catch-all 은 맨 마지막에 써야 함
  위처럼 함수로 인자 받기도 싫으면 _, 사용하면 됨</p>
<h2 id="if-let">if let</h2>
<p>하나의 패턴에만 코드 실행하고 나머지는 무시하고 싶을 때 사용</p>
<p>fn main() {
    let coin = Coin::Penny;
    let mut count = 0;
    if let Coin::Quarter(state) = coin {
        println!(&quot;State quarter from {:?}!&quot;, state);
    } else {
        count += 1;
    }
}</p>
]]></description>
        </item>
    </channel>
</rss>