<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>na_treeeum.log</title>
        <link>https://velog.io/</link>
        <description>BlockChain DEV</description>
        <lastBuildDate>Sun, 26 Jul 2026 08:33:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>na_treeeum.log</title>
            <url>https://velog.velcdn.com/images/na_treeeum/profile/7477c3f5-894f-475f-8558-5d1296030c48/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. na_treeeum.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/na_treeeum" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Scafonix (Serverless MPC) 연동기]]></title>
            <link>https://velog.io/@na_treeeum/Scafonix-Serverless-MPC-%EC%97%B0%EB%8F%99%EA%B8%B0</link>
            <guid>https://velog.io/@na_treeeum/Scafonix-Serverless-MPC-%EC%97%B0%EB%8F%99%EA%B8%B0</guid>
            <pubDate>Sun, 26 Jul 2026 08:33:11 GMT</pubDate>
            <description><![CDATA[<h1 id="scafonixserverless-mpc로-지갑-구현하기">Scafonix(Serverless MPC)로 지갑 구현하기</h1>
<p>제가 지금껏 사용해온 Web2 서비스에서의 이탈 지점은 보통 &quot;필요한 서비스가 아니어서&quot;, &quot;재미가 없어서&quot; 같은 이유였습니다. &quot;로그인이 어려워서&quot; 서비스를 그만둔 적은 거의 없었던 것 같습니다.</p>
<p>하지만 Web3에서는 이게 충분히 가능한 이탈 사유가 됩니다. 메타마스크 설치, 네트워크 추가, 시드 문구 백업, 가스비 준비까지 요구하면 사용자는 서비스에 들어오기도 전에 지칩니다. 그렇다고 서버가 개인키를 대신 보관하는 Custodial 방식은 보안적 책임이 너무 큽니다.</p>
<p>이번에는 Scafonix MPC 서비스를 사용해 Google 소셜 로그인만으로 테스트넷 지갑을 만들고, 직접 배포한 <code>SimpleCounter</code> 컨트랙트를 호출해봤습니다. Scafonix는 개인키를 P1, P2, P3로 나누는 2-of-3 MPC 방식입니다. 완성된 개인키를 DB에 저장하거나 서버에서 복구한 뒤 서명하는 구조가 아니라, 브라우저의 보안 샌드박스 안에서 필요한 share를 조합해 서명을 수행합니다. 서비스 제공자 입장에서는 별도의 키 보관 DB나 서명 서버를 운영하지 않아도 된다는 점이 가장 크게 와닿았습니다.</p>
<blockquote>
<p>Scafonix Docs를 참고하여 이번 데모를 제작해 봤습니다. <a href="https://www.scafonix.com/docs">Scafonix Docs</a></p>
</blockquote>
<blockquote>
<p>Scafonix 에서는 AI Agent 가 활용할 수 있는 프롬프트도 지원하기 때문에 어렵지 않게 구현할 수 있습니다. <a href="https://www.scafonix.com/llms.txt">AI Assistant Docs</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/2c10ee5e-0b9c-4312-8bf6-141a36fef2cc/image.png" alt=""></p>
<h2 id="1-simplecounter">1. SimpleCounter</h2>
<p>테스트할 컨트랙트는 일부러 단순하게 만들었습니다. 사용자별 count만 저장하고, <code>getCount()</code>는 <code>msg.sender</code>의 count를 반환합니다. <code>count()</code>는 같은 <code>msg.sender</code>의 count를 1 증가시킵니다.</p>
<h2 id="2-google-로그인으로-지갑-연결">2. Google 로그인으로 지갑 연결</h2>
<p>아래는 Scafonix 연동데모를 위해 만든 간단한 페이지 입니다.
<img src="https://velog.velcdn.com/images/na_treeeum/post/b9926e82-4121-4274-8d4d-91d69a6b1d90/image.png" alt=""></p>
<p>클라이언트는 프레임워크 없이 HTML과 JS만 사용했습니다.
단 두줄(ethers 연동을 위한 코드라인을 빼면 단 한줄)만으로도 손쉽게 라이브러리를 불러와서 사용할 수 있습니다.</p>
<pre><code class="language-html">&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/ethers/6.13.1/ethers.umd.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://sdk.scafonix.com/sdk.js?v=2&quot;&gt;&lt;/script&gt;</code></pre>
<p>Google Login 버튼을 누르면 Google OAuth 팝업이 나와서 구글계정으로 손쉽게 로그인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/94b04a8a-105f-499b-a8bc-ac7f36d07fe1/image.png" alt=""></p>
<p>로그인이 완료되면 별다른 과정 없이 나의 지갑이 생성되었고, 생성된 지갑주소를 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/a20f0b58-5385-44d8-a7b0-64409c5e22a5/image.png" alt=""></p>
<p>Google Login만으로 Scafonix Wallet 연결까지 이어지는 간편한 UX가 구현됐습니다.</p>
<h2 id="3-getcount와-count-호출">3. getCount와 count 호출</h2>
<p><code>getCount()</code>는 view 함수라서 단순히 RPC call로 조회했습니다. 다만 컨트랙트가 <code>msg.sender</code> 기준으로 count를 저장하기 때문에, call을 보낼 때도 Scafonix 지갑 주소를 <code>from</code>으로 넣어 현재 로그인한 사용자의 값을 읽었습니다.</p>
<pre><code class="language-js">const value = await contract.getCount({ from: connectedEvmAddress });</code></pre>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/3d4c2be0-f682-4ceb-9d35-799c3d79493b/image.png" alt=""></p>
<p>반면 <code>count()</code>는 실제 상태를 바꾸는 함수라 트랜잭션 서명이 필요합니다. Scafonix를 통해 tx에 서명하고 broadcast되는 과정은 아래 4단계로 이해하면 쉽습니다.</p>
<ol>
<li><p>클라이언트에서 unsigned tx를 만든다<br>먼저 <code>to</code>, <code>data</code>, <code>chainId</code>, <code>nonce</code>, <code>gasLimit</code>, fee 값을 채워 아직 서명되지 않은 transaction object를 만듭니다. <code>SimpleCounter.count()</code>의 경우 <code>contract.count.populateTransaction()</code>으로 호출 data를 만들고, RPC에서 nonce와 gas 정보를 조회합니다.</p>
</li>
<li><p>Scafonix SDK에 서명을 요청한다<br>만든 tx object를 <code>Scafonix.signTransaction(connectedEvmAddress, tx)</code>로 넘깁니다. 여기서 넘기는 것은 개인키가 아니라 서명할 트랜잭션 데이터입니다. device에 저장된 키를 Scafonix 서버로 전송해서 복구하는 구조가 아닙니다.</p>
</li>
<li><p>SDK 보안 샌드박스에서 MPC 서명이 생성된다<br>Scafonix는 P1, P2, P3 중 필요한 share 조합으로 2-of-3 threshold signing을 수행합니다. 문서 기준으로 이 과정에서도 완성된 private key는 메모리 안에서 조립되지 않습니다. EVM의 경우 결과값은 <code>{ r, s, v }</code> 형태의 ECDSA signature입니다.</p>
</li>
<li><p>서명을 붙여 raw transaction을 만들고 RPC에 broadcast한다<br>반환받은 signature를 ethers의 <code>Transaction.from({ ...tx, signature }).serialized</code>로 붙이면 signed raw transaction이 됩니다. 이 raw transaction을 <code>provider.broadcastTransaction(signedTx)</code>로 네트워크에 전송합니다.</p>
</li>
</ol>
<pre><code class="language-js">const signature = await Scafonix.signTransaction(connectedEvmAddress, tx);
const signedTx = ethers.Transaction.from({ ...tx, signature }).serialized;
await provider.broadcastTransaction(signedTx);</code></pre>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/faf6daa5-a09c-4f76-97dd-d6233a75950b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/20bab9f6-02b6-4066-bc3c-f09828c2296c/image.png" alt=""></p>
<p>서비스 제공자가 유저의 개인키를 들고 있지 않은데도, 사용자는 소셜 로그인 이후 자연스럽게 온체인 트랜잭션을 만들 수 있습니다.</p>
<h2 id="4-백업과-복구">4. 백업과 복구</h2>
<p>P3는 사용자가 직접 보관하는 복구 share입니다. Google Drive나 copy key share 같은 옵션을 선택할 수 있습니다.
백업 모달은 <code>showBackupModal(userId)</code>로 열 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/8f5b0ee3-3fdd-4168-a9cf-2b7eecb8545e/image.png" alt=""></p>
<h2 id="privy와-비교하며-느낀-차이">Privy와 비교하며 느낀 차이</h2>
<p>Privy 같은 Embedded Wallet도 Web3 온보딩 문제를 잘 풀어주는 선택지입니다. 다만 Scafonix는 서비스 제공자 서버 및 DB에 키를 저장하지 않으며, 서명 서버를 두지 않는다는 점이 강점입니다. P1은 브라우저, P2는 소셜 인증 기반 share, P3는 유저가 보관하는 백업 share로 나뉘고, 2-of-3 구조에서 서명이 일어납니다.</p>
<p>물론 &quot;프론트엔드가 해킹돼도 무조건 안전하다&quot;는 뜻은 아닙니다. 악성 트랜잭션 승인, 피싱, 스크립트 공급망 공격은 여전히 별도로 방어해야 합니다. 다만 개인키 전체를 앱이나 서버 및 DB에 저장하지 않는다는 점은 확실히 운영 부담을 줄여줄 수 있다고 생각합니다.</p>
<h2 id="마무리">마무리</h2>
<p>이번 데모에서는 Scafonix를 연동하여 Google 로그인 한 번으로 지갑 생성부터 연결까지 하고, 직접 배포한 <code>SimpleCounter</code>의 <code>getCount()</code>와 <code>count()</code>까지 호출했습니다. 메타마스크 설치 없이도 온체인 액션을 만들 수 있었고, 서비스 제공자 입장에서는 키를 따로 보관하는 설계를 하지 않아도 되기 때문에 정책적 리스크를 줄일 수 있으며 개별 키 보관 위험도 줄어든다는 점이 좋았습니다. 또한 분리된 키를 따로따로 보관하기 때문에 단일 share 유출만으로는 서명이 불가능하다는 점도 기존 private key 단일 보관 방식보다 안전하게 느껴졌습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ERC-4337 Account Abstraction]]></title>
            <link>https://velog.io/@na_treeeum/ERC-4337-Account-Abstraction</link>
            <guid>https://velog.io/@na_treeeum/ERC-4337-Account-Abstraction</guid>
            <pubDate>Thu, 28 May 2026 09:39:51 GMT</pubDate>
            <description><![CDATA[<p><a href="https://eips.ethereum.org/EIPS/eip-4337">https://eips.ethereum.org/EIPS/eip-4337</a></p>
<hr>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/289e74ea-03be-489e-9f06-0e75a9626bfe/image.png" alt=""></p>
<h1 id="custodial-wallet-non-custodial-wallet">Custodial Wallet? Non-Custodial Wallet?</h1>
<p>web3 서비스를 이용하다보면 커스터디 월렛, 논-커스터디 월렛이라는 용어를 사용합니다.</p>
<h2 id="custodial-wallet">Custodial Wallet</h2>
<blockquote>
<p>서비스 제공자가 사용자의 private key를 대신 보관하고 관리하는 지갑</p>
</blockquote>
<p><strong>특징</strong></p>
<ul>
<li>사용자는 private key를 직접 관리하지 않음</li>
<li>로그인 기반 (이메일, OAuth 등)으로 접근 가능</li>
<li>UX가 Web2 서비스처럼 간단함</li>
<li>서비스가 계정을 복구하거나 접근을 제한할 수 있음</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>사용하기 쉬움 (초보자 친화적)</li>
<li>사용자의 키 분실 리스크 없음</li>
<li>빠른 온보딩</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>서비스가 자산을 통제할 수 있음</li>
<li>해킹 또는 내부 사고 시 리스크 큼(SPOF)</li>
<li>“Not your key, not your coin”</li>
</ul>
<h2 id="non-custodial-wallet">Non-Custodial Wallet</h2>
<blockquote>
<p>Non-Custodial Wallet은 사용자가 자신의 private key를 직접 관리하는 지갑</p>
</blockquote>
<p><strong>특징</strong></p>
<ul>
<li>private key를 사용자 본인이 보관</li>
<li>모든 트랜잭션은 사용자가 직접 서명</li>
<li>!!누구도!! 계정을 임의로 통제할 수 없음</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>완전한 자산 소유권</li>
<li>검열 저항성</li>
<li>탈중앙성 유지</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>키 분실 시 복구 불가능</li>
<li>UX가 어렵고 진입장벽 높음</li>
<li>gas fee, 네트워크 이해 필요</li>
</ul>
<h1 id="account-abstraction--계정-추상화">Account Abstraction : 계정 추상화</h1>
<h2 id="aa-는-왜-등장했는가">AA 는 왜 등장했는가</h2>
<p>기존 EOA의 문제점</p>
<ul>
<li>모든 tx는 EOA private key 서명이 필요</li>
<li>gas fee는 반드시 ETH로만 지불 가능</li>
<li>ETH Funding이 꼭 필요함</li>
<li>nonce 관리 강제</li>
<li>하나의 private key가 곧 계정 자체</li>
<li>서명 정책 커스터마이징 불가능</li>
<li>recovery / session key / multi-sig 등을 구현하려면 별도 구조 필요</li>
</ul>
<h2 id="eoa-vs-account-abstraction-비교">EOA vs Account Abstraction 비교</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>EOA</th>
<th>AA</th>
</tr>
</thead>
<tbody><tr>
<td>서명 주체</td>
<td>Private Key만 가능</td>
<td>원하는 검증 로직 적용 가능</td>
</tr>
<tr>
<td>Gas 지불</td>
<td>ETH만 가능</td>
<td>Paymaster를 통해 확장 가능(대납 등)</td>
</tr>
<tr>
<td>복구</td>
<td>불가능</td>
<td>가능</td>
</tr>
<tr>
<td>멀티시그</td>
<td>외부 구현 필요</td>
<td>내장 가능</td>
</tr>
</tbody></table>
<h2 id="definitions--구성-항목">Definitions : 구성 항목</h2>
<ul>
<li><p><strong>UserOperation</strong> : 유저 행동을 정의하는 정보를 갖고 있는 데이터 구조체. 흔히 사용하는 transaction과 비슷한 개념이지만 같은 것은 아니기 때문에 <code>UserOperation</code> 이라는 용어 사용</p>
</li>
<li><p><strong>Sender</strong> : 추상화된 하나의 계정. <code>UserOperation</code>을 보내는 계정이며, 일반적으로 스마트 컨트랙트 계정(Account Contract)을 의미함</p>
</li>
<li><p><strong>Owner</strong> : 추상화된 계정의 주인 계정. Sender에 대한 통제권과 소유권을 갖고있음을 증명할 수 있는 계정이며 일반적으로 Private Key를 사용하는 EOA 계정</p>
</li>
<li><p><strong>EntryPoint</strong> : <code>UserOperation</code>을 검증하고 실행하는 중심 컨트랙트. Bundler는 여러 <code>UserOperation</code>을 모아 <code>EntryPoint.handleOps()</code>를 호출하고, EntryPoint는 각 Account의 검증 로직과 실행 로직을 호출함</p>
</li>
<li><p><strong>Bundler</strong> : UserOperation mempool에서 <code>UserOperation</code>들을 수집하고, 이를 하나의 일반 Ethereum transaction으로 묶어 EntryPoint에 제출하는 주체</p>
</li>
<li><p><strong>Paymaster</strong> : 사용자의 gas 비용을 대신 지불하거나, ETH가 아닌 다른 방식으로 gas 비용을 처리할 수 있게 해주는 선택적 컨트랙트. 예를 들어 서비스가 gas를 sponsor하거나, 사용자가 토큰으로 gas 비용을 지불하는 구조를 만들 수 있음</p>
</li>
<li><p><strong>AccountFactory</strong> : Account Contract를 생성하는 컨트랙트. 사용자의 Account가 아직 배포되지 않은 경우, <code>UserOperation</code>의 <code>initCode</code>를 통해 Factory가 Account를 생성할 수 있음</p>
</li>
<li><p><strong>UserOperation mempool</strong> : ERC-4337 표준 검증 규칙을 따르는 공개 UserOperation mempool. Bundler들이 공통 규칙에 따라 UserOperation을 수집하고 검증할 수 있도록 설계된 기본 mempool 개념</p>
</li>
<li><p><strong>Deposit</strong> : EntryPoint에 예치된 ETH 잔액. Account 또는 Paymaster가 gas 비용 정산을 위해 EntryPoint에 미리 예치해두는 자금</p>
</li>
</ul>
<h2 id="aa-동작방식">AA 동작방식</h2>
<ol>
<li>userOperation을 생성합니다.</li>
</ol>
<pre><code class="language-json">{
  &quot;sender&quot; : &quot;&quot;,
  &quot;nonce&quot; : &quot;&quot;,
  &quot;factory&quot; : &quot;&quot;,
  &quot;factoryData&quot; : &quot;&quot;,
  &quot;callData&quot; : &quot;&quot;,
  &quot;callGasLimit&quot; : &quot;&quot;,
  &quot;verificationGasLimit&quot; : &quot;&quot;,
  &quot;preVerificationGas&quot; : &quot;&quot;,
  &quot;maxFeePerGas&quot; : &quot;&quot;,
  &quot;maxPriorityFeePerGas&quot; : &quot;&quot;,
  &quot;paymaster&quot; : &quot;&quot;,
  &quot;paymasterVerificationGasLimit&quot; : &quot;&quot;,
  &quot;paymasterPostOpGasLimit&quot; : &quot;&quot;,
  &quot;paymasterData&quot; : &quot;&quot;,
  &quot;signature&quot; : &quot;&quot;,

}</code></pre>
<ol start="2">
<li><p>생성된 userOperation의 실행 주체는 sender입니다. sender가 생성한 userOp 이 맞다는 서명을 owner 계정을 통해 생성합니다.</p>
</li>
<li><p>생성된 userOperation 과 signature를 alt_mempool 에 제출합니다.</p>
</li>
<li><p>bundler는 alt_mempool에 등록된 userOperation 정보(signature 포함)를 가지고 entryPoint의 handleOps() 함수를 호출합니다.</p>
</li>
<li><p>handleOps() 함수는 내부적으로 account contract의 validateUserOp을 통해 userOperation을 검증합니다.</p>
</li>
<li><p>paymaster가 지정되었다면 validatePaymasterUserOp을 통해 paymaster도 검증합니다.</p>
</li>
<li><p>bundler가 handleOps()를 네트워크에 전송할 때 가스토큰을 지불합니다.</p>
</li>
<li><p>userOperation이 실행이 끝나면 사용한 gas 만큼 bundler가 user/paymaster로부터 돌려받습니다.</p>
</li>
<li><p>user/paymaster의 deposit에서 돌려받게 되며, atomic하게 정산됩니다.</p>
</li>
</ol>
<h2 id="validateuserop의-의미">validateUserOp의 의미</h2>
<p>기존 Ethereum 에서는 </p>
<p><code>이 트랜잭션이 유효한, 즉 트랜잭션을 전송하는 사람이 계정의 주인이 맞는지</code></p>
<p>를 검증하기 위하여 ECDSA 서명 검증 로직이 protocol 내부에 고정되어있습니다. 
: transaction data 를 생성 -&gt; 계정 소유자가 private key로 서명 -&gt; 프로토콜 레벨에서 ECDSA 서명검증 -&gt; 검증 성공 시 tx 실행</p>
<p>하지만 AA 에서는 이 검증 과정을 원하는 방식으로 직접 구현할 수 있으며 그로 인해 다음 기능들을 구현할 수 있습니다.</p>
<ul>
<li>owner signature 검증</li>
<li>multiSig 검증</li>
<li>특정 시간대만 사용 가능</li>
<li>특정 dApp만 허용</li>
<li>등</li>
</ul>
<blockquote>
<p>기존 Ethereum에서는 “유효한 서명”의 기준이 protocol에 고정되어 있었다면,
ERC-4337에서는 그 검증 기준 자체를 Account Contract가 직접 정의할 수 있게 됩니다.</p>
</blockquote>
<h2 id="paymaster-예시">paymaster 예시</h2>
<h3 id="gas-sponsor">gas sponsor</h3>
<ol>
<li>사용자는 ETH 없음</li>
<li>서비스는 Paymaster 운영</li>
<li>사용자는 단순 서명만 수행</li>
<li>Paymaster가 gas sponsor</li>
<li>사용자는 Web2처럼 서비스 이용</li>
</ol>
<h3 id="erc20-gas-payment">ERC20 gas payment</h3>
<p>사용자는 USDC만 보유</p>
<p>→ Paymaster가 ETH gas를 대신 지불
→ 이후 User의 USDC를 차감</p>
<h2 id="aa로-가능한-것들">AA로 가능한 것들</h2>
<p><code>계정 추상화</code>는 계정들로 하여금 다음을 가능하게 합니다.</p>
<ul>
<li>가스비 대납</li>
<li>일반 ERC-20 토큰으로 가스비 지불</li>
<li>트랜잭션 배치 실행</li>
<li>계정사용정책 적용</li>
<li>계정 signer 로테이션</li>
<li>등등</li>
</ul>
<h1 id="마무리">마무리</h1>
<p>ERC-4337은 단순히 “가스비를 대신 내주는 기술”이 아닙니다.</p>
<p>기존 Ethereum의 EOA는 강력하고 단순했지만, 계정 자체의 동작 방식을 프로그래밍할 수는 없었습니다.</p>
<p>하지만 Account Abstraction에서는 계정(Account)이 하나의 Smart Contract가 되며,
“어떤 요청을 유효하다고 판단할 것인가” 자체를 개발자가 정의할 수 있게 됩니다.</p>
<p>즉, 계정이 단순한 private key의 소유 개념을 넘어:</p>
<ul>
<li>정책을 가질 수 있고</li>
<li>복구 기능을 가질 수 있으며</li>
<li>여러 서명 방식을 사용할 수 있고</li>
<li>서비스 목적에 맞는 UX를 직접 구현할 수 있는</li>
</ul>
<p>하나의 programmable entity가 됩니다.</p>
<p>그리고 ERC-4337은 이러한 구조를 consensus layer 변경 없이 Ethereum 위에서 구현하기 위한 현실적인 접근입니다.</p>
<p>결국 Account Abstraction은 단순한 기능 추가가 아니라,</p>
<p>“Ethereum 계정 모델 자체를 소프트웨어적으로 확장 가능하게 만든 변화”</p>
<p>라고 볼 수 있겠습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Reentrancy Attack]]></title>
            <link>https://velog.io/@na_treeeum/Reentrancy-Attack</link>
            <guid>https://velog.io/@na_treeeum/Reentrancy-Attack</guid>
            <pubDate>Fri, 30 Jan 2026 09:40:16 GMT</pubDate>
            <description><![CDATA[<h1 id="reentrancy-attack재진입-공격이란">Reentrancy Attack(재진입 공격)이란?</h1>
<p>컨트랙트의 상태가 업데이트 되기 전, 함수를 <strong>다시 호출(재진입)</strong>하여 공격하는 기법</p>
<p>다음과 같은 기능을 갖고있는 컨트랙트는 reentrancy attack 에 취약합니다.</p>
<ul>
<li>외부 컨트랙트의 함수를 호출</li>
<li>외부 컨트랙트로 native token 을 전송</li>
<li>자산 전송 이후에 상태업데이트</li>
</ul>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/c5fe361c-55da-48e7-8ed3-7a1e8ade2945/image.png" alt=""></p>
<p>다음은 Reentrancy 에 취약한 컨트랙트와 이를 공격하는 컨트랙트 코드 예시입니다.</p>
<pre><code class="language-js">contract vulnerableContract{
    mapping(address =&gt; uint256) public balances;

    function withdraw() public{
        require(balances[msg.sender] &gt; 0);

        (bool success, ) = msg.sender.call{value: balances[msg.sender]}(&quot;&quot;);
        require(success);

        balances[msg.sender] = 0;
    }
}</code></pre>
<pre><code class="language-js">contract maliciousContract{
    IVulnerableContract public vCa = &quot;0xsomeAddress..&quot;;

    function exploit() external {
        vCa.withdraw();
    }

    fallback() external payable {
        if(address(vCa).balance &gt;= vCa.balances(address(this))){
            vCa.withdraw();
        }
    }
}</code></pre>
<p>위의 두 컨트랙트는 다음과 같은 흐름으로 exploit이 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/67ce38cf-adb7-4702-a628-ff8117e2b6b8/image.png" alt=""></p>
<h1 id="대응법">대응법</h1>
<h2 id="1-자산-전송-전-상태업데이트-먼저-하기">1. 자산 전송 전 상태업데이트 먼저 하기</h2>
<pre><code class="language-js">contract safeContract{
    mapping(address =&gt; uint256) public balances;

    function withdraw() public{
        require(balances[msg.sender] &gt; 0);

        uint256 transferAmount = balnaces[msg.sender];
        balances[msg.sender] = 0;

        (bool success, ) = msg.sender.call{value: transferAmount}(&quot;&quot;);
        require(success);
    }
}</code></pre>
<p><code>상태(msg.sender 의 balance) 업데이트 → 자산 전송</code> 의 순서로 코드가 실행되면 재진입을 하더라도 잔액을 확인하는 부분에서 오류가 발생하기 때문에 재진입 공격에 실패하게 됩니다.</p>
<h2 id="2-mutex-사용">2. Mutex 사용</h2>
<pre><code class="language-js">contract safeContract{
    mapping(address =&gt; uint256) public balances;
    bool mutex = false;

    function withdraw() public{
        require(!mutex, &quot;Mutex is locked&quot;);
        require(balances[msg.sender] &gt; 0, &quot;Insufficient balance&quot;);

        mutex = true;

        (bool success, ) = msg.sender.call{value: balances[msg.sender]}(&quot;&quot;);
        require(success, &quot;tx is failed&quot;);

        balances[msg.sender] = 0;

        mutex = false;
    }
}</code></pre>
<p>재진입을 하더라도 mutex가 잠겨있는 상태이기 때문에 tx가 실패하게 됩니다.</p>
<h2 id="3-openzeppelin-reentrancyguard-사용">3. Openzeppelin ReentrancyGuard 사용</h2>
<pre><code class="language-js">import {ReentrancyGuard} from &quot;@openzeppelin/contracts/utils/ReentrancyGuard.sol&quot;;

contract safeContract {
    mapping(address =&gt; uint256) public balances;

    function withdraw() public nonReentrant{
        require(balances[msg.sender] &gt; 0, &quot;Insufficient balance&quot;);

        (bool success, ) = msg.sender.call{value: balances[msg.sender]}(&quot;&quot;);
        require(success, &quot;tx is failed&quot;);

        balances[msg.sender] = 0;
    }
}</code></pre>
<p>mutex와 동일한 원리입니다. openzeppelin 에서 표준화한 코드를 사용합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[홈 서버 구축기 - 초기세팅]]></title>
            <link>https://velog.io/@na_treeeum/%ED%99%88-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%EA%B8%B0-%EC%B4%88%EA%B8%B0%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@na_treeeum/%ED%99%88-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%EA%B8%B0-%EC%B4%88%EA%B8%B0%EC%84%B8%ED%8C%85</guid>
            <pubDate>Fri, 09 Jan 2026 14:23:32 GMT</pubDate>
            <description><![CDATA[<p>램트코인 이라는 말이 나올 정도로 메모리, ssd 가격이 천정부지로 올라가고 있습니다.</p>
<p>여느때와 같이 당근을 보던 중</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/cd35fe62-445d-4d43-a1be-4850c093cd7a/image.png" alt=""></p>
<p>신품기준 무려 약 29만원짜리 상품을 단돈 13만원에 판매한다는 글을 보았고, 바로 메세지를 보냈습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/ec9fb8c8-beb0-4807-9292-902945c730ee/image.jpg" alt=""></p>
<p>오케이 굿. 바로 구매 ㄱㄱ</p>
<p>연락을 한 다음 날 바~~로 집으로 데려왔습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/9fd16747-858a-425a-86f2-2ea22e8a3637/image.png" alt=""></p>
<p>케이스는 군시절이 떠오르는 국방색이고 사이즈는 수첩? 정도의 아주 작고 귀여운 미니PC 입니다.</p>
<p>이 친구를 처음 데려왔을 때에는 윈도우11 이 설치되어 있었습니다.</p>
<p>저는 개발자기 때문에(?) 바로 우분투를 깔기로 마음먹었습니다.</p>
<p>또한, 그냥 개발자가 아닌 &quot;BackEnd Developer&quot; 이기 때문에, <em>GUI 따위 필요없다</em> 선언하고 바~로 Ubuntu Server 를 설치했습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/c44ee45b-af0b-4564-81b2-9ff2490973d7/image.png" alt=""></p>
<p>화면에 딱 한줄 띄워져 있는 모습, 아주 멋있지 않나요?</p>
<p>그런데, 막상 설치하고 나니 무인도에 알몸으로 던져진 느낌이었습니다.</p>
<p>인터넷 조차 직접 설정해야 되더라구요.. 새삼 GUI의 편리함을 느끼게 되는 계기였습니다.</p>
<p>패기롭게 시작하였지만 아는게 없는 저는 바~로 GPT선생님께 질문하였고 역시나 갓갓갓선생님께서는 설정법을 아주 쉽게 알려주셨습니다.</p>
<p>먼저 <code>ip a</code> 명령어를 입력해서 사용할 수 있는 랜카드를 확인합니다.</p>
<p>확인 결과, 유선랜포트는 enp3s0 라고 라고 돼있었습니다.</p>
<p>인터넷에 연결하기 위해 <code>/etc/netplan/00-wired.yaml</code> 파일을 생성해주고 다음 내용을 작성했습니다.</p>
<pre><code class="language-yaml">network:
  version: 2
  renderer: networkd
  ethernets:
    enp3s0:
      dhcp4: true</code></pre>
<p>그 다음 <code>~$ sudo netplan apply</code> 로 설정파일 적용!</p>
<p><code>~$ ping 8.8.8.8</code> 명령어를 통해 인터넷 연결이 잘 되었는지
(google dns 서버와 통신이 되는지) 확인을 하고</p>
<p>ssh를 설치했습니다</p>
<p><code>apt install -y openssh-server</code>
<code>systemctl enable ssh</code>
<code>systemctl start ssh</code></p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/88c75fc2-2eef-4b65-828b-483c586d79f3/image.png" alt=""></p>
<p>짜라란~ 접속 성공!</p>
<p>자, 그럼 이제 무엇을 해볼지 고민을 해야합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Websocket을 활용한 event indexing]]></title>
            <link>https://velog.io/@na_treeeum/indexing</link>
            <guid>https://velog.io/@na_treeeum/indexing</guid>
            <pubDate>Fri, 09 Jan 2026 05:49:54 GMT</pubDate>
            <description><![CDATA[<h3 id="github">github</h3>
<p><a href="https://github.com/natreeum/HybridIndexer">https://github.com/natreeum/HybridIndexer</a></p>
<h1 id="개요">개요</h1>
<p>기존 인덱싱 방식은 http 방식(Polling)으로 진행. 이로인해 설정된 주기로만 요청하므로 최대 ‘설정된 주기까지의 delay’가 생김. 이로 인해 <strong>낙관적 업데이트</strong>와 같은 추가 작업을 필요로 합니다.</p>
<h1 id="구상">구상</h1>
<ol>
<li>ws provider를 통한 event subscription으로 event 발생 시 트랜잭션(event) 수집</li>
<li>웹소켓으로 수집된 트랜잭션은 DB에 저장
 a. 최초 status 는 unconfirmed상태로 저장</li>
<li>batch indexing을 통해 unconfirmed 상태의 트랜잭션에 대해 confirm
 a. removed : true 인 경우(reorg) 삭제
 b. 누락된 데이터 업데이트</li>
<li>ws connection 이 유실되었을 경우 재연결 시도</li>
</ol>
<h2 id="0-contract">0. Contract</h2>
<p>Event 를 emit 하는 단순한 컨트랙트 배포</p>
<p>code : Test.sol</p>
<pre><code class="language-javascript">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Test {
    event LogMessage(string message);
    event TestTransfer(address indexed from, address indexed to, uint256 value);

    function emitLogMessage(string memory message) public {
        emit LogMessage(message);
    }

    function transfer(address to, uint256 value) public {
        emit TestTransfer(msg.sender, to, value);
    }
}
</code></pre>
<h2 id="1-websocket으로-emit되는-event-확인">1. Websocket으로 Emit되는 event 확인</h2>
<p>code : indexer.js</p>
<pre><code class="language-js">const { ethers } = require(&quot;ethers&quot;);
module.exports = class HybridIndexer {
  constructor({
    wsUrl,
    httpUrl,
    contractCfg, // {address, abi}
    events, // [&quot;Transfer&quot;, ...]
    confirmations = 10,
    chunkSize = 2000,
    poolIntervalMs = 5000,
  }) {
    this.wsUrl = wsUrl;
    this.httpUrl = httpUrl;
    this.contractCfg = contractCfg;
    this.events = events;
    this.confirmations = confirmations;
    this.chunkSize = chunkSize;
    this.poolIntervalMs = poolIntervalMs;

    this.wsProvider = null;
    this.httpProvider = null;
    this.wsContract = null;
    this.httpContract = null;
    this.running = false;
  }

  async start() {
    this.running = true;

    this.wsProvider = new ethers.WebSocketProvider(this.wsUrl);
    this.httpProvider = new ethers.JsonRpcProvider(this.httpUrl);

    this.wsContract = new ethers.Contract(
      this.contractCfg.address,
      this.contractCfg.abi,
      this.wsProvider
    );
    console.log(`WebSocket Provider connected to ${this.wsUrl}`);

    this.httpContract = new ethers.Contract(
      this.contractCfg.address,
      this.contractCfg.abi,
      this.httpProvider
    );
    console.log(`HTTP Provider connected to ${this.httpUrl}`);

    this._subscribeEvents();
    console.log(&quot;HybridIndexer started&quot;);
  }

  _subscribeEvents() {
    this.events.forEach((eventName) =&gt; {
      this.wsContract.on(eventName, async (...args) =&gt; {
        console.log(args);
      });
    });
  }
};
</code></pre>
<p>code : server.js</p>
<pre><code class="language-js">const express = require(&quot;express&quot;);
const Indexer = require(&quot;./indexer/hybridIndexer&quot;);

const app = express();

// Fuji Network configuration
const wsUrl = &quot;wss://api.avax-test.network/ext/bc/C/ws&quot;;
const httpUrl = &quot;https://api.avax-test.network/ext/bc/C/rpc&quot;;
const abi = require(&quot;./abi/ABI.json&quot;); // 예시 ABI 파일
const contractAddress = &quot;0xa85761417ed8ae28378612a63282b4A576949377&quot;;
const contractCfg = {address : contractAddress, abi}
const events = [&quot;LogMessage&quot;,&quot;TestTransfer&quot;]

app.get(&quot;/&quot;, (req, res) =&gt; {
  return res.send(&quot;Hello, World!&quot;);
});

const indexer = new Indexer({
  wsUrl,
  httpUrl,
  contractCfg, // ethers.Contract Instance
  events, // [&quot;Transfer&quot;, ...]
});


indexer.start()

app.listen(3000, () =&gt; {
  console.log(&quot;Server is running on port 3000&quot;);
});</code></pre>
<p>서버를 실행하면 indexer가 실행되고, events에 대해 subscription 실행</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/175a4963-de86-4a3b-9c36-448d7aedc968/image.png" alt=""></p>
<p>Test.sol 의 LogMessage Event 발생 시 서버측에서 이벤트 확인</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/84e84dde-6f7f-429a-a736-0601c609e8f5/image.png" alt=""></p>
<h2 id="2-db-table">2. DB Table</h2>
<p>확장성을 고려해서 공통 column / eventArgs column을 구분</p>
<h3 id="공통-column">공통 column</h3>
<ul>
<li>transaction_hash</li>
<li>block_number</li>
<li>log_index</li>
<li>transaction_index</li>
<li>confirmed</li>
</ul>
<h3 id="event-args-column">Event Args Column</h3>
<ul>
<li>... </li>
</ul>
<h2 id="3-indexer-constructor">3. Indexer Constructor</h2>
<ul>
<li><code>wsUrl</code> : WebSocket RPC URL</li>
<li><code>httpUrl</code> : http RPC URL</li>
<li><code>contractCfg</code><ul>
<li><code>address</code> : 컨트랙트 주소</li>
<li><code>abi</code> : 컨트랙트 abi</li>
</ul>
</li>
<li><code>eventName</code> : 받아올 event의 이름</li>
<li><code>tableName</code> : 받아온 event data 를 저장하는 table 이름</li>
<li><code>argKeys</code> : args 이름 배열 <code>[”eventArgName1”, “eventArgName2”]</code></li>
<li><code>confirmations</code> : block confirmation 수</li>
<li><code>chunkSize</code> : polling 최대 블록 수</li>
<li><code>pollIntervalMs</code> : polling 인터벌 ms</li>
<li><code>pingIntervalMs</code> : ws connection status 체크를 위한 ping 인터벌 ms</li>
</ul>
<h1 id="구현">구현</h1>
<h2 id="hybrid-indexer">Hybrid Indexer</h2>
<pre><code class="language-js">const { ethers } = require(&quot;ethers&quot;);
const log = require(&quot;../libraries/timestampLog&quot;);
const insertEvent = require(&quot;../database/insert&quot;);
const updateEvent = require(&quot;../database/update&quot;);
const deleteEvent = require(&quot;../database/delete&quot;);
const updateLastQueryBlockNumber = require(&quot;../database/updateLastQueryBlock&quot;);
const getLastBlockNumber = require(&quot;../database/getLastBlockNumber&quot;);
const getEvent = require(&quot;../database/getEvent&quot;);

module.exports = class HybridIndexer {
  constructor({
    wsUrl,
    httpUrl,
    contractCfg, // {address, abi}
    eventName,
    tableName,
    argKeys, // [&quot;eventArgName1&quot;, &quot;eventArgName2&quot;]
    confirmations = 10,
    chunkSize = 1000,
    pollIntervalMs = 30000, // Default 30 seconds
    pingIntervalMs = 300000, // Default 5 minutes
  }) {
    this.wsUrl = wsUrl;
    this.httpUrl = httpUrl;
    this.contractCfg = contractCfg;
    this.eventName = eventName;
    this.confirmations = confirmations;
    this.chunkSize = chunkSize;
    this.pollIntervalMs = pollIntervalMs;
    this.intervalPoll = null;
    this.mutexLock = false;
    this.lastConfirmedBlock = 0;

    this.wsProvider = null;
    this.httpProvider = null;
    this.wsContract = null;
    this.httpContract = null;

    this.tableName = tableName;
    this.argKeys = argKeys;

    this.intervalPing = null;
    this.pingIntervalMs = pingIntervalMs;

    this.running = false;
  }

  async start() {
    this.running = true;

    this.wsProvider = new ethers.WebSocketProvider(this.wsUrl);
    this.httpProvider = new ethers.JsonRpcProvider(this.httpUrl);

    this.wsContract = new ethers.Contract(
      this.contractCfg.address,
      this.contractCfg.abi,
      this.wsProvider
    );
    log(`WebSocket Provider is set to ${this.wsUrl}`);
    this.wsProvider.websocket.on(&quot;open&quot;, () =&gt; {
      log(`WebSocket connected to ${this.wsUrl}`);
    });

    this.httpContract = new ethers.Contract(
      this.contractCfg.address,
      this.contractCfg.abi,
      this.httpProvider
    );
    log(`HTTP Provider is set to ${this.httpUrl}`);

    this._subscribeEvents();
    log(&quot;HybridIndexer started&quot;);

    // reconnect on error
    this.wsProvider.websocket.on(&quot;error&quot;, () =&gt; {
      log(`WebSocket Error Occurred`);
      this.wsProvider?.removeAllListeners?.();
      log(`Removing all listeners from WebSocket Provider`);
      this.wsProvider?.destroy?.();
      log(`WebSocket Provider destroyed`);

      if (this.running) {
        log(&quot;Attempting to reconnect...&quot;);
        this.start();
      }
    });

    // reconnect on close
    this.wsProvider.websocket.on(&quot;close&quot;, () =&gt; {
      log(`WebSocket Connection Closed`);
      this.wsProvider?.removeAllListeners?.();
      log(`Removing all listeners from WebSocket Provider`);
      this.wsProvider?.destroy?.();
      log(`WebSocket Provider destroyed`);

      // Reconnect if still running
      if (this.running) {
        log(&quot;Attempting to reconnect...&quot;);
        this.start();
      }
    });

    // To keep the WebSocket connection alive, send periodic pings
    this.intervalPing = setInterval(() =&gt; {
      if (this.wsProvider.websocket.readyState === 1) {
        this.wsProvider.websocket.ping();
        log(&quot;WebSocket ping sent&quot;);
      } else {
        log(&quot;WebSocket is not open, skipping ping&quot;);
      }
    }, this.pingIntervalMs);

    this._confirmEvents();
  }

  stop() {
    this.running = false;
    try {
      this.wsProvider?.removeAllListeners?.();
      this.wsProvider?.destroy?.();
      this.wsProvider = null;
    } catch (error) {
      error(&quot;Error stopping WebSocket Provider:&quot;, error);
    }

    // Clear the ping interval if it exists
    if (this.intervalPing) {
      clearInterval(this.intervalPing);
      this.intervalPing = null;
      log(&quot;Ping interval cleared&quot;);
    }

    log(&quot;HybridIndexer stopped&quot;);
  }

  _subscribeEvents() {
    this.wsContract.on(this.eventName, async (...args) =&gt; {
      const event = args[args.length - 1]; // ethers v6: 마지막 인자가 Event

      await insertEvent({
        tableName: this.tableName,
        transactionHash: event.log.transactionHash,
        blockNumber: event.log.blockNumber,
        logIndex: event.log.index,
        transactionIndex: event.log.transactionIndex,
        argKeys: this.argKeys,
        argValues: event.log.args,
      });
      log(
        `Event has been inserted into the database : ${event.log.blockNumber}-${event.log.index}-${event.log.transactionIndex}`
      );
    });
    log(`Subscribed to event: ${this.eventName}`);
  }

  _confirmEvents() {
    this.intervalPoll = setInterval(async () =&gt; {
      if (this.mutexLock) {
        console.log(`Mutex is locked`);
        return;
      }
      this.mutexLock = true;
      try {
        const network = await this.httpProvider.getNetwork();
        const lastBlockNumber = await getLastBlockNumber({
          eventName: this.eventName,
          contractAddress: this.contractCfg.address,
          networkId: network.chainId.toString(),
        });
        console.log(&quot;last block number : &quot;, lastBlockNumber);
        const currentBlockNumber = await this.httpProvider.getBlockNumber();
        console.log(&quot;Current block number : &quot;, currentBlockNumber);
        const filter = this.httpContract.filters[this.eventName]();

        const fromBlockNumber = lastBlockNumber + 1;
        const toBlockNumber = Math.min(
          currentBlockNumber - this.confirmations,
          fromBlockNumber + this.chunkSize
        );
        console.log(fromBlockNumber);
        console.log(toBlockNumber);

        // invalid block range
        if (fromBlockNumber &gt;= toBlockNumber) {
          console.log(
            `Block range is invalid: from:${fromBlockNumber} &gt;= to:${toBlockNumber}`
          );
          throw new Error(`Invalid block range`);
        }

        const events = await this.httpContract.queryFilter(
          filter,
          fromBlockNumber,
          toBlockNumber
        );

        for (const e of events) {
          const {
            transactionHash,
            blockNumber,
            removed,
            index,
            transactionIndex,
            args,
          } = e;
          // Check if this event exist on db
          const exists = await getEvent({
            tableName: this.tableName,
            transactionHash,
            blockNumber,
            index,
            transactionIndex,
          });
          if (exists.length &gt; 0) {
            if (!removed) {
              // if the event is not removed, update &quot;confirmed&quot; as 1
              await updateEvent({
                tableName: this.tableName,
                transactionHash,
                blockNumber,
                index,
                transactionIndex,
              });
            } else {
              // if the event is removed, delete the row
              await deleteEvent({
                tableName: this.tableName,
                transactionHash,
                blockNumber,
                index,
                transactionIndex,
              });
            }
          } else {
            // if the event is not exist on db, insert it
            await insertEvent({
              tableName: this.tableName,
              transactionHash,
              blockNumber,
              logIndex: index,
              transactionIndex,
              argKeys: this.argKeys,
              argValues: args,
            });
          }
        }
        console.log(`${events.length} events processed`);
        await updateLastQueryBlockNumber({
          tableName: &quot;event_query_info&quot;,
          eventName: this.eventName,
          contractAddress: this.contractCfg.address,
          networkId: network.chainId.toString(),
          lastQueryBlockNumber: toBlockNumber,
        });
      } catch (e) {
        console.log(e);
      } finally {
        this.mutexLock = false;
      }
    }, this.pollIntervalMs);
  }

  websocketStatus() {
    return this.wsProvider?.websocket?.readyState === 1
      ? &quot;Connected&quot;
      : &quot;Disconnected&quot;;
  }
};</code></pre>
<h2 id="서버-실행">서버 실행</h2>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/a012f1d0-0e78-45aa-b02c-d679132c8138/image.png" alt=""></p>
<h2 id="event-발생-시-즉시-db에-row-생성">Event 발생 시 즉시 DB에 row 생성</h2>
<p>새로운 row 확인
<img src="https://velog.velcdn.com/images/na_treeeum/post/f6b5f02f-d186-4e0e-8b4a-1e52ea770914/image.png" alt=""></p>
<p>새로운 row가 insert된 내용의 log
<img src="https://velog.velcdn.com/images/na_treeeum/post/167e3daa-2abf-4fc6-8fab-20d492a422bd/image.png" alt=""></p>
<h2 id="polling-으로-tx-confirm">Polling 으로 Tx Confirm</h2>
<p>confirmed status 가 0-&gt;1 로 변경됨
<img src="https://velog.velcdn.com/images/na_treeeum/post/5092dc07-1d2d-43b0-86cc-28f7dc0af602/image.png" alt=""></p>
<p>server - confirmed event log
<img src="https://velog.velcdn.com/images/na_treeeum/post/ca40fffe-568d-4f9c-b101-b62aa91919cb/image.png" alt=""></p>
<p>Websocket Ping 으로 health check 
<img src="https://velog.velcdn.com/images/na_treeeum/post/e379e9df-23d9-47fa-b055-805d60904e8d/image.png" alt=""></p>
<p>Polling 할 block range 를 기록하기 위한 테이블
<img src="https://velog.velcdn.com/images/na_treeeum/post/3c96f3a1-9932-4d5d-8af8-d28afde246f9/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Multicall Contract 취약점 사례]]></title>
            <link>https://velog.io/@na_treeeum/Multicall-Contract-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%82%AC%EB%A1%80</link>
            <guid>https://velog.io/@na_treeeum/Multicall-Contract-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%82%AC%EB%A1%80</guid>
            <pubDate>Thu, 08 Jan 2026 07:01:44 GMT</pubDate>
            <description><![CDATA[<h1 id="uniswap-multicall">Uniswap Multicall</h1>
<p>Uniswap Multicall 컨트랙트의 코드를 살펴보면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/6c77b27b-9a67-453c-a1c7-3edb245f5259/image.png" alt=""></p>
<p><em>Call</em> 이라는 data struct를 배열로 받고, target.call() 을 통해 배열로 전달된 callData 를 target contract에 실행시킵니다.</p>
<p>여기서 해당 tx의 실행 주체는 uniswap multicall contract가 됩니다.</p>
<p>이를 악용한 공격 수법을 알아보겠습니다.</p>
<p>공격자는 피해자에게 다음과 같은 Approve 트랜잭션에 서명하도록 합니다.</p>
<ul>
<li>Spender : Uniswap Multicall Contract</li>
<li>Amount :  uint256 최대치</li>
</ul>
<p>피해자의 입장에서는 수많은 트랜잭션이 일어나고, <strong>유명한 Uniswap 컨트랙트</strong>가 spender 이므로 별다른 의심없이 해당 트랜잭션에 서명하게 됩니다.</p>
<p>이 과정에서 Uniswap Contract는 피해자의 지갑이 보유하고 있는 토큰에 대한 제어권을 갖게 됩니다.</p>
<p>Uniswap Contract가 피해자의 토큰에 대한 제어권을 갖게 되면, 공격자는 Uniswap Contract에 대해 transferFrom 메소드를 다음과같이 호출합니다.</p>
<ul>
<li>sender : 피해자 지갑주소</li>
<li>recipient : 공격자 지갑 주소</li>
<li>amount : 수량</li>
</ul>
<blockquote>
<p>여기서 <strong>transferFrom</strong> 은 다음과 같이 작동합니다.
<code>transferFrom(sender, recipient, amount);</code>
tx를 전송하는 주체(msg.sender)는 sender로 부터 allowance 를 취득해야하고,
allowance가 충분할 경우 sender의 지갑에서 recipient에게 amount만큼 전송합니다.</p>
</blockquote>
<p>이 과정을 도식화 하면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/67dd0257-d551-44eb-aff9-b134ee27c2f8/image.png" alt=""></p>
<h1 id="gnosis-safe">Gnosis Safe</h1>
<p>비슷한 사례로 Gnosis safe wallet (multi sig wallet)을 통한 공격도 있었습니다.</p>
<p>공격의 순서는 다음과 같습니다</p>
<ol>
<li>공격자는 safe wallet(proxy)을 생성합니다</li>
<li>공격자는 피해자에게 safe wallet에 대한 approve를 서명하도록 유도합니다.</li>
<li>피해자가 approve tx에 서명을 하면, 공격자는 proxy 컨트랙트의 execTransaction 메소드를 호출</li>
<li>proxy 컨트랙트는 마스터 컨트랙트의 execTransaction을 delegateCall을 통해 호출</li>
</ol>
<p>이 과정을 도식화 하면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/4e2a3607-0613-44a7-8141-1f9db59a004c/image.png" alt=""></p>
<h1 id="개발-시-주의할-점">개발 시 주의할 점</h1>
<ul>
<li>공개된 multicall contract 에 approve 를 수행해서는 절대 안됨</li>
<li>multicall 기능이 필요하다면, tx를 대신해서 수행하는 메소드에 권한을 추가하여(whitelisted) 제한적으로 사용</li>
</ul>
<p>참고 : <a href="https://research.checkpoint.com/2024/unveiling-the-scam-how-fraudsters-abuse-legitimate-blockchain-protocols-to-steal-your-cryptocurrency-wallet/">https://research.checkpoint.com/2024/unveiling-the-scam-how-fraudsters-abuse-legitimate-blockchain-protocols-to-steal-your-cryptocurrency-wallet/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Off-chain message Signing 검증하기 (EIP-712)]]></title>
            <link>https://velog.io/@na_treeeum/Off-chain-message-Signing-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0-EIP-712</link>
            <guid>https://velog.io/@na_treeeum/Off-chain-message-Signing-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0-EIP-712</guid>
            <pubDate>Wed, 07 Jan 2026 07:43:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://eips.ethereum.org/EIPS/eip-712">https://eips.ethereum.org/EIPS/eip-712</a>
<img src="https://velog.velcdn.com/images/na_treeeum/post/fb5cfc76-5632-4382-b256-a1882eb2b00a/image.png" alt=""></p>
<h1 id="eip-712">EIP-712</h1>
<p>EIP-712 는 트랜젝션 데이터가 아닌 모든 데이터를 <strong>어떤 구조로 서명할 지</strong>에 대해 정의한 제안이며 사용자에게 서명할 메세지를 <strong>사람이 읽을 수 있는 구조로 보여주고</strong>, 그 구조에 따라 서명하여 <strong>신뢰성</strong>과 <strong>보안성</strong>을 높이는데 초점을 둡니다.</p>
<h1 id="핵심-요소">핵심 요소</h1>
<h2 id="type_hash">TYPE_HASH</h2>
<p>TYPE_HASH 는 <code>기능이름(parameters)</code> 로 된 문자열을 keccak256 으로 해싱 한 값입니다.</p>
<p>EIP712Domain 에 대한 TYPE_HASH 는 다음과 같습니다.</p>
<pre><code class="language-javascript">keccak256(&quot;EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)&quot;);</code></pre>
<h2 id="domainseperator">domainSeperator</h2>
<blockquote>
<p>어떤 네트워크의 어떤 컨트랙트에서 사용되는(검증되는) 서명인지 구분하기 위한 구분자.</p>
</blockquote>
<p>DomainSeperator 의 구성은 다음과 같습니다.</p>
<ul>
<li><code>string name</code> : 컨트랙트(DApp 혹은 Protocol)의 이름</li>
<li><code>string version</code> : 컨트랙트 버전</li>
<li><code>uint256 chainId</code> : 컨트랙트가 배포된 네트워크 아이디</li>
<li><code>address verifyingContract</code> : 서명을 검증하는 컨트랙트의 주소(배포된 컨트랙트 주소)</li>
</ul>
<p>Openzeppelin 에서 DomainSeperator를 생성하는 코드는 다음과 같습니다. (<a href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol#L90">EIP712.sol#L90</a>)</p>
<pre><code class="language-javascript">function _buildDomainSeparator() private view returns (bytes32) {
    return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
}</code></pre>
<h2 id="encodetype">encodeType</h2>
<blockquote>
<p>Parameter 로 전달되는 인자의 type을 정의한 정의서. encodeType을 통해 해당 함수에 대한 TYPE_HASH 가 생성됩니다.</p>
</blockquote>
<p>예시</p>
<pre><code class="language-javascript">const type = {
    transfer: [
      {name:&quot;to&quot;, type:&quot;address&quot;},
      {name:&quot;amount&quot;, type:&quot;uint256&quot;},
    ]
};</code></pre>
<h2 id="encodedata">encodeData</h2>
<blockquote>
<p>Parameter 로 전달되는 인자의 값</p>
</blockquote>
<p>예시</p>
<pre><code class="language-javascript">const data = {
    to : wallet.address,
      amount : 1000000000000000000n
};</code></pre>
<h1 id="검증-과정">검증 과정</h1>
<ol>
<li><p>signer는 private key 를 사용하여 <strong>메세지를 서명</strong>합니다.
 서명된 메세지는 다음 데이터들로부터 생성됩니다.</p>
<ul>
<li>domainSeperator</li>
<li>encodeType</li>
<li>encodeData</li>
</ul>
</li>
<li><p>서명된 메세지(signature)는 <strong>v, r, s 로 분리</strong>됩니다.</p>
<ul>
<li>v, r, s 는 ECDSA(타원곡선을 이용한 전자서명알고리즘)을 위한 값</li>
</ul>
</li>
<li><p>함수 호출자는 서명된 <strong>데이터(v,r,s) 와 arguments 를 함께 전달</strong>합니다.</p>
</li>
<li><p>함수에서는 전달받는 arguments 와 v,r,s 값으로 <code>ecrecover</code> 함수를 통해 해당 서명을 검증합니다.</p>
<p> a. 서명에 사용된 값과 arguments 값이 일치하면 <code>ecrecover</code> 는 <strong>서명자의 주소를 반환</strong>합니다.</p>
<p> b. 서명에 사용된 값과 arguments 값이 일치하지 않을경우 <code>ecrecover</code> 는 <strong>엉뚱한 주소를 반환</strong>합니다.</p>
</li>
</ol>
<pre><code>예시
1. owner 가 to:0x1234, amount:100 값에 대해 서명하여 Signature 생성
2. 함수호출자가 to:0x1234, amount:9000000 라는 값을 Owner 의 Signature 와 함께 호출
-&gt; 검증 과정에서 ecrecover 가 엉뚱한 주소를 반환하여 검증 실패

1. owner 가 to:0x1234, amount:100 값에 대해 서명하여 Signature 생성
2. 함수호출자가 to:0x1234, amount:100 라는 값을 Owner 의 Signature 와 함께 호출
-&gt; 검증 과정에서 ecrecover 가 owner의 주소를 반환하여 검증 성공
</code></pre><p>컨트랙트 코드를 함께 보며 이해도를 높이면 좋을것 같습니다.
다음은 설명을 위한 컨트랙트의 함수 코드입니다.</p>
<pre><code class="language-javascript">// mintWithApproval() 은 owner의 private key 로 sign 된 메세지(signature)가 정상일 때 민팅을 할 수 있는 함수입니다.
function mintWithApproval(
    address to,
    uint256 amount,
    uint8 v,
    bytes32 r,
    bytes32 s
) external {
    // 함수의 TYPE_HASH 와 전달받은 arguments 로 structHash 를 생성합니다.
    bytes32 structHash = keccak256(
        abi.encode(
            keccak256(&quot;mintWithApproval(address to,uint256 amount)&quot;), // TYPE_HASH
            to, // arg1
            amount // arg2
        )
    );

    bytes32 hash = _hashTypedDataV4(structHash);

    // 서명된 데이터(v,r,s) 에 사용된 데이터와 arguments(to, amount) 값이 같다면 ecrecover 는 서명한 지갑의 주소를 반환합니다.
    address signer = ecrecover(hash, v, r, s);


    if (signer != owner) {
        revert(&quot;Invalid signature&quot;);
    }

    _mint(to, amount);
}</code></pre>
<h1 id="eip-712-를-따르는-서명-생성하기">EIP-712 를 따르는 서명 생성하기</h1>
<p>EIP-712 를 따르는 서명을 만드는 방법은 다양하겠지만 이 글에서는 ehters.js 라이브러리로 서명을 생성하는 방법만 다뤄보겠습니다.</p>
<h2 id="ethersjs">ethers.js</h2>
<h3 id="도메인-지정">도메인 지정</h3>
<p>domainSeperator 생성을 위한 도메인을 지정해줍니다.</p>
<pre><code class="language-javascript">const domain = {
    name: &quot;Test&quot;,
      version: &quot;1&quot;,
      chainId: 1,
      verifyingContract: &quot;0x1234contractAddress&quot;
};</code></pre>
<h3 id="type-지정">Type 지정</h3>
<p>parameter 로 전달할 인자들의 타입을 지정해줍니다.</p>
<pre><code class="language-javascript">const types = {
    mintWithApproval:[
      {name: &quot;to&quot;, type: &quot;address&quot;},
      {name: &quot;amount&quot;, type: &quot;uint256&quot;},
    ]
};</code></pre>
<h3 id="data-지정">Data 지정</h3>
<p>parameter 로 전달할 인자들의 값을 지정해줍니다.</p>
<pre><code class="language-javascript">const data = {
    to: &quot;0x9876walletAddress&quot;,
      amount: 1000000000000000000n
};</code></pre>
<h3 id="signature-생성--vrs-로-분리">Signature 생성 + vrs 로 분리</h3>
<p>ethers 의 Wallet instance 로 서명을 생성합니다.</p>
<pre><code class="language-javascript">const owner = ethers.Wallet.createRandom();

const signature = owner.signTypedData(domain, types, data);

const {v, r, s} = ethers.Signature.from(signature);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Easy Rust Korean 051 ~ 060]]></title>
            <link>https://velog.io/@na_treeeum/Easy-Rust-Korean-051-060</link>
            <guid>https://velog.io/@na_treeeum/Easy-Rust-Korean-051-060</guid>
            <pubDate>Sun, 30 Nov 2025 12:36:43 GMT</pubDate>
            <description><![CDATA[<h1 id="051">051</h1>
<p>HashMap : key - value</p>
<p>key와 value 는 어떤 타입도 될 수 있음</p>
<p><code>(key)String -&gt; (value)Vec&lt;String&gt;</code>
의 hashMap 을 만들고 싶다면 다음과 같이 사용</p>
<pre><code class="language-rust">// HashMap&lt;String, Vec&lt;String&gt;&gt;</code></pre>
<p>hashMap example</p>
<pre><code class="language-rust">use std::collections::HashMap;

struct City{
    name: String,
    population: HashMap&lt;u32, u32&gt; // year -&gt; population
}

fn main(){
    let mut tallin = City {
        name : &quot;Tallin&quot;.to_string(),
        population: HashMap::new()
    };

    tallin.population.insert(1372, 3_250);
    tallin.population.insert(1851, 24_000);
    tallin.population.insert(2020, 437_619);

    for(year, population) in tallin.population{
        println!(&quot;In the year {} the population was {}&quot;, year, population)
    }
}</code></pre>
<p>1372년, 1851년, 2020년의 순서로 나오지 않음. (순서는 완전 랜덤)</p>
<p>hashMap 은 순서를 정의할 수 없는 특징이 있음.</p>
<p>BTreeMap : ordering 이 가능한 HashMap</p>
<blockquote>
<p>order가 필요없다면 HashMap, 필요하다면 BTreeMap(상대적으로 느림)</p>
</blockquote>
<p>BTreeMap 예시</p>
<pre><code class="language-rust">use std::collections::BTreeMap;

struct City{
    name: String,
    population: BTreeMap&lt;u32, u32&gt; // year -&gt; population
}

fn main(){
    let mut tallin = City {
        name : &quot;Tallin&quot;.to_string(),
        population: BTreeMap::new()
    };

    tallin.population.insert(1372, 3_250);
    tallin.population.insert(1851, 24_000);
    tallin.population.insert(2020, 437_619);

    for(year, population) in tallin.population{
        println!(&quot;In the year {} the population was {}&quot;, year, population)
    }
}
// 1372년 -&gt; 1851년 -&gt; 2020년 순서가 보장됨</code></pre>
<h1 id="052">052</h1>
<p>HashMap 의 값을 불러오는 방법</p>
<p>(안전x 방법)</p>
<pre><code class="language-rust">use std::collections::HashMap;

fn main(){
    let canadian_cities = vec![&quot;Calgary&quot;, &quot;Vancouver&quot;, &quot;Gimli&quot;];
    let german_cities = vec![&quot;Karlsruhe&quot;, &quot;Bad Doberan&quot;, &quot;Bielefeld&quot;];

    let mut city_hashmap = HashMap::new();

    for city in canadian_cities{
        city_hashmap.insert(city, &quot;Canada&quot;);
    }

    for city in german_cities{
        city_hashmap.insert(city, &quot;Germany&quot;);
    }

    println!(&quot;{:?}&quot;, city_hashmap[&quot;Bielefeld&quot;]); // 정상 출력 (Bielefeld 라는 key가 존재함)

    println!(&quot;{:?}&quot;, city_hashmap[&quot;Bielefeld123&quot;]); // key 가 존재하지 않아서 오류 발생 
}</code></pre>
<p>(안전한 방법, .get 사용)
.get을 사용하면 &quot;None&quot;을 반환</p>
<pre><code class="language-rust">use std::collections::HashMap;

fn main(){
    let canadian_cities = vec![&quot;Calgary&quot;, &quot;Vancouver&quot;, &quot;Gimli&quot;];
    let german_cities = vec![&quot;Karlsruhe&quot;, &quot;Bad Doberan&quot;, &quot;Bielefeld&quot;];

    let mut city_hashmap = HashMap::new();

    for city in canadian_cities{
        city_hashmap.insert(city, &quot;Canada&quot;);
    }

    for city in german_cities{
        city_hashmap.insert(city, &quot;Germany&quot;);
    }

    println!(&quot;{:?}&quot;, city_hashmap.get(&quot;Bielefeld&quot;)); // Some(&quot;Germany&quot;) 출력

    println!(&quot;{:?}&quot;, city_hashmap.get(&quot;Bielefeld123&quot;)); // None 출력
}</code></pre>
<p>이미 값이 존재하는 key 에 대해 insert하게 되면 덮어쓰기가 됨.</p>
<pre><code class="language-rust">use std::collections::HashMap;

fn main(){
    let mut book_hashmap = HashMap::new();

    book_hashmap.insert(1, &quot;Book1&quot;);
    book_hashmap.insert(1, &quot;Book2&quot;);
    book_hashmap.insert(1, &quot;Book3&quot;);
    book_hashmap.insert(1, &quot;Book4&quot;);

    println!(&quot;{:?}&quot;, book_hashmap.get(&amp;1)); // hashmap 에게 소유권을 넘기지 않고 reference 만 제공하기 위해 &amp;1 사용
}</code></pre>
<p>is_none 활용</p>
<pre><code class="language-rust">use std::collections::HashMap;

fn main(){
    let mut book_hashmap = HashMap::new();

    book_hashmap.insert(1, &quot;Book1&quot;);

    if book_hashmap.get(&amp;1).is_none() {
        book_hashmap.insert(1, &quot;Book1&quot;);
    } else {
        println!(&quot;Already got a book&quot;);
    }
}</code></pre>
<ul>
<li>if let 활용</li>
</ul>
<pre><code class="language-rust">use std::collections::HashMap;

fn main(){
    let mut book_hashmap = HashMap::new();

    book_hashmap.insert(1, &quot;Book1&quot;);

    if let Some(book_name) = book_hashmap.get(&amp;1) {
        println!(&quot;Already got a book : {}&quot;, book_name);
    } else {
        book_hashmap.insert(1, &quot;Book1&quot;);
    }
}</code></pre>
<h1 id="053">053</h1>
<p>Entry Method</p>
<pre><code class="language-rust">use std::collections::HashMap;

fn main(){
    let book_collection = vec![
        &quot;book1&quot;,
        &quot;book2&quot;,
        &quot;book3&quot;,
        &quot;book4&quot;,
        &quot;book4&quot;,
    ];

    let mut book_hashmap = HashMap::new();

    for book in book_collection{
        book_hashmap.entry(book).or_insert(true);
    }

    for (book, true_or_false) in book_hashmap{
        println!(&quot;Do we have {}? : {}&quot;, book, true_or_false);
    }
}</code></pre>
<p>.entry() method</p>
<pre><code class="language-rust">pub fn entry(&amp;mut self, key: K) -&gt; Entry&lt;K, V&gt;

pub enum Entry&lt;&#39;a, K: &#39;a, V: &#39;a&gt; {
    Occupied(OccupiedEntry&lt;&#39;a, K, V&gt;),
    Vacant(VacantEntry&lt;&#39;a, K, V&gt;),
}</code></pre>
<p>Entry.or_insert() 는 &amp;&#39;a mut 을 반환하기 때문에 값을 바꿀 수 있음</p>
<pre><code class="language-rust">use std::collections::HashMap;

fn main(){
    let book_collection = vec![
        &quot;book1&quot;,
        &quot;book2&quot;,
        &quot;book3&quot;,
        &quot;book4&quot;,
        &quot;book4&quot;,
    ];

    let mut book_hashmap = HashMap::new();

    for book in book_collection{
        let number_of_books = book_hashmap.entry(book).or_insert(0); // number_of_books -&gt; &amp;ref
        *number_of_books += 1;
    }

    for (book, number) in book_hashmap{
        println!(&quot;{}: {}copies&quot;, book, number);
    }
}</code></pre>
<h1 id="054">054</h1>
<pre><code class="language-rust">use std::collections::HashMap;

fn main(){
    let data = vec![
        (&quot;male&quot;, 9),
        (&quot;female&quot;, 5),
        (&quot;male&quot;, 0),
        (&quot;female&quot;, 6),
        (&quot;female&quot;, 5),
        (&quot;male&quot;, 10),
    ];

    let mut survey_hash = HashMap::new();

    for item in data { // (&amp;str, i32)
        survey_hash.entry(item.0).or_insert(Vec::new()).push(item.1);
    }

    for (male_or_female, numbers) in survey_hash{
        println!(&quot;{:?}, {:?}&quot;, male_or_female, numbers)
    }
}</code></pre>
<p>HashSet</p>
<pre><code class="language-rust">use std::collections::HashSet;

fn main(){
    let many_numbers = vec![1,3,5,7,7,9];

    let mut number_hashset = HashSet::new();

    for number in many_numbers {
        number_hashset.insert(number);
    }

    let hashset_length = number_hashset.len(); // hashset length
    println!(&quot;There are {} unique numbers, so we are missing {}&quot;, hashset_length, 10-hashset_length);

    let mut missing_vec = vec![];
    for number in 0..10{
        if number_hashset.get(&amp;number).is_none() {
            missing_vec.push(number);
        }
    }

    print!(&quot;Missing numbers : &quot;);
    for number in missing_vec{
        print!(&quot;{} &quot;, number);
    }
}</code></pre>
<p>Set 에서도 Map과 동일하게 순서가 중요하다면 BTreeSet 사용!</p>
<h1 id="055">055</h1>
<p>binaryHeap : 아직 실제로 써본적은 없다고 함</p>
<pre><code class="language-rust">use std::collections::BinaryHeap;

fn show_remainder(input: &amp;BinaryHeap&lt;i32&gt;) -&gt; Vec&lt;i32&gt; {
    let mut remainder_vec = vec![];
    for number in input {
        remainder_vec.push(*number)
    }
    remainder_vec
}

fn main(){
    let many_numbers = vec![0, 5, 10, 15, 20, 25, 30];

    let mut my_heap = BinaryHeap::new();

    for number in many_numbers{
        my_heap.push(number);
    }

    while let Some(number) = my_heap.pop(){
        println!(&quot;Popped off {}. Remaining numbers are : {:?}&quot;, number, show_remainder(&amp;my_heap));
    }
}
/* Output
Popped off 30. Remaining numbers are : [25, 15, 20, 0, 10, 5]
Popped off 25. Remaining numbers are : [20, 15, 5, 0, 10]
Popped off 20. Remaining numbers are : [15, 10, 5, 0]
Popped off 15. Remaining numbers are : [10, 0, 5]
Popped off 10. Remaining numbers are : [5, 0]
Popped off 5. Remaining numbers are : [0]
Popped off 0. Remaining numbers are : []
*/</code></pre>
<p>-&gt; 첫 번째 요소는 가장 큰 값, 나머지 요소들의 순서는 일관되지 않음</p>
<p>Most popular Usage : Priority Queue</p>
<p>예시 2</p>
<pre><code class="language-rust">use std::collections::BinaryHeap;

fn main(){
    let mut jobs = BinaryHeap::new();

    jobs.push((100, &quot;Write back to email from the CEO&quot;));
    jobs.push((80, &quot;보고서 마감&quot;));
    jobs.push((5, &quot;Youtube 시청&quot;));
    jobs.push((70, &quot;동료들에게 감사인사 전하기&quot;));
    jobs.push((30, &quot;다음 고용계획 작성&quot;));

    while let Some(job) = jobs.pop(){
        println!(&quot;You need to: {}&quot;, job.1);
    }
}</code></pre>
<h1 id="056">056</h1>
<p>VecDeque</p>
<p>Vec의 경우</p>
<pre><code class="language-rust">fn main(){
    let my_vec = vec![8,9,10];
}</code></pre>
<p>.pop : 맨 마지막값을 뺌
.push : 맨 마지막에 값을 넣음
Vec::with_capacity(10) : reallocation 없이 빠르게 사용</p>
<p>.remove(idx) : idx 의 값을 빼고 나머지 값들의 idx 를 한자리씩 앞으로 이동시킴</p>
<p>다음 상황에서 연산량이 많아서 오래걸리는 처리를 VecDeque 를 사용해서 빠르게 처리할 수 있음</p>
<pre><code class="language-rust">// 오래 걸리는 코드
fn main(){
    let mut my_vec = vec![0; 600_000]; // 0이 60만개있는 vec
    for i in 0..600_000 {
        my_vec.remove(0);
    }
}

// VecDeque 사용
use std::collections::VecDeque;

fn main(){
    let mut my_vec = VecDeque::from(vec![0; 600_000]);
    for i in 0..600_000{
        my_vec.pop_front();
    }
}</code></pre>
<p>Ring buffer 를 이용해서...........빠름</p>
<h1 id="057">057</h1>
<p>?</p>
<p><code>use std::num::ParseIntError</code>
&quot;8&quot; 같은 값을 int로 변환, &quot;8eo&quot;같은 값은 에러 반환</p>
<pre><code class="language-rust">use std::num::ParseIntError;

fn parse_str(input:&amp;str) -&gt; Result&lt;i32, ParseIntError&gt;{
    let parsed_number = input.parse::&lt;i32&gt;()?; // 성공하면 넘어감 실패하면 return error
    Ok(parsed_number)
}

fn main(){
    for item in vec![&quot;seven&quot;, &quot;8&quot;, &quot;9.0&quot;, &quot;nice&quot;,&quot;6060&quot;]{
        let parsed = parse_str(item);
        println!(&quot;{:?}&quot;, parsed);
    }
}

/*
Err(ParseIntError { kind: InvalidDigit })
Ok(8)
Err(ParseIntError { kind: InvalidDigit })
Err(ParseIntError { kind: InvalidDigit })
Ok(6060)
*/</code></pre>
<h1 id="058">058</h1>
<p><a href="https://play.rust-lang.org/">https://play.rust-lang.org/</a>
에서 monaco editor 지원 시작
-&gt; 인텔리센스(자동완성) 가능</p>
<p>format Strings</p>
<p>기존에는 다음과같이 써야만 했다면
<code>println!(&quot;Books\n- {:?}\n- {:?}, book1, book2)</code></p>
<p>업데이트 이후에는 다음과같이 쓸 수 있게 됨
<code>println!(&quot;Books\n- {book1:?}\n- {book2:?})</code></p>
<pre><code class="language-rust">#[derive(Debug)]
struct Book{
    title: String,
    year: u16
}

fn main(){
    let book1 = Book{
        title : &quot;Some title&quot;.to_string(),
        year: 1919
    };
    let book2 = Book{
        title : &quot;Book2&quot;.to_string(),
        year: 2020
    };

    println!(&quot;Books\n{book1:?}\n{book2:?}&quot;);

    /* 아직 struct 안의 property 는 지원안함 */
    // println!(&quot;My book name : {book1.title});
}</code></pre>
<h1 id="059-60">059, 60</h1>
<p>traits (다른 언어의 interface와 비슷)</p>
<pre><code class="language-rust">struct Animal {
    name : String
}

trait Canine {
    fn bark(&amp;self){
        println!(&quot;Woof Woof!&quot;);
    }
    fn run(&amp;self){
        println!(&quot;I am running!&quot;);
    }
}

impl Canine for Animal{
    // 덮어쓰기 가능
    fn bark(&amp;self){
        println!(&quot;멍멍!&quot;);
    }
}

fn main(){
    let my_animal = Animal{
        name : &quot;Mr. Mantle&quot;.to_string()
    };

    my_animal.bark();
    my_animal.run();

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Easy Rust Korean 031 ~ 040
]]></title>
            <link>https://velog.io/@na_treeeum/Easy-Rust-Korean-031-040</link>
            <guid>https://velog.io/@na_treeeum/Easy-Rust-Korean-031-040</guid>
            <pubDate>Sun, 16 Nov 2025 13:31:19 GMT</pubDate>
            <description><![CDATA[<h1 id="031">031</h1>
<p>match 활용!</p>
<pre><code class="language-rust">fn match_colours(rgb: (u32, u32, u32)){
    match rgb{
        (r, _, _) if r &lt; 10 =&gt; println!(&quot;Not much red!&quot;),
        (_, g, _) if g &lt; 10 =&gt; println!(&quot;Not much green!&quot;),
        (_, _, b) if b &lt; 10 =&gt; println!(&quot;Not much blue!&quot;),
        _ =&gt; println!(&quot;Every colour has at least 10!&quot;)
    }
}

fn main(){
    let first = (200, 0, 0);
    let second = (50, 50, 50);
    let third = (200, 50, 0);

    match_colours(first);
    match_colours(second);
    match_colours(third);
}</code></pre>
<p>다음과 같이 여러 type을 혼용해서 사용할 수 없음</p>
<pre><code class="language-rust">fn main(){
    let my_number = 10;
    let some_var = match my_number {
        10 =&gt; 8,
        _ =&gt; &quot;Not ten&quot;
    };
}</code></pre>
<p>if 를 사용해도 안되는건 마찬가지</p>
<pre><code class="language-rust">fn main(){
    let my_number = 8;
    let some_var = if my_number == 10 {8} else {
        &quot;something else&quot;
    };
}</code></pre>
<p>return 하는 값의 type을 맞추면 가능!</p>
<pre><code class="language-rust">fn main(){
    let my_number = 10;
    let some_var = if my_number == 10 {&quot;ten&quot;} else {
        &quot;else&quot;
    };
    println!(&quot;{}&quot;, some_var);
}</code></pre>
<p>다음과 같이도 사용 가능</p>
<pre><code class="language-rust">fn match_number(input: i32){
    match input{
        number @ 0..=10 =&gt; println!(&quot;It&#39;s between 0 and 10. Number : {}&quot;, number),
        _ =&gt; println!(&quot;It&#39;s greater than ten&quot;)
    }
}

fn main(){
    match_number(10);
    match_number(50);
}</code></pre>
<h1 id="032">032</h1>
<p>Struct!
(나중에 비슷한 enum 도 있음)</p>
<p>rust에는 3가지 struct가 있음</p>
<pre><code class="language-rust">// unit struct ( 아무것도 없는 구조체 )
struct FileDirectory;

fn main(){
    let x = FileDirectory;
    println!(&quot;The size of struct is {}&quot;, std::mem::size_of_val(&amp;x)); // size : 0
}</code></pre>
<pre><code class="language-rust">// tuple struct
struct Colour(u8, u8, u8);

fn main(){
    let my_colour = Colour(20, 50, 100);
    println!(&quot;The second colour is {}&quot;, my_colour.1); 
}</code></pre>
<pre><code class="language-rust">// named struct : 제일 많이 사용됨
struct Country{
    population: u32,
    capital : String,
    leader_name : String
}

fn main(){
    let canada = Country{
        population : 35_000_000,
        capital: &quot;Ottawa&quot;.to_string(),
        leader_name: &quot;Justin Trudeau&quot;.to_string()
    };

    println!(&quot;The population is {}&quot;, canada.population);
}</code></pre>
<h1 id="033">033</h1>
<p>Struct Size</p>
<pre><code class="language-rust">struct Country {
    population: u32,
    capital: String,
    leader_name: String
}

fn main(){
    let population = 35_000_000;
    let capital = &quot;Ottawa&quot;.to_string();
    let leader_name = &quot;Justin Trudeau&quot;.to_string();

    let my_country = Country {
        population,
        capital,
        leader_name
    };
}</code></pre>
<p>-&gt; js와 비슷한 기능!</p>
<p><code>use std::mem::size_of_val</code> 을 사용해서 struct의 사이즈를 볼 수 있음</p>
<pre><code class="language-rust">use std::mem::size_of_val;

struct Country {
    population: u32,
    capital: String,
    leader_name: String
}

fn main(){
    let population = 35_000_000;
    let capital = &quot;Ottawa&quot;.to_string();
    let leader_name = &quot;Justin Trudeau&quot;.to_string();

    let my_country = Country {
        population,
        capital,
        leader_name
    };

    println!(&quot;Country is {} bytes in size&quot;, size_of_val(&amp;my_country));
}</code></pre>
<p>Alignment</p>
<pre><code class="language-rust">use std::mem::size_of_val;

struct Numbers{
    one: u8,
    two: u8,
    three: u8
}

fn main(){
    let my_numbers = Numbers{
        one : 1,
        two : 2,
        three : 3
    };

    println!(&quot;Size of struct : {}&quot;, size_of_val(&amp;my_numbers));
} // -&gt; size : 3 (bytes)</code></pre>
<pre><code class="language-rust">use std::mem::size_of_val;

struct Numbers{
    one: u8,
    two: u8,
    three: u8,
    four: u32
}

fn main(){
    let my_numbers = Numbers{
        one : 1,
        two : 2,
        three : 3,
        four: 4
    };

    println!(&quot;Size of struct : {}&quot;, size_of_val(&amp;my_numbers));
} // -&gt; size : 8 (bytes)</code></pre>
<p>왜 size 가 7 이 아닌 8이냐면 러스트가 바이트수를 맞추기 위해........align하는...부분..</p>
<h1 id="034">034</h1>
<p>ENUM</p>
<p>struct 는 and 의 성격
enum 은 or 의 성격</p>
<pre><code class="language-rust">enum ThingsInTheSky{
    Sun, // compiler 가 보기에 0
    Stars // compiler 가 보기에 1
}

fn create_sky_state(time: i32) -&gt; ThingsInTheSky{
    match time{
        6..=18 =&gt; ThingsInTheSky::Sun,
        _ =&gt; ThingsInTheSky::Stars
    }
}

fn check_sky_state(state: &amp;ThingsInTheSky){
    match state{
        ThingsInTheSky::Sun =&gt; println!(&quot;I can see the sun&quot;),
        ThingsInTheSky::Stars =&gt; println!(&quot;I can see the stars&quot;),
    }
}

fn main(){
    let time = 8;
    let sky_state = create_sky_state(time);

    check_sky_state(&amp;sky_state);
}</code></pre>
<h1 id="035">035</h1>
<p>ENUM2 : 간편하게 사용하기</p>
<pre><code class="language-rust">enum Mood{
    Happy,
    Sleepy,
    NotBad,
    Angry
}

fn match_mood(mood: &amp;Mood) -&gt; i32{
    let happiness_level = match mood {
        Mood::Happy =&gt; 10,
        Mood::Sleepy =&gt; 6,
        Mood::NotBad =&gt; 7,
        Mood::Angry =&gt; 2
    };
    happiness_level
}

fn main(){
    let my_mood = Mood::NotBad;
    let happiness_level = match_mood(&amp;my_mood);
    println!(&quot;Out of 1 to 10, my happiness is {}&quot;, happiness_level);
}</code></pre>
<p>이거를 <code>use</code>를 사용해서 쉽게 쓸 수 있음</p>
<pre><code class="language-rust">enum Mood{
    Happy,
    Sleepy,
    NotBad,
    Angry
}

fn match_mood(mood: &amp;Mood) -&gt; i32{
    use Mood::*;
    let happiness_level = match mood {
        Happy =&gt; 10, // Mood:: 안써도 됨 
        Sleepy =&gt; 6, // Mood:: 안써도 됨 
        NotBad =&gt; 7, // Mood:: 안써도 됨 
        Angry =&gt; 2 // Mood:: 안써도 됨 
    };
    happiness_level
}

fn main(){
    let my_mood = Mood::NotBad;
    let happiness_level = match_mood(&amp;my_mood);
    println!(&quot;Out of 1 to 10, my happiness is {}&quot;, happiness_level);
}</code></pre>
<p>enum의 인덱스(?)를 보려면 </p>
<pre><code class="language-rust">enum Season{
    Spring, // 0
    Summer, // 1
    Autumn, // 2
    Winter //3
}

fn main(){
    use Season::*;

    let four_seasons = vec![Spring, Summer, Autumn, Winter]; // Vec&lt;Season&gt;
    for season in four_seasons {
        println!(&quot;The number is: {}&quot;, season as u32);
    }
}</code></pre>
<h1 id="036">036</h1>
<p>ENUM3</p>
<pre><code class="language-rust">enum Star{
    BrownDwarf = 10,
    RedDwarf = 50,
    YellowStar = 100,
    RedGiant = 1000,
    DeadStar 
}

fn main(){
    use Star::*;

    let starvec = vec![BrownDwarf, RedDwarf, YellowStar, RedGiant, DeadStar];

    for star in starvec{
        match star as u32{
        size if size &lt;= 80 =&gt; println!(&quot;Not the biggest star: {}&quot;, size),
        size if size &gt;= 80 =&gt; println!(&quot;Pretty big star: {}&quot;, size),
        _ =&gt; println!(&quot;Some other star&quot;)
        }
    };

    println!(&quot;What about DeadStar? It is: {}&quot;, DeadStar as u32); // 제일 마지막 숫자 + 1
}</code></pre>
<p>enum 이 값을 가지게 할수있음</p>
<pre><code class="language-rust">enum Number{
    U32(u32),
    I32(i32)
}

fn get_number(input: i32) -&gt; Number {
    let number = match input.is_positive(){
        true =&gt; Number::U32(input as u32),
        false =&gt; Number::I32(input)
    };
    number
}

fn main(){
    let my_vec = vec![get_number(-800), get_number(8)];

    for item in my_vec{
        match item {
            Number::U32(number) =&gt; println!(&quot;It&#39;s a u32 with the value : {}&quot;, number),
            Number::I32(number) =&gt; println!(&quot;It&#39;s a i32 with the value : {}&quot;, number),
        }
    }
}</code></pre>
<h1 id="037">037</h1>
<p>Loops</p>
<p>기본적인 loop</p>
<pre><code class="language-rust">// rust 스럽지 않은 방법!
fn main(){
    let mut counter = 0;
    loop{
        counter += 1;
        println!(&quot;The counter is: {}&quot;, counter);
        if counter == 5 {
            break;
        }
    }
}</code></pre>
<p>loop에 이름을 지을 수 있음! (tick 이라고 함)</p>
<pre><code class="language-rust">fn main(){
    let mut counter = 0;
    let mut counter2 = 0;

    &#39;first_loop: loop {
        counter += 1;
        println!(&quot;The counter is now: {}&quot;, counter);
        if counter &gt; 9 {
            println!(&quot;Now entering the second loop&quot;);

            &#39;second_loop: loop{
                println!(&quot;The second counter is: {}&quot;, counter2);
                counter2 += 1;
                if counter2 == 3 {
                    break &#39;first_loop;
                }
            }
        }
    }
}    </code></pre>
<h1 id="038">038</h1>
<p>counter.. 만들어서 하면 rust 적이지 않은 loop!</p>
<p>그럼 어케?</p>
<pre><code class="language-rust">fn main(){
    let mut counter = 0;
    while counter != 5 {
        counter += 1;
        println!(&quot;The counter is now: {}&quot;, counter);
    }
}</code></pre>
<p>while loop 도 괜찮긴 하지만 제일 많이 사용하는건 for loop</p>
<pre><code class="language-rust">fn main(){
    for number in 0..3 { // 0..3 -&gt; exclusive range!
        println!(&quot;The number is {}&quot;, number);
    }

    for number in 0..=3 { // 0..3 -&gt; inclusive range!
        println!(&quot;The number is {}&quot;, number);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Easy Rust Korean 021 ~ 030]]></title>
            <link>https://velog.io/@na_treeeum/Easy-Rust-Korean-021-030</link>
            <guid>https://velog.io/@na_treeeum/Easy-Rust-Korean-021-030</guid>
            <pubDate>Sun, 09 Nov 2025 13:35:16 GMT</pubDate>
            <description><![CDATA[<h1 id="021">021</h1>
<p>mutable references and mut in function</p>
<pre><code class="language-rust">fn add_is_great(country_name: &amp;mut String){
    country_name.push_str(&quot; is great!&quot;);
    println!(&quot;Now it says: {}&quot;, country_name);
}

fn main(){
    let mut my_country = &quot;캐나다&quot;.to_string();

    /*
    add_is_great(my_country); // by value
    add_is_great(&amp;my_country); // by reference
    */
    add_is_great(&amp;mut my_country); // by mutable reference
    add_is_great(&amp;mut my_country);
}

/* Output
Now it says: 캐나다 is great!
Now it says: 캐나다 is great! is great!
*/</code></pre>
<p>비슷하지만 완전 다른 개념!</p>
<pre><code class="language-rust">fn add_is_great(mut country_name: String){ // take by value, declare as mutable
    country_name.push_str(&quot; is great!&quot;);
    println!(&quot;Now it says: {}&quot;, country_name);
}

fn main(){
    let my_country = &quot;대한민국&quot;.to_string();
    add_is_great(my_country); // 여기서 소유권이 넘어감!
}</code></pre>
<h1 id="022">022</h1>
<p>Ownership and copy types</p>
<pre><code class="language-rust">fn prints_number(number: i32){
    println!(&quot;{}&quot;, number);
}

fn prints_string(input: String){
    println!(&quot;{}&quot;, input);
}

fn main(){
    let my_number = 8;
    prints_number(my_number); // number 는 copy type(copy 하기 쉬움)이라 소유권 신경쓸 필요 없음!
    prints_number(my_number);

    let my_country = &quot;Austria&quot;.to_string();
    prints_string(my_country); // 여기서 소유권 넘어감!
    prints_string(my_country); // 그래서 여기서 오류남!

    /*
        오류가 안나게 하려면 .clone 메서드를 사용
        prints_string(my_country.clone());

        clone은 빠르게 컴파일을 하고싶을 때 많이 사용함!
    */

}</code></pre>
<h1 id="023">023</h1>
<p>uninitialized variable &amp; loop</p>
<pre><code class="language-rust">fn main(){
    /*
    let my_number; // 는 불가능!
    */

    /*
    let my_number: i8; // 는 사용은 가능!

    println!(&quot;{}&quot;, my_number); // 하지만 여기서 에러남!! value가 initialized 되지 않았기 때문
    */

}</code></pre>
<pre><code class="language-rust">fn loop_then_return(mut counter: i32) -&gt; i32 {
    loop {
        counter += 1;
        if counter % 50 == 0 {
            break;
        }
    }
    counter
}

fn main(){
    let my_number;

    {
        let x = loop_then_return(43);
        my_number = x
    }

    println!(&quot;{}&quot;, my_number); // my_number가 위에서 initialized 됐기 때문에 오류가 발생하지 않음
}</code></pre>
<p>하지만 이 패턴은 잘 안사용!
기존의 코드(C 등등..)을 사용하던 개발자들의 편의를 위해 rust에서 허용하는 기능!</p>
<h1 id="024">024</h1>
<p>Arrays</p>
<pre><code class="language-rust">fn main(){
    let array = [&quot;one&quot;, &quot;two&quot;]; // type -&gt; [&amp;str; 2]
    let array2 = [&quot;one&quot;, &quot;two&quot;, &quot;Five&quot;]; // type -&gt; [&amp;str; 3]]

    // array 와 array2 는 다른 타입이라 비교 자체가 불가능!!
    // array 는 추가할 수 없고, 삭제할 수 없고.. 
}</code></pre>
<pre><code class="language-rust">fn main(){
    let array = [&quot;one&quot;, &quot;two&quot;];

    //type을 보기 위한 꼼수 : 존재하지 않는 method 사용 ㅋㅋ
      array.zxcvzxcvasdfasdfasd(); // &gt;&gt; 여기서 [type]에 대한 method 가 없습니다!  라고 에러를 뱉음
}</code></pre>
<p>indexing 은 다른 언어와 비슷!</p>
<pre><code class="language-rust">fn main(){
    let array = [&quot;one&quot;, &quot;two&quot;];

    println!(&quot;{}&quot;, array[0]); // &quot;one&quot; 출력

    /*
        없는 index 출력하면 오류남 !
    */
}</code></pre>
<h1 id="025">025</h1>
<p>Slices</p>
<pre><code class="language-rust">fn main(){
    let seasons = [&quot;봄&quot;, &quot;여름&quot;, &quot;가을&quot;, &quot;겨울&quot;];
    println!(&quot;{:?}&quot;, seasons[0..2]); // 에러 발생!! dynamically sized type 이라 사이즈를 알 수 없기 때문에 오류가 발생함
}</code></pre>
<pre><code class="language-rust">fn main(){
    let seasons = [&quot;봄&quot;, &quot;여름&quot;, &quot;가을&quot;, &quot;겨울&quot;];
    println!(&quot;{:?}&quot;, &amp;seasons[0..2]); // reference 를 사용해서 넘길 수 있음!

    /*
        &amp;seasons[0..=2] // up to and including
        &amp;seasons[..] // all
        &amp;seasons[3..] // from 4th index
        &amp;seasons[..4] // up to 5th index
    */
}</code></pre>
<h1 id="026">026</h1>
<p>&quot;누구나 좋아하는&quot; Vecs</p>
<p>= vector</p>
<pre><code class="language-rust">fn main(){
    let my_string = String::new(); // string 선언!
    let mut my_vec: Vec&lt;String&gt; = Vec::new(); 
}</code></pre>
<pre><code class="language-rust">fn main(){
    let name1 = String::from(&quot;Windy); 
    let name2 = String::from(&quot;Gomesy&quot;);

    let mut my_vec = Vec::new();
    // Vec::from() 도 사용가능
    /*
        vec![] 로 더 쉽게 사용가능!

        예를 들어
        let my_vec = vec![name1, name2];
    */

    my_vec.push(name1);
    my_vec.push(name2);

    println!(&quot;My cats are {:?}&quot;, my_vec);
}</code></pre>
<h1 id="027">027</h1>
<p>Traits = 초능력</p>
<pre><code class="language-rust">fn main(){
    let my_name = String::from(&quot;Dave Macleod&quot;); // from 사용!
    let my_city: String = &quot;Seoul&quot;.into(); // into 사용!! &quot;Seoul&quot;은 String이 아닌 &amp;str 인데 String 변수에 맞게 변환!?
}</code></pre>
<pre><code class="language-rust">fn main(){
    let my_vec = Vec::from([8,9,10]); // type : [i32; 3]
}</code></pre>
<h1 id="028">028</h1>
<p>tuples : 여러가지 타입을 한 가지 타입으로 표현하고 싶을 때 사용!</p>
<pre><code class="language-rust">fn main(){
    let my_tuple = (8, &quot;Dave MacLeod&quot;, vec![8,9,10]);

    my_tuple.asdfasdfasdf(); // 없는 method 로 컴파일러에게 타입 물어보기 ㅋㅋ 
    // -&gt; no method named `asdfasdfasdf` found for tuple `({integer}, &amp;str, Vec&lt;{integer}&gt;)`
    // type : tuple
}</code></pre>
<p>tuple indexing</p>
<pre><code class="language-rust">/*
    Array 와 Vector 에서 indexing 은 array[0], vec[0] 이런식으로 대괄호를 사용해서 인덱싱
    tuple은 대괄호를 사용하지 않고 .0 .1 이런식으로 사용
*/

fn main(){
    let random_tuple = (&quot;Here is a name&quot;, 8, vec![&#39;a&#39;], &#39;b&#39;, [8,9,10], 7.7);

    println!(&quot;Inside the tuple&quot;);
    println!(&quot;0 : {:?}&quot;, random_tuple.0);
    println!(&quot;1 : {:?}&quot;, random_tuple.1);
    println!(&quot;2 : {:?}&quot;, random_tuple.2);
    println!(&quot;3 : {:?}&quot;, random_tuple.3);
    println!(&quot;4 : {:?}&quot;, random_tuple.4);
    println!(&quot;5 : {:?}&quot;, random_tuple.5);
}</code></pre>
<p>여기서 재밌는 점은, Vector의 경우 같은 type의 요소들만 가질 수 있으나 다음과 같이 여러개의 타입을 묶어서 사용할 수도 있음</p>
<pre><code class="language-rust">fn main(){
    let my_vec = vec![(&quot;hey&quot;,9), (&quot;Hello there&quot;, 123123)];
    println!(&quot;{:?}&quot;, my_vec)
}</code></pre>
<p>Destructuring! -&gt; js의 분해구조할당과 비슷</p>
<pre><code class="language-rust">fn main(){
    let str_tuple = (&quot;one&quot;, &quot;two&quot;, &quot;three&quot;);

    let (a,b,c) = str_tuple;

    println!(&quot;Item &#39;a&#39; is : {}&quot;, a);
}</code></pre>
<p>array 도 똑같이 분해 가능!</p>
<pre><code class="language-rust">fn main(){
    let str_array = [&quot;one&quot;, &quot;two&quot;, &quot;three&quot;];

    let [a,_,_] = str_array; // 관심없는 b, c 대신 _ 사용 가능 -&gt; 워닝 안뜸

    println!(&quot;Item &#39;a&#39; is : {}&quot;, a);
}</code></pre>
<h1 id="029">029</h1>
<p>흐름제어</p>
<p>다른언어에서 if 문을 사용할 때 조건은 () 안에 넣는데 러스트는 괄호를 쓸 필요 없음 (에러는 아님)</p>
<pre><code class="language-rust">fn main(){
    let my_number = 5;
    if my_number == 7 {
        println!(&quot;It&#39;s seven&quot;);
    } else if my_number == 6 {
        println!(&quot;It&#39;s six&quot;);
    } else {
        println!(&quot;It&#39;s a different number&quot;);
    }
}</code></pre>
<p>&amp;&amp; || 도 동일하게 사용 가능</p>
<pre><code class="language-rust">fn main(){
    let my_number = 5;
    if my_number == 5 &amp;&amp; my_number == 11 {
        println!(&quot;Both are matched&quot;);
    } else if my_number == 5 || my_number == 11 {
        println!(&quot;my number is 5 or 11&quot;);
    } else {
        println!(&quot;It&#39;s a different number&quot;);
    }
}</code></pre>
<p>하지만 이것보다 더 자주 쓰이는건 &quot;match&quot;</p>
<p>-&gt; switch 와 비슷</p>
<pre><code class="language-rust">fn main(){
    let my_number: u8 = 5;

    match my_number{
        0 =&gt; println!(&quot;It&#39;s a zero&quot;),
        1 =&gt; println!(&quot;It&#39;s a one&quot;)
    }
} //=&gt;에러남. u8 은 256까지 가능한데 나머지에 대한 정의가 안돼있기 때문 </code></pre>
<pre><code class="language-rust">fn main(){
    let my_number: u8 = 5;

    match my_number{
        0 =&gt; println!(&quot;It&#39;s a zero&quot;),
        1 =&gt; println!(&quot;It&#39;s a one&quot;),
        _ =&gt; println!(&quot;Other number&quot;); // _ 기호를 사용해서 나머지 타입(else)에 대한 정의
    }
} </code></pre>
<p>match 로 변수에 값을 할당할 수도 있음</p>
<pre><code class="language-rust">fn main(){
    let my_number: u8 = 5;

    let my_second_number = match my_number{
        0 =&gt; 23,
        1 =&gt; 34,
        _ =&gt; 0
    };

    println!(&quot;{}&quot;, my_second_number);
} </code></pre>
<h1 id="030">030</h1>
<p>tuple 활용</p>
<p>여러개의 변수를 한번에 비교할 수도 있음!</p>
<pre><code class="language-rust">fn main(){
    let sky = &quot;cloudy&quot;;
    let temperature = &quot;warm&quot;;

    match (sky, temperature){
        (&quot;cloudy&quot;, &quot;cold&quot;) =&gt; println!(&quot;It&#39;s not very nice day&quot;),
        (&quot;cloudy&quot;, &quot;warm&quot;) =&gt; println!(&quot;It&#39;s a nice day&quot;),
        (&quot;cloudy&quot;, _) =&gt; println!(&quot;Cloudy and something else&quot;),
        _ =&gt; println!(&quot;Not sure what the weather is&quot;)
    }
}</code></pre>
<p>조건문도 사용 가능</p>
<pre><code class="language-rust">fn main(){
    let children = 5;
    let married = true;

    match(children, married){
        (children, married) if married == false =&gt; println!(&quot;Not married with {} children&quot;, children),
        (c,m) if c==0 &amp;&amp; m =&gt; println!(&quot;Married but with no children&quot;),
        _ =&gt; println!(&quot;Some other type of marriage and children combination&quot;)
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Easy Rust Korean 011 ~ 020]]></title>
            <link>https://velog.io/@na_treeeum/Easy-Rust-Korean-011-020</link>
            <guid>https://velog.io/@na_treeeum/Easy-Rust-Korean-011-020</guid>
            <pubDate>Sun, 09 Nov 2025 13:35:11 GMT</pubDate>
            <description><![CDATA[<h1 id="011">011</h1>
<h2 id="mutability--shadowing">mutability &amp; shadowing</h2>
<p>mutability : 변경가능여부</p>
<p>기본적으로 immutable (변수 선언 시 기본적으로 변경 불가능)</p>
<pre><code class="language-rust">fn main(){
    let my_number = 10;
    my_number = 9;
}

&gt;&gt;&gt; ERROR</code></pre>
<p>my_number 는 immutable 이기 때문에 에러 발생
이 경우 <code>mut</code> 키워드를 사용해서 변경가능할 수 있게 해줘야함</p>
<pre><code class="language-rust">fn main(){
    let mut my_number = 10;
    my_number = 9;
}</code></pre>
<p><code>mut</code> 키워드를 사용하고나서 변경되지 않았다면, 컴파일러가 이를 알려주고, mut 키워드 삭제 가능</p>
<p>shadowing : 같은 이름을 다시 쓰는 것</p>
<pre><code class="language-rust">fn main(){
    let my_variable = 10;
       let my_variable = &quot;My Variable&quot;;
}

// -&gt; my_variable 은 마지막에 작성된 &quot;My Variable&quot;만 값으로 생각함.</code></pre>
<p>왜 사용?
코딩을 하면서 필요한 값은 최종 결과인데, 이를 위한 여러 변수들이 불필요하게 선언될 필요 없음</p>
<pre><code class="language-rust">fn double(input: i32) -&gt; i32 {
    input * 2
}

fn triple(input: i32) -&gt; i32 {
    input * 3
}

fn main(){
    let x_origin = 9;
       let x_double = double(x_origin);
    let x_final = triple(x_double);

    println!(&quot;{}&quot;, x_final);
}

// -&gt; 결과적으로 필요한 값은 xFinal 인데, xOrigin, xDouble 같은 변수가 선언됨

//-----------Shadowing 으로 다음과같이 사용할 수 있음-----------
fn main(){
    let x = 9;
       let x = double(x);
    let x = triple(x);

    println!(&quot;{}&quot;, x);
}</code></pre>
<p>CodeBlock 을 이용해서 Shadowing을 다양하게 활용할 수 있음</p>
<pre><code class="language-rust">fn main(){
    let my_var = 9;
    {
        let my_var = &quot;Some String&quot;;
        println!(&quot;{}&quot;, my_var); // Some String 출력
    }
    println!(&quot;{}&quot;, my_var); // 9 출력
}</code></pre>
<h1 id="012">012</h1>
<h2 id="stack--heap--pointers">Stack / heap / pointers</h2>
<h3 id="stack-memory">Stack Memory</h3>
<p>먼저 들어온 Task가 다음에 들어온 Task가 종료될때까지 기다리는 메모리 구조.
Task가 종료되면 할당받은 Memory 는 반환</p>
<h3 id="heap-memory">Heap Memory</h3>
<p>창고같은 곳에서 &quot;어디로 가면 어떤 물건이 있어요&quot; 같이 동작하는 메모리 구조. 비교적 속도가 느리지만 많은 용량을 저장할 수 있음.</p>
<h3 id="pointers">Pointers</h3>
<p>아래와 같이 어떤 내용이 어디에 있는지 알려주는 역할</p>
<pre><code class="language-PlainText">Chapter
Chapter 1: My life, Page : 1 
Chapter 2: My life, Page : 15
Chapter 3: My life, Page : 23 </code></pre>
<p>Rust에서는 Pointer를 Reference 라고 부름.
reference 를 사용할 때에는 &quot;&amp;&quot; 기호를 사용함.</p>
<pre><code class="language-rust">fn main(){
    let my_number = 15; // 값. i32
    let single_reference = &amp;my_number; // my_number 에 대한 싱글 레퍼런스. &amp;i32
    let double_reference = &amp;single_reference; // my_number 에 대한 레퍼런스에 대한 레퍼런스. &amp;&amp;i32

    let five_references = &amp;&amp;&amp;&amp;&amp;my_number; // 이것도 사용 가능. 
}</code></pre>
<p>싱글레퍼런스, 더블레퍼런스, 모두 다른 타입이기 때문에 상호 비교가 불가능!!</p>
<h1 id="013">013</h1>
<p>Fancy Printing : print!() 사용</p>
<p>아래 두 줄의 문장은 같은 동작을 함</p>
<p>println!(message);
print!(&quot;{}\n&quot;,message);</p>
<p>예를들면</p>
<pre><code class="language-rust">fn main(){
    //#1
    println!(&quot;This&quot;);
    println!(&quot;is&quot;);
    println!(&quot;a&quot;);
    println!(&quot;message&quot;);

    //#2
    print!(&quot;This\n&quot;);
    print!(&quot;is\n&quot;);
    print!(&quot;a\n&quot;);
    print!(&quot;message\n&quot;);

    //#3
    print!(&quot;This\nis\na\nmessage\n&quot;);
}</code></pre>
<p>위의 1, 2, 3번 코두 동일한 동작을 함</p>
<pre><code class="language-PlainText">Output : 

This
is
a
message</code></pre>
<p><code>\n</code> 이라는 기호를 출력하고 싶다면? -&gt; <code>\\n</code> 사용</p>
<pre><code class="language-rust">fn main(){
    print!(&quot;This \\n is a message&quot;); // Output : This \n is a message
}</code></pre>
<p>그런데 <code>\</code>를 너무 많이 써야하는 상황이라면? 
예를들어 다음 디렉토리를 표시하려면
<code>c:\drive_a\new_drive\path_name</code></p>
<p>코드는 다음과 같이 안예뻐질 것</p>
<pre><code class="language-rust">fn main(){
    print!(&quot;c:\\drive_a\\new_drive\\path_name&quot;);
}</code></pre>
<p>이럴때 사용하는것이 &quot;Raw Text&quot;
다음과 같이 사용할 수 있다. </p>
<pre><code class="language-rust">fn main(){
    print!(r#&quot;c:\drive_a\new_drive\path_name&quot;#);
}</code></pre>
<p>줄바꿈을 다음과같이 사용할 수도 있는데</p>
<pre><code class="language-rust">fn main(){
    println!(&quot;Let me tell you 
    some story
    about
    rust&quot;);
}</code></pre>
<p>출력이 다음과 같다. </p>
<pre><code>Let met tell you
    some story
    about
    rust</code></pre><p>앞에 인덴트가 있기 때문에 그런것! 주의하여 사용할 것</p>
<p>전에 Debug Print 에 대해 잠시 다뤘는데 </p>
<pre><code class="language-rust">println!(&quot;{:?}&quot;, var_name);</code></pre>
<p>으로 사용했다. </p>
<p>왜 물음표로 사용하냐면 어떤 타입이든 출력을 하기 때문.</p>
<p>특정 data type을 출력하기 위해선 다음과 같은 format을 사용한다 </p>
<pre><code class="language-rust">fn main(){
    let my_number = 9000;
    println!(&quot;{:p}&quot;, &amp;my_number); // reference(pointer) 출력
    println!(&quot;{:X}&quot;, my_number); // Hex값 출력
    println!(&quot;{:b}&quot;, my_number); // Bytes값 출력

    println!(&quot;{:p}&quot;, my_number); // 는 오류 : p는 포인터 data type을 출력한다고 하는데, my_number 는 값이기 때문에
}</code></pre>
<p>이 포맷을 조금 더 다양하게 활용이 가능하다.</p>
<pre><code class="language-rust">fn main() {
    let title = &quot;TODAY&#39;S NEWS&quot;;
    println!(&quot;{:-^30}&quot;, title); // no variable name, pad with -, put in centre, 30 characters long
    let bar = &quot;|&quot;;
    println!(&quot;{: &lt;15}{: &gt;15}&quot;, bar, bar); // no variable name, pad with space, 15 characters each, one to the left, one to the right
    let a = &quot;SEOUL&quot;;
    let b = &quot;TOKYO&quot;;
    println!(&quot;{city1:-&lt;15}{city2:-&gt;15}&quot;, city1 = a, city2 = b); // variable names city1 and city2, pad with -, one to the left, one to the right
}</code></pre>
<p>Output: </p>
<pre><code>---------TODAY&#39;S NEWS---------
|                            |
SEOUL--------------------TOKYO</code></pre><p>더 많은 내용은 아래 링크 참고
<a href="https://doc.rust-lang.org/stable/std/fmt/">https://doc.rust-lang.org/stable/std/fmt/</a></p>
<h1 id="014">014</h1>
<p>Strings</p>
<p>String / &amp;str 을 주로 사용</p>
<pre><code class="language-rust">fn main(){
    let my_name = &quot;David&quot;; // &amp;str
    let other_name = String::from(&quot;David2&quot;);

    /*
    let my_other_name = &quot;David3&quot;; // &amp;str
    my_other_name.push(&quot;!&quot;); // 여기서 에러 발생!  &amp;str 에는 .push 가 없기 때문
    */


    // 다음과 같이 하면 오류 발생 안함
    let my_other_name = &quot;David3&quot;.to_string(); // String
    my_other_name.push(&quot;!&quot;);


    println!(&quot;{}&quot;, my_other_name);
}</code></pre>
<p>String type 은 Sized type
str = dynamic type -&gt; 길이를 컴파일러가 알 수 없음 =&gt; &amp;str을 사용</p>
<h1 id="015">015</h1>
<p>&amp;str 보다 String 이 사용하기 더 쉬운 이유!</p>
<p>method 사용이 용이함</p>
<p>.push : 한 글자 추가
.push_str : &amp;str 추가
.capacity : 바이트 크기
.pop : 
with_capacity : 바이트 크기 명시하여 String 선언</p>
<pre><code class="language-rust">let mut my_name = &quot;David&quot;.to_string();
my_name.push(&#39;!&#39;);
println!(&quot;My name is {}&quot;, my_name);</code></pre>
<pre><code class="language-rust">let mut my_name = String::with_capacity(26);
println!(&quot;Length is {} and capacity is {}&quot;, my_name.len(), my_name.capacity());
my_name.push_str(&quot;David!&quot;);
println!(&quot;Length is {} and capacity is {}&quot;, my_name.len(), my_name.capacity());
my_name.push_str(&quot;And I live in Seoul&quot;);
println!(&quot;Length is {} and capacity is {}&quot;, my_name.len(), my_name.capacity());
my_name.push(&#39;a&#39;);
println!(&quot;Length is {} and capacity is {}&quot;, my_name.len(), my_name.capacity());

/* Output : 
Length is 0 and capacity is 26
Length is 6 and capacity is 26
Length is 25 and capacity is 26
Length is 26 and capacity is 26
*/</code></pre>
<h1 id="016">016</h1>
<p>const / static</p>
<pre><code class="language-rust">fn main(){
    let x = 8; // &#39;let&#39; binding 이라고 함
}</code></pre>
<p>const는 Global 변수를 선언함</p>
<pre><code class="language-rust">const NUMBER: i32 = 20; // const 선언 변수명은 대문자로(소문자로 쓰면 warning), 타입 지정은 필수!

fn main(){
    let x = 8; // &#39;let&#39; binding 이라고 함
}</code></pre>
<p>static도 const와 비슷</p>
<pre><code class="language-rust">const NUMBER: i32 = 20; // const 선언 변수명은 대문자로(소문자로 쓰면 warning), 타입 지정은 필수!
static LOW_SCORE: i32 = 0;

/*
static 은 mut 를 통해 unsafe 를 사용할 수는 있으나 거~~의 안씀!!

static mut LOW_SCORE: i32 = 0;
*/

fn main(){
    let x = 8; // &#39;let&#39; binding 이라고 함
}</code></pre>
<h1 id="017">017</h1>
<p>OWNERSHIP</p>
<pre><code class="language-rust">fn main(){
    let country = String::from(&quot;대한민국&quot;);
    let ref_one = &amp;country;
    let ref_two = &amp;country;

    println!(&quot;Country is: {}&quot;, ref_one);
}</code></pre>
<pre><code class="language-rust">fn return_it() -&gt; &amp;String {
    let country = String::from(&quot;대한민국&quot;);
    &amp;country // ref 반환
}

fn main(){
    let my_country = return_it(); // &gt;&gt; 오류!!
}</code></pre>
<h1 id="018">018</h1>
<p>mutable references</p>
<p>&amp; -&gt; immutable reference / shared reference
&amp;mut -&gt; mutable reference / unique reference</p>
<pre><code class="language-rust">fn main(){
    let mut my_number = 9;
    let num_ref = &amp;mut my_number;

    /* 이렇게 코딩하면 오류남!! 
    num_ref = 10;
    */

    *num_ref = 10; // &amp;의 반대개념 *

    // &amp;&amp; 이면 ** 사용!
    /*
        let num_ref = &amp;mut &amp;mut my_number;
        **num_ref = 10;
    */
}</code></pre>
<h1 id="019">019</h1>
<p>references and shadowing</p>
<pre><code class="language-rust">// 오류 반환됨!!
fn main(){
    let mut number = 10;
    let number_ref = &amp;number;
    let number_change = &amp;met number;
    *number_change += 10;
}</code></pre>
<pre><code class="language-rust">// 정상 작동됨
fn main(){
    let mut number = 10;
    let number_change = &amp;met number;
    *number_change += 10;
    let number_ref = &amp;number;
}</code></pre>
<p>shadowing!</p>
<pre><code class="language-rust">// 정상 작동됨
fn main(){
    let country = &quot;대한민국&quot;;
    let country_ref = &amp; country;
    let country = 8; // shadowing

    println!(&quot;{}, {}&quot;, country_ref, country); 
    // -&gt; &quot;대한민국&quot; 이 없어지지 않았음을 확인할 수 있음!&quot;
}</code></pre>
<h1 id="020">020</h1>
<p>OWNERSHIP</p>
<pre><code class="language-rust">fn print_country(country_name: String){
    println!(&quot;My country is {}&quot;, country_name);
}

fn main(){
    let country = &quot;대한민국&quot;.to_string();
    print_country(country); // 여기서 country 의 소유권이 print_country 함수로 넘어감
    print_country(country); // 여기서 에러 발생!!
}</code></pre>
<pre><code class="language-rust">fn print_country(country_name: &amp;String){
    println!(&quot;My country is {}&quot;, country_name);
}

fn main(){
    let country = &quot;대한민국&quot;.to_string();
    print_country(&amp;country); // 여기서 country 의 소유권은 넘어가지 않음!
    print_country(&amp;country); // 에러 발생 X!
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Easy Rust Korean 001 ~ 010]]></title>
            <link>https://velog.io/@na_treeeum/Easy-Rust-001-010</link>
            <guid>https://velog.io/@na_treeeum/Easy-Rust-001-010</guid>
            <pubDate>Sun, 26 Oct 2025 02:26:27 GMT</pubDate>
            <description><![CDATA[<h1 id="001">001</h1>
<h2 id="웹-ide-rust전용">웹 IDE (rust전용)</h2>
<p><a href="https://play.rust-lang.org">https://play.rust-lang.org</a></p>
<ul>
<li>permalink to the playground : 코드를 포함한 ide 링크 복사가능</li>
</ul>
<h2 id="rust-의-장단점">Rust 의 장단점</h2>
<p>장점</p>
<ul>
<li>c, c++ 처럼 빠른속도</li>
</ul>
<p>단점</p>
<ul>
<li>어려움</li>
</ul>
<hr>
<h1 id="002">002</h1>
<h2 id="comments-주석">comments (주석)</h2>
<p>가장 기본적인 주석</p>
<pre><code class="language-rust">// This is normal comment</code></pre>
<p>Documentation 주석</p>
<pre><code class="language-rust">/// This is normal comment</code></pre>
<p>부분적인 주석</p>
<pre><code class="language-rust">fn main() {
    let x/*: i16*/ = 10;
}</code></pre>
<ul>
<li>사용하지 않은 변수에 대해 언더스코어(_) 로 이름을 시작하게 되면 미사용변수에 대한 Warning 이 없음</li>
</ul>
<h1 id="003">003</h1>
<h2 id="primitive-types--기본-데이터-타입">Primitive types : 기본 데이터 타입</h2>
<p>integer (정수, 음수 가능)</p>
<ul>
<li>i8, i16, i32, i64, i128, and isize</li>
</ul>
<p>unsigned integer (부호없는 정수, 음수 불가능)</p>
<ul>
<li>u8, u16, u32, u64, u128, and usize</li>
</ul>
<p>(i -&gt; integer || u -&gt; unsigned) + bit 수 로 표현</p>
<pre><code class="language-rust">let my_number = 100; </code></pre>
<p>변수 선언 시 let 사용</p>
<p>my_number 는 어떤 타입인지 알 수 없음</p>
<p>-&gt; u8타입을 지정하고 싶다면 다음과 같이 지정 가능</p>
<pre><code class="language-rust">let my_number: u8 = 100; </code></pre>
<h2 id="type-inference">type inference</h2>
<p>다음과 같은 코드에서는 컴파일러가 타입을 추론하여 계산함</p>
<pre><code class="language-rust">let my_number: u8 = 100; 
let my_other_number = 50; // type 미지정, 기본 : i32(or i64)
let _third_number = my_number + my_other_number;</code></pre>
<p>u8 + i64 연산은 기본적으로 안되지만, my_other_number 에 대해 타입지정을 안했기 때문에 컴파일러가 타입을 추론하여 연산할 수 있음.</p>
<p>다음과 같이 각 변수에 대해 타입이 지정돼있는 변수사이의 연산은 불가능</p>
<pre><code class="language-rust">let my_number: u8 = 100; 
let my_other_number: u16 = 50; // u16 지정
let _third_number = my_number + my_other_number;

&gt;&gt; ERROR</code></pre>
<h1 id="004">004</h1>
<h2 id="char">Char</h2>
<p>println!(String); 의 형태에서는 무조건 큰 따옴표 (&quot;&quot;)를 사용하고, Char type 변수를 선언할 때에는 작은 따옴표 (&#39;&#39;)를 사용</p>
<p>String : 문자열
Char : 문자</p>
<p>char type은 4bytes 크기</p>
<pre><code class="language-rust">println!(&quot;Hello, world!&quot;);
let first_letter = &#39;A&#39;;
let space = &#39; &#39;;</code></pre>
<h2 id="casting">Casting</h2>
<blockquote>
<p>simple type change using &#39;as&#39;</p>
</blockquote>
<pre><code class="language-rust">let my_number: u16 = 8;
let second_number: u8 = 10;
let third_number = my_number + second_number as u16;</code></pre>
<p>Char -&gt; u8 으로 캐스팅 하면 ASCII Code 가 됨</p>
<pre><code class="language-rust">let my_number = &#39;a&#39; as u8;
println!(&quot;Hello, world! My number is {}&quot;, my_number);</code></pre>
<h1 id="005">005</h1>
<h2 id="char-와-string의-차이">Char 와 String의 차이</h2>
<p>Char type 의 크기는 4bytes</p>
<pre><code class="language-rust">println!(&quot;Size of a char: {}&quot;, std::mem::size_of::&lt;char&gt;()); // 4bytes</code></pre>
<p>Sring (&quot;&quot;)타입의 크기는 <strong>.len()</strong>으로 알 수 있음</p>
<pre><code class="language-rust">println!(&quot;Size of string containing &#39;a&#39;: {}&quot;, &quot;a&quot;.len()); // 1 출력 = 1byte</code></pre>
<p>글자수와 비슷하지만, 사실 글자수가 아닌 bytes 수</p>
<pre><code class="language-rust">println!(&quot;Size of string containing &#39;ß&#39;: {}&quot;, &quot;ß&quot;.len()); // 독일어 : 2bytes
println!(&quot;Size of string containing &#39;国&#39;: {}&quot;, &quot;国&quot;.len()); // 한자 : 3bytes
println!(&quot;Size of string containing &#39;𓅱&#39;: {}&quot;, &quot;𓅱&quot;.len()); // 고대 이집트어 : 4bytes</code></pre>
<p>string의 bytes수가 아닌 글자수를 알려면?</p>
<p><strong>.chars().count()</strong> 사용</p>
<pre><code class="language-rust">let slice = &quot;Hello!&quot;;
println!(&quot;Slice is {} bytes and also {} characters.&quot;, slice.len(), slice.chars().count());

// .chars() : &quot;Hello!&quot; -&gt; &#39;H&#39;, &#39;e&#39;, &#39;l&#39;, ... , &#39;!&#39;;</code></pre>
<h1 id="006">006</h1>
<p>숫자형 (u8, u16, ...) 앞에 숫자를 넣어서 변수선언과 타입지정을 한번에 할 수 있음</p>
<pre><code class="language-rust">// 중간에 underscore(_)는 아무리 많이 써도 상관없음
let my_number = 9_u8; // u8 타입의 9값을 가진 변수
let second_number = 1_000_000_000u64; 
let third_number = 9_____________1u32; </code></pre>
<h2 id="float">Float</h2>
<ol>
<li><code>let float_number = 9.;</code></li>
<li>f32 / f64 로 타입지정(주로 f64사용) <pre><code class="language-rust">let f_number = 9.; // f64
let s_number: f64 = 1.1;
let t_number = 8; // i32
println!(&quot;{}&quot;, f_number + s_number + t_number as f64); // 18.1</code></pre>
</li>
</ol>
<h1 id="007">007</h1>
<h2 id="println">println!()</h2>
<p>-&gt; println meand print line
-&gt; ! means macro</p>
<p>매크로는 super function.</p>
<p>변수 사용({})기호를 사용하고 변수할당을 하지 않으면 에러 발생</p>
<pre><code class="language-rust">let my_name = &quot;David&quot;;
println!(&quot;My name is {}&quot;);

&gt;&gt; ERROR</code></pre>
<h2 id="function">function</h2>
<pre><code class="language-rust">fn func_name() -&gt; return_type {
}</code></pre>
<p>형식으로 함수 선언 가능</p>
<pre><code class="language-rust">fn give_age() -&gt; i32 {
    42 // return 42 라고 하지 않아도 됨!
}

fn main(){
    let my_name = &quot;David&quot;;
    println!(&quot;My name is {} and my age is {}&quot;, my_name, give_age());
}</code></pre>
<h1 id="008">008</h1>
<h2 id="println---2">println!() - 2</h2>
<p>{} 안에 변수명을 넣어서 사용 가능!</p>
<pre><code class="language-rust">fn main(){
    let my_city = &quot;Seoul&quot;;
    let year = 2002;
    let population = 9_987_987;
    println!(&quot;The city of {city} in {year} had a population of {population}&quot;,
    city = my_city,
    year = year,
    population = population);
}</code></pre>
<p>{} 안에 변수 인덱스를 넣어서 사용 가능!</p>
<pre><code class="language-rust">fn main(){
    let my_city = &quot;Seoul&quot;;
    let year = 2002;
    let population = 9_987_987;
    println!(&quot;The city of {0} in {1} had a population of {2}. I love {0}&quot;,
    my_city,
    year,
    population);
}</code></pre>
<p>string interpolation</p>
<pre><code class="language-rust">fn main(){
    let city = &quot;Seoul&quot;;
    let year = 2002;
    let population = 9_987_987;
    println!(&quot;The city of {city} in {year} had a population of {population}. I love {city}&quot;);
}</code></pre>
<p>다음과 같이 expression(표현식)은 사용 불가능</p>
<pre><code class="language-rust">fn main(){
    let city = &quot;Seoul&quot;;
    let year = 2002;
    let population = 9_987_987;
    println!(&quot;The city of {city} in {year+2} had a population of {population}. I love {city}&quot;);
}

&gt;&gt; year+2 에서 Error</code></pre>
<h1 id="009">009</h1>
<h2 id="empty-tuple">empty tuple</h2>
<pre><code class="language-rust">// 8을 반환하는 함수 number()
fn number() -&gt; i32 {
    8
}</code></pre>
<p>하지만 8 뒤에 세미콜론을 붙이게 된다면 에러 발생</p>
<pre><code class="language-rust">// 8을 반환하는 함수 number()
fn number() -&gt; i32 {
    8;
}

fn main() {
    let my_number = number();
}

/*
&gt;&gt; Error : expected i32 but found ()
() 는 Rust 에서 empty tuple, unit type 이라고 부르고 다른 언어에서는 void 라고 부름
*/</code></pre>
<h2 id="display-debug">Display, Debug</h2>
<ul>
<li>Display : {}</li>
<li>Debug Print : {:?}</li>
</ul>
<pre><code class="language-rust">fn empty_tuple() {
}

fn main() {
    let tuple = empty_tuple();
    println!(&quot;{}&quot;, tuple); // error
    println!(&quot;{:?}&quot;, tuple); // 웬만해서는 출력 가능!
}</code></pre>
<h1 id="010">010</h1>
<h2 id="function-1">function</h2>
<p>앞서 알아본 간단한 함수의 모양은 다음과 같음</p>
<pre><code class="language-rust">fn give_number() -&gt; i32 {
    8
}

fn main() {
    let my_number = give_number();
}</code></pre>
<p>Parameter 를 받아서 반환하는 형태의 함수는 다음과 같음</p>
<pre><code class="language-rust">fn give_number(one: i32, two: i32) -&gt; i32 {
    one * two
}

fn main() {
    let my_number = give_number(9,8);
    println!(&quot;{}&quot;, my_number);
}</code></pre>
<h2 id="code-block-활용">Code block 활용</h2>
<pre><code class="language-rust">fn give_number(one: i16, two: i16) -&gt; i16 {
    let multiplied_by_ten = {
        let f_number = 10;
        f_number * one * two
    };
    multiplied_by_ten
}

fn main() {
    let my_number = give_number(9,8);
    println!(&quot;{}&quot;, my_number);
}</code></pre>
<hr>
<p>강의 링크
<a href="https://youtube.com/playlist?list=PLfllocyHVgsSJf1zO6k6o3SX2mbZjAqYE&amp;si=LfVF7DHgzWT7iUrp">https://youtube.com/playlist?list=PLfllocyHVgsSJf1zO6k6o3SX2mbZjAqYE&amp;si=LfVF7DHgzWT7iUrp</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Grafana 대시보드 설정]]></title>
            <link>https://velog.io/@na_treeeum/Grafana-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@na_treeeum/Grafana-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 03 Apr 2025 06:53:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/na_treeeum/post/073600c8-8731-4787-a4d7-aa843d0af592/image.png" alt="1">
최종 결과 대시보드!</p>
<h1 id="대시보드-구성">대시보드 구성</h1>
<h2 id="node-exporter">Node Exporter</h2>
<blockquote>
<p>시스템 리소스 정보(CPU, 메모리, 디스크, 네트워크 등)를 HTTP endpoint로 제공</p>
</blockquote>
<ul>
<li><a href="http://localhost:9100/metrics">localhost:9100/metrics</a> 로 시스템 리소스 정보를 제공</li>
</ul>
<h2 id="prometheus">Prometheus</h2>
<blockquote>
<p>다양한 지표를 수집하고 저장하는 시스템</p>
</blockquote>
<ul>
<li>대상(예 : node-exporter) 에 주기적으로 접근하여 메트릭 데이터를 가져옴</li>
</ul>
<h2 id="grafana">Grafana</h2>
<blockquote>
<p>메트릭 데이터를 불러와 대시보드 형태로 시각화해주는 툴</p>
</blockquote>
<h2 id="관계-요약">관계 요약</h2>
<table>
<thead>
<tr>
<th>Element</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Node Exporter</td>
<td>메트릭 데이터 제공</td>
</tr>
<tr>
<td>⬇️</td>
<td></td>
</tr>
<tr>
<td>Prometheus</td>
<td>데이터 수집 및 저장</td>
</tr>
<tr>
<td>⬇️</td>
<td></td>
</tr>
<tr>
<td>Grafana</td>
<td>데이터 시각화</td>
</tr>
</tbody></table>
<h1 id="설치--실행--설정">설치 &amp; 실행 &amp; 설정</h1>
<blockquote>
<p>OS : Ubuntu, sudo su 로 관리자 계정에서 진행합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/cc5ab4fa-2def-4ed6-af2d-dec1fb34f77d/image.png" alt="2"></p>
<h2 id="node-exporter-1">Node Exporter</h2>
<p><a href="https://prometheus.io/download/#node_exporter">https://prometheus.io/download/#node_exporter</a></p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/8d40da27-92fb-450e-9c5f-2470b92a9e90/image.png" alt="3"></p>
<p>리눅스 버전의 다운로드 링크를 복사합니다.</p>
<p>wget 명령어를 통해서 압축파일을 다운받습니다.</p>
<pre><code>wget https://github.com/prometheus/node_exporter/releases/download/v1.9.0/node_exporter-1.9.0.linux-amd64.tar.gz</code></pre><p>압축파일 다운로드가 완료되었다면 아래 명령어로 파일이 존재하는지 확인합니다.</p>
<pre><code>ls node_exporter*</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/57f8f554-f86b-45b4-9551-59716d0a070d/image.png" alt="4"></p>
<p>파일이 존재한다면 압축을 풀어준 뒤, 압축해제된 경로로 이동합니다.</p>
<pre><code>tar xzvf node_exporter-1.9.0.linux-amd64.tar.gz
cd node_exporter-1.9.0.linux-amd64</code></pre><p>ls 명령어로 디렉토리의 파일 목록을 확인하면 다음과 같습니다 </p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/8c94a8c5-586f-4827-9311-2f958ec050e8/image.png" alt="5"></p>
<p>이제 node_exporter 파일을 /usr/local/bin 폴더로 옮겨줍니다.</p>
<pre><code>mv node_exporter /usr/local/bin</code></pre><p>파일을 옮겼다면, 시스템 서비스 등록을 합니다.</p>
<pre><code>vim /etc/systemd/system/node-exporter.service</code></pre><p>아래 내용을 복사 붙여넣기 한 뒤 저장합니다.</p>
<pre><code>[Unit]
Description=Node Exporter
After=network.target

[Service]
User=nobody
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=default.target</code></pre><p>서비스를 시작하고, 부팅 시 자동 실행하도록 설정합니다.</p>
<pre><code>systemctl daemon-reexec
systemctl daemon-reload
systemctl start node-exporter
systemctl enable node-exporter</code></pre><p>아래 명령어를 통해 시스템이 정상작동하고 있는지 확인합니다.</p>
<pre><code>systemctl status node-exporter</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/422e4159-dada-4e52-9002-00ee8ca80393/image.png" alt="6"></p>
<pre><code>curl http://localhost:9100/metrics</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/bfd28a01-d577-4694-9f91-1699c87c4e39/image.png" alt="7"></p>
<h2 id="prometheus-1">Prometheus</h2>
<p>node-exporter 설정이 끝났으니 prometheus 를 설치해 봅니다.</p>
<p>먼저, Prometheus 시스템 그룹을 생성합니다.</p>
<pre><code>groupadd --system prometheus</code></pre><p>prometheus 사용자를 추가하고, 생성된 그룹에 할당해 줍니다.</p>
<pre><code>useradd -s /sbin/nologin --system -g prometheus prometheus</code></pre><p>그리고 prometheus를 위한 디렉토리를 생성해 줍니다.</p>
<pre><code>mkdir /etc/prometheus</code></pre><p>다음으로 아래 링크에 들어가서 프로메테우스 다운로드 링크를 복사합니다.</p>
<p><a href="https://prometheus.io/download/#prometheus">https://prometheus.io/download/#prometheus</a></p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/58704c5d-3042-42f7-b248-a37ebe2b821f/image.png" alt="8"></p>
<p>LTS 버전을 받도록 하겠습니다.</p>
<p>wget 명령어를 통해서 압축파일을 다운받습니다.</p>
<pre><code>wget https://github.com/prometheus/prometheus/releases/download/v2.53.4/prometheus-2.53.4.linux-amd64.tar.gz</code></pre><p>압축파일을 풀어준 뒤 압축이 해제된 디렉토리로 이동해줍니다.</p>
<pre><code>tar xzvf prometheus*.tar.gz
cd prometheus*/</code></pre><p>해당 폴더에 있는 prometheus 바이너리 파일을 옮겨준 뒤 버전을 확인합니다.</p>
<pre><code>mv prometheus promtool /usr/local/bin/
prometheus --version</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/139cff0a-bd7c-4de2-8feb-4cad48db9767/image.png" alt="9"></p>
<p>그럼 이제 아까 만들어둔 prometheus 폴더에 구성 파일을 옮겨줍니다.</p>
<pre><code>mv prometheus.yml /etc/prometheus/prometheus.yml
mv consoles/ console_libraries/ /etc/prometheus/</code></pre><p>파일을 옮긴 뒤 구성파일의 내용을 확인해 줍니다.</p>
<pre><code>cat /etc/prometheus/prometheus.yml</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/a13dc8fa-f808-4b2f-b246-eab653a4d09a/image.png" alt="10"></p>
<p>이제 프로메테우스 시스템을 등록합니다. </p>
<pre><code>vim /etc/systemd/system/prometheus.service</code></pre><p>위 명령어로 에디터를 열어서, 아래 내용을 붙여넣어 줍니다.</p>
<pre><code>[Unit]
Description=Prometheus
Documentation=https://prometheus.io/docs/introduction/overview/
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file /etc/prometheus/prometheus.yml \
--storage.tsdb.path /var/lib/prometheus/ \
--web.console.templates=/etc/prometheus/consoles \
--web.console.libraries=/etc/prometheus/console_libraries \
--web.listen-address=0.0.0.0:9090 \

[Install]
WantedBy=multi-user.target</code></pre><p>/var/lib/prometheus/  디렉토리를 storage path 로 설정해 놓았기 때문에, 해당 디렉토리를 생성하고, 권한을 부여합니다.</p>
<pre><code>mkdir /var/lib/prometheus
chown -R prometheus:prometheus /var/lib/prometheus</code></pre><p>이제 prometheus 설정은 끝났으니 실행시켜 봅니다.</p>
<pre><code>systemctl daemon-reload
systemctl start prometheus
systemctl enable prometheus</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/9bce1944-21eb-4ba0-978e-30485a91a308/image.png" alt="11"></p>
<p>잘 작동하고 있음을 확인할 수 있습니다.</p>
<h2 id="node-exporter--prometheus-연동">node-exporter &lt;&gt; prometheus 연동</h2>
<p>Prometheus 의 설정파일을 수정해줍니다.</p>
<pre><code>vim /etc/prometheus/prometheus.yml</code></pre><p>최하단에 있는 scrape_configs: 부분에 아래 내용을 추가해 줍니다.</p>
<pre><code>- job_name: &#39;node-exporter&#39;
    static_configs:
      - targets: [&#39;localhost:9100&#39;]</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/315e6727-6fb7-4b61-a28e-dd8186854e2f/image.png" alt="12"></p>
<p>prometheus를 재시작 하고, 연동이 잘 되었는지 확인합니다.</p>
<pre><code>systemctl restart prometheus
curl http://localhost:9090/api/v1/targets</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/9ae7ebaa-ea88-4675-8273-0d35d5a58e78/image.png" alt="13"></p>
<p>activeTargets 에 <a href="http://localhost:9100/metrics">localhost:9100/metrics</a> 가 있는지 확인합니다.</p>
<h2 id="grafana-1">Grafana</h2>
<p>먼저 apt 업데이트를 진행한 뒤, 필요 패키지를 다운받습니다.</p>
<pre><code>apt update -y &amp;&amp; sudo apt upgrade -y
apt install -y apt-transport-https software-properties-common wget</code></pre><p>Grafana GPG 키를 추가해주어야 합니다.</p>
<p>아래 명령어를 통해 GPG 키를 다운받아 줍니다.</p>
<pre><code>sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg &gt; /dev/null</code></pre><p>Grafana APT 저장소를 추가해 준 뒤 grafana 를 설치해 줍니다.</p>
<pre><code>echo &quot;deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main&quot; | sudo tee -a /etc/apt/sources.list.d/grafana.list
apt update
apt install grafana</code></pre><p>설치가 완료되었다면 제대로 설치가 되었는지 확인하고, 서비스를 시작합니다.</p>
<pre><code>grafana-server -v

systemctl start grafana-server
systemctl enable grafana-server

systemctl status grafana-server</code></pre><p><img src="https://velog.velcdn.com/images/na_treeeum/post/629eec0b-5a1d-4941-a92a-e754d531c1af/image.png" alt="14"></p>
<p>잘 동작하고 있는것을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/b1203271-1902-4b8b-9404-9f7083377a79/image.png" alt="15"></p>
<p>grafana 가 동작하는 3000번 포트를 개방하고 접속해보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/0c910a3b-2354-41f4-a4fa-92402df1db6c/image.png" alt="16"></p>
<p>최초 자격증명은 admin/admin 입니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/b55151f0-9fbf-4f96-a2ea-bcbe52212eaf/image.png" alt="17"></p>
<p>비밀번호를 변경하라는 창이 뜹니다만 skip 하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/104777d5-190d-4c71-8cf1-fc86ccdc4796/image.png" alt="18"></p>
<p>먼저 데이터 소스를 추가해 주겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/beb6055f-766f-4703-a523-b71b119fab80/image.png" alt="19"></p>
<p>프로메테우스 클릭</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/89001ff3-3df7-4a3f-a9ed-4f2d9c74e24e/image.png" alt="20"></p>
<p>connection 에는 <a href="http://localhost:9090">http://localhost:9090</a> 을 입력해줍니다.</p>
<p>제일 밑에 save&amp;test를 누르면 쿼리에 성공했다는 메세지가 나옵니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/4f5d70df-2e69-4931-aeaf-3af5767b6986/image.png" alt="21"></p>
<p>그렇다면 다시 홈으로 돌아와서 dashboard 설정을 해보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/a770ee84-4f44-4720-900c-871381fd625f/image.png" alt="22"></p>
<p>우측 상단의 + 버튼을 누르고 import dashboard를 클릭</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/fa157ea5-0ce9-4ac9-bdb9-acf7e4fcee50/image.png" alt="23"></p>
<p>node exporter 기본 템플릿을 불러옵니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/2ad7f633-ee42-497f-8190-94810d73fa92/image.png" alt="24"></p>
<p>데이터 소스를 프로메테우스로 선택해주면</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/5e4b3a0b-dfc8-4afc-aacb-00ead100f275/image.png" alt="25"></p>
<p>대시보드 설정 완료입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Multicall Contract]]></title>
            <link>https://velog.io/@na_treeeum/Multicall-Contract</link>
            <guid>https://velog.io/@na_treeeum/Multicall-Contract</guid>
            <pubDate>Sun, 17 Nov 2024 11:52:08 GMT</pubDate>
            <description><![CDATA[<h1 id="multicall-이란">MultiCall 이란?</h1>
<p>여러개의 트랜잭션을 한번에 전송할 수 있게 도와주는 컨트랙트입니다.</p>
<h2 id="장점">장점</h2>
<ul>
<li>여러개의 트랜잭션을 한번에 묶어서 보내기 때문에 각각의 트랜잭션을 전송하는것 보다 가스비가 저렴할 수 있습니다.</li>
<li>트랜잭션에 대해 서명을 한 번만 하면 되기 때문에, UX향상을 기대할 수 있습니다.</li>
</ul>
<h1 id="multicall-컨트랙트-주소">Multicall 컨트랙트 주소</h1>
<blockquote>
<p>웬만한 네트워크에는 배포되어있는듯 합니다.
신기하게도 모든 네트워크에 배포된 컨트랙트 주소가 동일합니다</p>
</blockquote>
<p><a href="https://www.multicall3.com/deployments">https://www.multicall3.com/deployments</a></p>
<h1 id="multicall-abi">Multicall Abi</h1>
<p>아래 링크에서 확인할 수 있습니다.</p>
<p><a href="https://www.multicall3.com/abi#json">https://www.multicall3.com/abi#json</a></p>
<h1 id="사용법">사용법</h1>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/973512a8-2a12-445d-84ca-d1531af9e7fb/image.png" alt=""></p>
<ul>
<li>여러개의 contract 에서 read 함수를 호출하여 결과를 종합하는 경우</li>
<li>여러개의 상태가 변경되는 함수를 호출하는 경우</li>
</ul>
<h3 id="🍏여러개의-contract-에서-read-함수를-호출하여-결과를-종합하는-경우">🍏여러개의 contract 에서 read 함수를 호출하여 결과를 종합하는 경우</h3>
<ol>
<li><p>callData를 생성</p>
<pre><code class="language-jsx"> // callData 형식
 struct {
     target : contractAddress,
     callData : contract.interface.encodeFunctionData(&quot;methodName&quot;, [param1, param2, ...]
 }

 // contract instance 에서 functionData encoding
 const mulltiCallData = [
     {
         target : contractAddress1,
         callData : contract1.interface.encodeFunctionData(&quot;methodName1&quot;, [param1, param2, ...]
     },
     {
         target : contractAddress2,
         callData : contract2.interface.encodeFunctionData(&quot;methodName2&quot;, [param1, param2, ...]
     },
 ]

 // contractInterface instance 에서 functionData encoding
 const contractInterface1 = new ethers.Interface(ABI1)
 const contractInterface2 = new ethers.Interface(ABI2)

 const mulltiCallData = [
     {
         target : contractAddress1,
         callData : contractInterface1.encodeFunctionData(&quot;methodName1&quot;, [param1, param2, ...]
     },
     {
         target : contractAddress2,
         callData : contractInterface2.encodeFunctionData(&quot;methodName2&quot;, [param1, param2, ...]
     },
 ]</code></pre>
</li>
<li><p>aggregate 함수 호출</p>
<pre><code class="language-jsx"> // ethers.js v6 기준!

 const multicallResultRaw = await multicallContract.aggregate.staticCall(multiCallData)</code></pre>
</li>
<li><p>호출결과를 decoding</p>
<pre><code class="language-jsx"> /*
 aggregate 반환값 형식 = [blockNumber , [ Result1, Result2, Result3, ... ]
 */

 // contract instance 에서 functionDataResult decoding
 const [ blockNum, results ] = multicallResultRaw
 const [ result1, result2 ] = [
     contract1.interface.decodeFunctionResult(&quot;methodName1&quot;,results[0]),
     contract2.interface.decodeFunctionResult(&quot;methodName2&quot;,results[1]),
 ]

 // contractInterface instance 에서 functionDataResult decoding
 const [ result1, result2 ] = [
     contractInterface1.decodeFunctionResult(&quot;methodName1&quot;,results[0]),
     contractInterface2.decodeFunctionResult(&quot;methodName2&quot;,results[1]),
 ]</code></pre>
</li>
</ol>
<ul>
<li><p>코드예시</p>
<pre><code class="language-jsx">  async function main() {
    const rpcUrl = &quot;https://api.avax-test.network/ext/bc/C/rpc&quot;;
    const provider = new ethers.JsonRpcProvider(rpcUrl);
    const multicallContractAddress = &quot;0xcA11bde05977b3631167028862bE2a173976CA11&quot;;

    const multicallContract = new ethers.Contract(
      multicallContractAddress,
      multicallAbi,
      provider
    );

    const token0Address = &quot;0x2B4931ab37d33AE909d605476168F90E10C1A9e4&quot;;
    const token1Address = &quot;0xb13BebA6d96244C38dcbcbd448303351BaF7723c&quot;;

    const tokenInterface = new ethers.Interface(tokenAbi);

    const multiCallData = [
      {
        target: token0Address,
        callData: tokenInterface.encodeFunctionData(&quot;name&quot;),
      },
      {
        target: token0Address,
        callData: tokenInterface.encodeFunctionData(&quot;symbol&quot;),
      },
      {
        target: token1Address,
        callData: tokenInterface.encodeFunctionData(&quot;name&quot;),
      },
      {
        target: token1Address,
        callData: tokenInterface.encodeFunctionData(&quot;symbol&quot;),
      },
    ];

      // only call once
    const [, [token0NameRaw, token0SymbolRaw, token1NameRaw, token1SymbolRaw]] =
      await multicallContract.aggregate.staticCall(multiCallData);

    const [token0Name] = tokenInterface.decodeFunctionResult(
      &quot;name&quot;,
      token0NameRaw
    );
    const [token0Symbol] = tokenInterface.decodeFunctionResult(
      &quot;symbol&quot;,
      token0SymbolRaw
    );
    const [token1Name] = tokenInterface.decodeFunctionResult(
      &quot;name&quot;,
      token1NameRaw
    );
    const [token1Symbol] = tokenInterface.decodeFunctionResult(
      &quot;symbol&quot;,
      token1SymbolRaw
    );

    console.log(token0Name);
    console.log(token0Symbol);
    console.log(token1Name);
    console.log(token1Symbol);
  }</code></pre>
</li>
</ul>
<h3 id="🍏여러개의-contract-에서-state-changing-함수를-호출하여-결과를-종합하는-경우">🍏여러개의 contract 에서 state-changing 함수를 호출하여 결과를 종합하는 경우</h3>
<ol>
<li><p>callData를 생성</p>
<pre><code class="language-jsx"> // callData 형식
 struct {
     address target;
     bool allowFailure; // false 일 경우 해당 callData 호출 실패시, 전체 트랜잭션이 revert됨
     bytes callData;
 }

 // contract instance 에서 functionData encoding
 const mulltiCallData = [
     {
         target : contractAddress1,
         allowFailure : true,
         callData : contract1.interface.encodeFunctionData(&quot;methodName1&quot;, [param1, param2, ...]
     },
     {
         target : contractAddress2,
         allowFailure : false,
         callData : contract2.interface.encodeFunctionData(&quot;methodName2&quot;, [param1, param2, ...]
     },
 ]

 // contractInterface instance 에서 functionData encoding
 const contractInterface1 = new ethers.Interface(ABI1)
 const contractInterface2 = new ethers.Interface(ABI2)

 const mulltiCallData = [
     {
         target : contractAddress1,
         allowFailure : true,
         callData : contractInterface1.encodeFunctionData(&quot;methodName1&quot;, [param1, param2, ...]
     },
     {
         target : contractAddress2,
         allowFailure : false,
         callData : contractInterface2.encodeFunctionData(&quot;methodName2&quot;, [param1, param2, ...]
     },
 ]</code></pre>
</li>
<li><p>aggregate3 함수 호출</p>
<pre><code class="language-jsx"> // ethers.js v6 기준!

 const tx = await multicallContract.connect(wallet).aggregate3(multiCallData)
 await tx.wait()</code></pre>
</li>
</ol>
<ul>
<li><p>코드예시</p>
<pre><code class="language-jsx">  const multicallData2 = [
      {
        target: token0Address,
        allowFailure: true,
        callData: tokenInterface.encodeFunctionData(&quot;faucet&quot;, [tokenAmount]),
      },
      {
        target: token0Address,
        allowFailure: true,
        callData: tokenInterface.encodeFunctionData(&quot;transfer&quot;, [
          wallet.address,
          tokenAmount,
        ]),
      },
      {
        target: token1Address,
        allowFailure: true,
        callData: tokenInterface.encodeFunctionData(&quot;faucet&quot;, [tokenAmount]),
      },
      {
        target: token1Address,
        allowFailure: true,
        callData: tokenInterface.encodeFunctionData(&quot;transfer&quot;, [
          wallet.address,
          tokenAmount,
        ]),
      },
    ];

    const tx = await multicallContract.connect(wallet).aggregate3(multicallData2);

    await tx.wait();

    const token0BalanceAfter = await token0Contract.balanceOf(wallet.address);
    const token1BalanceAfter = await token1Contract.balanceOf(wallet.address);

    console.log(
      `Token0 Balance : ${ethers.formatEther(
        token0BalanceBefore
      )} -&gt; ${ethers.formatEther(token0BalanceAfter)}`
    );

    console.log(
      `Token1 Balance : ${ethers.formatEther(
        token1BalanceBefore
      )} -&gt; ${ethers.formatEther(token1BalanceAfter)}`
    );</code></pre>
</li>
</ul>
<blockquote>
<p>💡multicall 을 통해 함수를 실행할 때 주의해야할 점</p>
</blockquote>
<ul>
<li>msg.sender 가 multicall contract Address 가 되기 때문에 transfer, approve같은 메서드를 호출할 때 메서드 실행주체에 대해 잘 생각해야함.<blockquote>
</blockquote>
</li>
</ul>
<p>실행결과 tx 정보 확인 → <a href="https://testnet.snowtrace.io/tx/0x3395ec1e1888acc7e91f78f4b4969f6cde93e43d881fc37086f80bc8c2d8a94d/internal?chainid=43113">https://testnet.snowtrace.io/tx/0x3395ec1e1888acc7e91f78f4b4969f6cde93e43d881fc37086f80bc8c2d8a94d/internal?chainid=43113</a></p>
<hr>
<h1 id="multicall-contract-와-import-된-multicall-차이점">Multicall Contract 와 import 된 Multicall 차이점</h1>
<h2 id="multicall-contract">Multicall Contract</h2>
<p>Multicall Contract 를 통해 tx 를 전송하게 될 경우 구조는 다음과 같습니다.</p>
<p>[ Wallet ] → [ Multicall Contract ] → [ Target Contract ]</p>
<p>따라서 Target Contract 입장에서 tx의 주체, 즉 msg.sender 는 Multicall Contract가 됩니다.</p>
<p><strong>상황 예시</strong> </p>
<ul>
<li>내 지갑(W0)에 90 TokenA 가 있다.</li>
<li>(W1, W2, W3) 에 30 TokenA 씩 전송하려고 한다.</li>
<li>( token.transfer ) 트랜잭션 3개를 multicall로 전송한다.</li>
<li>multicall은 3개의 트랜잭션을 TokenA 컨트랙트로 전송한다.</li>
<li>이때, transfer의 호출 주체(msg.sender) 는 multicall contract 이고, multicall contract 는 tokenA 잔액이 0 이기 때문에 모든 트랜잭션이 revert 된다.</li>
</ul>
<p>코드를 살펴보면, multicall Contract 가 target Contract로 callData 를 그대로 전송하고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/29ff5cd5-64a8-4c7e-83fb-ffeaa4783590/image.png" alt=""></p>
<h2 id="import-된-multicall">import 된 Multicall</h2>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/ed46ca9c-adb6-42ed-85dc-6b38b405d337/image.png" alt=""></p>
<p>위 사진과 같이 Multicall 을 상속받는 경우는 조금 다른 부분이 있습니다.</p>
<p>위 코드에서 상속받은 Multicall 코드를 살펴보면 다음과 같이 작성돼있습니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/cc8516ef-90ee-467c-a51c-62e3a7c85415/image.png" alt=""></p>
<p>여기서는 delegatecall 을 사용하여 tx를 처리하고 있음을 확인할 수 있습니다.</p>
<p>delegateCall 은 특정 함수를 호출할 때 msg.sender 와 msg.value를 그대로 유지하여 트랜잭션을 전송하게 됩니다.</p>
<h2 id="상황-비유">상황 비유</h2>
<p>예시 상황으로 두 방법에 대해 정리하자면,</p>
<p>상황 : 사람A 가 사람B 에게 중간 매개자(사람C)를 통해 물건을 전송하는 상황.</p>
<ul>
<li>Multicall Contract를 사용한다면<ol>
<li>사람A 가 사람C에게 물건을 전달한다. 물건에는 [보낸 사람 : 사람A] 라고 적혀있다.</li>
<li>사람C 가 사람B에게 물건을 전달한다. 물건에는 [보낸 사람 : 사람C] 라고 적혀있다.</li>
</ol>
</li>
<li>import된 Multicall 을 사용한다면<ol>
<li>사람A 가 사람C에게 물건을 전달한다. 물건에는 [보낸 사람 : 사람A] 라고 적혀있다.</li>
<li>사람C 가 사람B에게 물건을 전달한다. 물건에는 [보낸 사람 : 사람A] 라고 적혀있다.</li>
</ol>
</li>
</ul>
<h2 id="정리">정리</h2>
<ul>
<li>Multicall Contract<ul>
<li>&quot;한번에&quot; 여러개의 target Contract로, 여러개의 트랜잭션을 보낼 수 있는것</li>
<li>트랜잭션의 주체가 multicall Contract</li>
</ul>
</li>
<li>import된 Multicall<ul>
<li>한번에 여러개의 트랜잭션을 보낼 수 있지만, target Contract는 해당 컨트랙트로만 한정</li>
<li>트랜잭션의 주체가 wallet(caller)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[UniswapV2 동작 이해하기 (1)]]></title>
            <link>https://velog.io/@na_treeeum/UniswapV2-%EB%8F%99%EC%9E%91-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@na_treeeum/UniswapV2-%EB%8F%99%EC%9E%91-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Tue, 04 Jun 2024 15:17:36 GMT</pubDate>
            <description><![CDATA[<h1 id="uniswap-v2-란">Uniswap V2 란?</h1>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/b284edfb-2a06-4fc2-8ea0-345305ca8855/image.png" alt=""></p>
<h1 id="cpmm">CPMM</h1>
<p>uniswapV2 에서는 CPMM (Constant Product Market Maker)을 사용합니다.
다른건 몰라도 이것만 알면 됩니다.</p>
<p><code>x * y = k</code></p>
<p>뭔가 복잡해보이는 이름에 걸맞지 않는 간단한 수식입니다.
매우 간단해 보여도 uniswapV2의 핵심이 되는 수식이니 잘 이해해야합니다.
위의 수식이 어떻게 사용되는지 예시를 통해 알아봅시다.</p>
<h1 id="유동성liquidity">유동성(Liquidity)</h1>
<p>유동성이란 거래를 위해 사용될 수 있는 자산을 의미합니다.</p>
<h1 id="swap">Swap</h1>
<p>swap은 &quot;교환&quot;과 같은 의미입니다.</p>
<p><img src="https://velog.velcdn.com/images/na_treeeum/post/4e9eed40-025f-4a7f-8e22-3ac78e021359/image.png" alt=""></p>
<p>사과를 구매하려는 사람의 이야기로 예시를 들어보겠습니다.</p>
<pre><code>금화를 1개 가지고 있는 사람A가 사과를 구매하려고 한다.

길을 가다 한 무인과일가게에 들어가서 보니
상자에 금화 100 개와 사과 101개가 들어있었다.

옆에 종이에는 이렇게 적혀있었다.

&quot;상자에 남아있는 금화와 사과의 개수의 곱은 항상 10100 이어야합니다.&quot;

사람A 는 상자에 금화를 한개 넣고 사과를 하나 뺐다.

상자에 남은건 금화 101개와 사과 100개로 두 수의 곱은 10100으로 변함이 없다.</code></pre><p>여기서 가장 핵심적이 되는 부분은 <strong>금화의 개수</strong>와 <strong>사과의 개수</strong>의 곱이 10100로 일정했다는 것입니다.
그렇다면 금화(x)와 사과(y)의 현재 개수를 알면 <strong>지불하는 금화(혹은 사과)의 양에 대해 수령하게 되는 사과(혹은 금화)의 양</strong>을 계산할 수 있다는 것입니다.</p>
<p>간단하게 수식으로 정리하면</p>
<ul>
<li>reserve0(금화 개수) = r0</li>
<li>reserve1(사과 개수) = r1</li>
<li>k = r0 * r1</li>
<li>amount0In(지불하는 금액) = amt0In</li>
<li>amount1Out(수령하는 금액) = amt1Out</li>
</ul>
<p><code>(r0 + amt0In)(r1 - amt1Out) = k</code></p>
<p>위 식을 다시 위의 사과예시에 대입해보면 다음과 같습니다.</p>
<p><code>k = r0(금화의 유동성=100) * r1(사과의 유동성=101) = 10100</code>
<code>(r0(금화의 유동성=100) + amt0In(A가 지불한 금화=1)) * (r1(사과의 유동성=101) - amt1Out(A가 받게 될 사과의 개수)) = k</code></p>
<h1 id="price-impact">Price Impact</h1>
<p>CPMM 특성 상 같은 금액을 지불해도 유동성 풀 규모에 따라 받게되는 금액이 차이가 납니다.</p>
<p><code>50개의 token0 을 token1로 swap</code> 하는 상황에서 예를 들어보겠습니다.</p>
<ul>
<li>금화가 100개, 사과가 1000개 라고 했을 때<ul>
<li>금화 : 사과 = 1 : 10</li>
<li>금화 50개에 대한 사과의 기대수량 = 500개</li>
<li>실제 스왑량<ul>
<li>(100 + 50) * (1000 - 사과) = 100000</li>
<li>사과 개수 ≈ 333.333</li>
</ul>
</li>
<li>PriceImpact = (1 - 실제수량 / 기대수량) * 100 ≈ 33.33%</li>
</ul>
</li>
<li>금화가 10,000,000개, 사과가 100,000,000개 라고 했을 때<ul>
<li>금화:사과 = 1: 10<ul>
<li>금화 50개에 대한 사과의 기대수량 : 500개</li>
</ul>
</li>
<li>실제 스왑량<ul>
<li>(10,000,000+50) * (100,000,000 - 사과) = 1,000,000,000,000,000</li>
<li>사과 개수 ≈ 499.998</li>
</ul>
</li>
<li>PriceImpact = (1 - 실제수량 / 기대수량) * 100 ≈ 0.0004%</li>
</ul>
</li>
</ul>
<p>유동성이 클 수록 기대수량에 대한 오차가 작아집니다.</p>
<hr>
<p>유동성 공급/제거와 수수료에 대한 내용은 다음편에서..</p>
]]></description>
        </item>
    </channel>
</rss>