diff --git a/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/index.html b/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/index.html index 5a5e330e..1d9d9ac7 100644 --- a/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/index.html +++ b/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/index.html @@ -567,4 +567,4 @@

ThoughtsJoel Mun

Written by Joel Mun. Joel likes Typescript, React, Node.js, GoLang, Python, Wasm and more. He also loves to enlarge the boundaries of his knowledge, mainly by reading books and watching lectures on Youtube. Guitar and piano are necessities at his home.

\ No newline at end of file +/static/1265356b9e6bab3ff8ccafeaca6d96b8/b315d/profile-pic.jpg 2x" src="/static/1265356b9e6bab3ff8ccafeaca6d96b8/99438/profile-pic.jpg" alt="Joel Mun" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/>

Written by Joel Mun. Joel likes Typescript, React, Node.js, GoLang, Python, Wasm and more. He also loves to enlarge the boundaries of his knowledge, mainly by reading books and watching lectures on Youtube. Guitar and piano are necessities at his home.

\ No newline at end of file diff --git a/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/index.html b/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/index.html index e139ccec..480357fb 100644 --- a/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/index.html +++ b/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/index.html @@ -48,7 +48,7 @@ } } }) - Technical intro to DeFi lending protocols with zkLend codebase as an example | Joel's dev blog

Joel's dev blog

Technical intro to DeFi lending protocols with zkLend codebase as an example

October 26, 2023

36 min read

+ Technical intro to DeFi lending protocols with zkLend codebase as an example | Joel's dev blog

Joel's dev blog

Technical intro to DeFi lending protocols with zkLend codebase as an example

October 26, 2023

40 min read

@@ -630,6 +632,81 @@

tt is calculated as 100 seconds divided by the number of seconds per year (without caring about leap years).

Other than this, the calculation above should be straightforward.

+

Update frequency of cumulated liquidity index and interest rates

+

The cumulated liquidity index is updated once per block only. You can go back and check this line:

+
if (reserve.last_update_timestamp == block_timestamp) {
+    // Accumulator already updated on the same block
+    reserve.debt_accumulator
+}
+

So even if further transactions take place in the same block, the accumulator will change only once for the first transaction in the block and that will be it.

+

However, interest rates will update every transaction that relates to the change in the amount of value borrowed/lent. This means that the accumulator will take the last available interest rate into consideration, which should be from more than or equal to one previous block from the current block.

+

This makes sense because accumulators use the time difference between the last update time and now. So it’s not possible to update accumulators more than once in the same block.

+

But the liquidity in the same block can change multiple times, which means there can be varying interest rates per block. The last interest rate of that block will be used for the next accumulator calculation in the later block.

+

Accumulator example:

+ +

Two different AccumulatorsSync events for the same token 0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7, but the value of accumulators stays the same.

+

Interest rates example:

+ +

Two different InterestRatesSync events, in the same block 10000, for the same asset 0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 but different lending_rate and borrowing_rate.

+

Approximation of compound interest over a time period

+

It is impossible to predictably calculate the compound interest accrued/earned from the protocol because the interest rate is variable every block, and the movement of interest rates cannot be predicted without users’ actions.

+

There is a way to run an approximate calculation: continuous compounding interest formula.

+
P(t)=P0ertP(t) = P_0e^{rt}
+

where

+

P(t)=value at time tP(t) = \text{value at time }t

+

P0=original principalP_0 = \text{original principal}

+

e=Euler’s numbere = \text{Euler's number}

+

r=annual interest rater = \text{annual interest rate}

+

t=length of time the interest rate is applied per yeart = \text{length of time the interest rate is applied per year}

+

To elaborate, let’s take an example: you deposit 25 USDC and wait for a month. The projected average APY over the month is 5%, so we just take a stable 5% APY for example.

+

Then,

+
P(t)=P0×er×t=25e0.05×112=25.1043839823P(t) = P_0 × e^{r \times t} = 25 * e^{0.05 \times \frac{1}{12}} = 25.1043839823
+

This means the compound interest for an average of 5% APY over a month approx 0.10 zUSDC.

+

The reason we use continuous compounding is that the frequency of compounding is simply impossible to be predicted in the case of lending protocols on blockchain and the time difference between each compounding is always different. The frequency of compounding under a given time period is the number of blocks that contain at least one transaction regarding a particular token on the protocol\text{the number of blocks that contain at least one transaction regarding a particular token on the protocol}.

+

Let’s give an example of AccumulatorsSync event for token 0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8 from block 61993 to 61998:

+
    +
  • at block 61993
  • +
  • at block 61997
  • +
  • at block 61998
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
blockeventtimestamptime diff (secs) from previous compounding
61993link1684912486-
61997link1684912643157
61998link168491267835
+

Given that there were already existing accumulators before block 61993, the lending and borrowing accumulators compounded 3 times within the specified block range.

+

And the time difference between each compounding event is different, making us unable to use the traditional compound interest formula (A=P(1+rn)ntA = P(1 + \frac{r}{n})^{nt}), where you need to specify n = number of compounding periods per unit of time\text{n = number of compounding periods per unit of time}, which is always variable as explained in above example. Also, each compounding period isn’t constant; so this equation can’t really be used, because it assumes that each compounding period is the same. Therefore, a fair approximation can be better deduced by using continuous compounding interest formula.

Liquidation

A few more terms need to be defined to be able to understand liquidation.

Health factor. Health factor=Collaterali×USD value of Collaterali×Collateral factoriLiabilityi×USD value of Liabilityi\text{Health factor} = \frac{\sum{Collateral_i \times \text{USD value of Collateral}_i \times \text{Collateral factor}_i}}{\sum{Liability_i \times \text{USD value of Liability}_i}}. It denotes the status of user’s position. If the health factor is lower than 1, the position may be liquidated, because that would mean the value of collaterals is not enough to back the value of borrowings.

diff --git a/index.html b/index.html index 6db54895..b5b0a10f 100644 --- a/index.html +++ b/index.html @@ -52,7 +52,7 @@ /static/1265356b9e6bab3ff8ccafeaca6d96b8/aba1d/profile-pic.jpg 1.5x, /static/1265356b9e6bab3ff8ccafeaca6d96b8/b315d/profile-pic.jpg 2x" />Joel Mun

Written by Joel Mun. Joel likes Typescript, React, Node.js, GoLang, Python, Wasm and more. He also loves to enlarge the boundaries of his knowledge, mainly by reading books and watching lectures on Youtube. Guitar and piano are necessities at his home.

Projects👉 PostsJournalsTags

Decoding calldata on Starknet

January 26, 2024| ⏳ 3 min read

This is a brief summary of the anatomy of Starknet’s calldata and how to decode it. The Internet needs more information about how Starknet…

Technical intro to DeFi lending protocols with zkLend codebase as an example

October 26, 2023| ⏳ 36 min read

How a lending protocol works Functionalities Deposit Borrow Leverage Other usages Liquidate Repay Over-collateralization Utilization rate…

Full summary of <Life 3.0: Being Human in the Age of Artificial Intelligence> (14 / 100)

January 01, 2023| ⏳ 18 min read

I noticed that my previous summaries are too comprehensive and inclusive, so from now on I will try to be more succinct. Here’s the summary…

Full summary of <Think Again> by Adam Grant (13 / 100)

December 30, 2022| ⏳ 15 min read

This is my summary on Think Again by Adam Grant, who’s already a very renowned professor teaching at Wharton, and also a bestselling author…

All-in-one data structures and algorithms cheatsheet

September 15, 2022| ⏳ 67 min read

Preface This is my own curated list of tips and tricks about data structures and algorithms, in Python or TypeScript. Checklist If you are…

Announcing a performant knowledge graph visualization tool for Notion and its underlying library

September 06, 2022| ⏳ 11 min read

Why does Notion not have a built-in knowledge graph? So I made it myself The libraries: and Why I made How I made it The way forward…

Hong Kong government announces the list of quarantine designated hotels for the 8th cycle, and here's how I became the first person in the world to know it.

June 17, 2022| ⏳ 9 min read

This is a brief story of how I chose the best hotel for quarantine upon arrival in Hong Kong I could possibly choose using Github actions…

Practical Emscripten & Webassembly: Simple RISC-V Simulator on the web

May 14, 2022| ⏳ 13 min read

So I have enrolled in this computer architecture class for the current semester. The lecture is based on the RISC-V instruction set, which…

How to compile C++ code into Webassembly with Emscripten + Docker and use it in Webpack + Typescript + React project

May 09, 2022| ⏳ 7 min read

Rationale I have been trying to port some C++ project to Webassembly for the first time (I have worked on Assemblyscript and Rust projects…

<평균의 종말> 완전 요약 및 감상평 (12 / 100)

February 20, 2022| ⏳ 64 min read

TED…

12 Rules for Life: a detailed summary and reflection of a truly life-changing book (11 / 100)

February 17, 2022| ⏳ 75 min read

12 Rules for Life This post is a very detailed summary and reflection of Jordan Peterson’s book: 12 Rules for Life. Each short summary…

100BooksProject: (10): Cosmos

February 02, 2022| ⏳ 11 min read

This is a personal reflection and review of the book Cosmos. Table of contents Cosmos Cosmos is (and has been) everything Human intellect…

Find the shortest palindrome: an intensive review of the KMP(Knuth–Morris–Pratt) algorithm

January 06, 2022| ⏳ 19 min read

Find the shortest palindrome Inefficient algorithm: bruteforcing Longest Proper Prefix which is Suffix (LPS) The KMP algorithm Finding the…

elasticpwn: how to collect and analyse data from exposed Elasticsearch and Kibana instances

January 02, 2022| ⏳ 6 min read

Your Elasticsearch and Kibana instances are open, and that’s a real problem I have been doing some serious research on the public exposure…

Set up multiple monitors on Optimus laptop running Kali linux

September 17, 2021| ⏳ 7 min read

I needed to figure out how to set up multiple monitors on my new Kali linux laptop, because it wouldn’t just work automatically like MacOS…

Complete end-to-end guide for developing dockerized lambda in Typescript, Terraform and SAM CLI

March 13, 2021| ⏳ 40 min read

This is a full guide to locally develop and deploy a backend app with a recently released container image feature for lambda on AWS…

100BooksProject: (9): Atomic habits

October 04, 2020| ⏳ 30 min read

As always, I’ve summarized the book so that anyone can get the main concept in about 20-30 mins. And then I wrote some reflections and…

100BooksProject: (8): 두려움 없는 조직 (The fearless organization)

September 26, 2020| ⏳ 45 min read

회사에서 ‘사실상 PM’과 개발자 역할을 같이 수행하게 되면서, 경영에 자꾸 관심이 갔다. 내가 부족하다는 느낌이 많이 들었다. 그래서 무작정 Yes2…

How to make useSelector not a disaster

September 13, 2020| ⏳ 8 min read

Disclaimer: We will focus on itself in this article, rather than third-party libraries like , because it’s out of scope of the article. How…

100BooksProject: (7): 돈의 속성 (Properties of money)

August 18, 2020| ⏳ 47 min read

오랜만에 반디앤루니스에 가서 읽을 만한 책을 고르다가 베스트셀러를 차지하고 있는 돈의 속성을 사 봤다. 매번 그래왔듯이, 20~3…

100BooksProject: (6): How to Win Friends & Influence People (인간관계론)

August 17, 2020| ⏳ 40 min read

I wrote a summary of each section and my reflections following it. typo alert: I typed so much that I can’t just review all… if you find a…

Learn all major functionalities on Chrome's Performance tab and practice it with a simple React project

August 03, 2020| ⏳ 14 min read

Yes, Chrome’s performance tab is overwhelming First time you look into all the charts, you have no idea what means what. What does those…

100BooksProject: (5): 초격차 (The Great Gap)

July 29, 2020| ⏳ 51 min read

초격차 경영에 관련된 책이라 일단은 무작정 사 보게 됐다. 일단 나중에 다시 책 볼 필요 없이 줄거리만 20~30분 안에 읽을 수 있는 정도로 요약했다. 그리고 나서 맨 마지막에 느낀 점과 나에게 적용할 수 있는 점을 적어보았다. 초격차 프롤로그…

100BooksProject: (4): Ego is the enemy (에고라는 적)

July 05, 2020| ⏳ 19 min read

Summary Introduction Craving (열망) Success (성공) 실패 (Failure) Impressions after finishing this book The ways to survive in whichever contexts…

How to communicate better

June 06, 2020| ⏳ 5 min read

I find this problem happening very often. And I never expected it to be so. I thought everyone in the world is able to express his/her…

Extensive introduction to why and how you might want to use and test redux-observable

January 24, 2020| ⏳ 22 min read

Problem I was struggling at my company trying to write some tests for RxJS operations. +/static/1265356b9e6bab3ff8ccafeaca6d96b8/b315d/profile-pic.jpg 2x" src="/static/1265356b9e6bab3ff8ccafeaca6d96b8/99438/profile-pic.jpg" alt="Joel Mun" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/>

Written by Joel Mun. Joel likes Typescript, React, Node.js, GoLang, Python, Wasm and more. He also loves to enlarge the boundaries of his knowledge, mainly by reading books and watching lectures on Youtube. Guitar and piano are necessities at his home.

Projects👉 PostsJournalsTags

Decoding calldata on Starknet

January 26, 2024| ⏳ 3 min read

This is a brief summary of the anatomy of Starknet’s calldata and how to decode it. The Internet needs more information about how Starknet…

Technical intro to DeFi lending protocols with zkLend codebase as an example

October 26, 2023| ⏳ 40 min read

How a lending protocol works Functionalities Deposit Borrow Leverage Other usages Liquidate Repay Over-collateralization Utilization rate…

Full summary of <Life 3.0: Being Human in the Age of Artificial Intelligence> (14 / 100)

January 01, 2023| ⏳ 18 min read

I noticed that my previous summaries are too comprehensive and inclusive, so from now on I will try to be more succinct. Here’s the summary…

Full summary of <Think Again> by Adam Grant (13 / 100)

December 30, 2022| ⏳ 15 min read

This is my summary on Think Again by Adam Grant, who’s already a very renowned professor teaching at Wharton, and also a bestselling author…

All-in-one data structures and algorithms cheatsheet

September 15, 2022| ⏳ 67 min read

Preface This is my own curated list of tips and tricks about data structures and algorithms, in Python or TypeScript. Checklist If you are…

Announcing a performant knowledge graph visualization tool for Notion and its underlying library

September 06, 2022| ⏳ 11 min read

Why does Notion not have a built-in knowledge graph? So I made it myself The libraries: and Why I made How I made it The way forward…

Hong Kong government announces the list of quarantine designated hotels for the 8th cycle, and here's how I became the first person in the world to know it.

June 17, 2022| ⏳ 9 min read

This is a brief story of how I chose the best hotel for quarantine upon arrival in Hong Kong I could possibly choose using Github actions…

Practical Emscripten & Webassembly: Simple RISC-V Simulator on the web

May 14, 2022| ⏳ 13 min read

So I have enrolled in this computer architecture class for the current semester. The lecture is based on the RISC-V instruction set, which…

How to compile C++ code into Webassembly with Emscripten + Docker and use it in Webpack + Typescript + React project

May 09, 2022| ⏳ 7 min read

Rationale I have been trying to port some C++ project to Webassembly for the first time (I have worked on Assemblyscript and Rust projects…

<평균의 종말> 완전 요약 및 감상평 (12 / 100)

February 20, 2022| ⏳ 64 min read

TED…

12 Rules for Life: a detailed summary and reflection of a truly life-changing book (11 / 100)

February 17, 2022| ⏳ 75 min read

12 Rules for Life This post is a very detailed summary and reflection of Jordan Peterson’s book: 12 Rules for Life. Each short summary…

100BooksProject: (10): Cosmos

February 02, 2022| ⏳ 11 min read

This is a personal reflection and review of the book Cosmos. Table of contents Cosmos Cosmos is (and has been) everything Human intellect…

Find the shortest palindrome: an intensive review of the KMP(Knuth–Morris–Pratt) algorithm

January 06, 2022| ⏳ 19 min read

Find the shortest palindrome Inefficient algorithm: bruteforcing Longest Proper Prefix which is Suffix (LPS) The KMP algorithm Finding the…

elasticpwn: how to collect and analyse data from exposed Elasticsearch and Kibana instances

January 02, 2022| ⏳ 6 min read

Your Elasticsearch and Kibana instances are open, and that’s a real problem I have been doing some serious research on the public exposure…

Set up multiple monitors on Optimus laptop running Kali linux

September 17, 2021| ⏳ 7 min read

I needed to figure out how to set up multiple monitors on my new Kali linux laptop, because it wouldn’t just work automatically like MacOS…

Complete end-to-end guide for developing dockerized lambda in Typescript, Terraform and SAM CLI

March 13, 2021| ⏳ 40 min read

This is a full guide to locally develop and deploy a backend app with a recently released container image feature for lambda on AWS…

100BooksProject: (9): Atomic habits

October 04, 2020| ⏳ 30 min read

As always, I’ve summarized the book so that anyone can get the main concept in about 20-30 mins. And then I wrote some reflections and…

100BooksProject: (8): 두려움 없는 조직 (The fearless organization)

September 26, 2020| ⏳ 45 min read

회사에서 ‘사실상 PM’과 개발자 역할을 같이 수행하게 되면서, 경영에 자꾸 관심이 갔다. 내가 부족하다는 느낌이 많이 들었다. 그래서 무작정 Yes2…

How to make useSelector not a disaster

September 13, 2020| ⏳ 8 min read

Disclaimer: We will focus on itself in this article, rather than third-party libraries like , because it’s out of scope of the article. How…

100BooksProject: (7): 돈의 속성 (Properties of money)

August 18, 2020| ⏳ 47 min read

오랜만에 반디앤루니스에 가서 읽을 만한 책을 고르다가 베스트셀러를 차지하고 있는 돈의 속성을 사 봤다. 매번 그래왔듯이, 20~3…

100BooksProject: (6): How to Win Friends & Influence People (인간관계론)

August 17, 2020| ⏳ 40 min read

I wrote a summary of each section and my reflections following it. typo alert: I typed so much that I can’t just review all… if you find a…

Learn all major functionalities on Chrome's Performance tab and practice it with a simple React project

August 03, 2020| ⏳ 14 min read

Yes, Chrome’s performance tab is overwhelming First time you look into all the charts, you have no idea what means what. What does those…

100BooksProject: (5): 초격차 (The Great Gap)

July 29, 2020| ⏳ 51 min read

초격차 경영에 관련된 책이라 일단은 무작정 사 보게 됐다. 일단 나중에 다시 책 볼 필요 없이 줄거리만 20~30분 안에 읽을 수 있는 정도로 요약했다. 그리고 나서 맨 마지막에 느낀 점과 나에게 적용할 수 있는 점을 적어보았다. 초격차 프롤로그…

100BooksProject: (4): Ego is the enemy (에고라는 적)

July 05, 2020| ⏳ 19 min read

Summary Introduction Craving (열망) Success (성공) 실패 (Failure) Impressions after finishing this book The ways to survive in whichever contexts…

How to communicate better

June 06, 2020| ⏳ 5 min read

I find this problem happening very often. And I never expected it to be so. I thought everyone in the world is able to express his/her…

Extensive introduction to why and how you might want to use and test redux-observable

January 24, 2020| ⏳ 22 min read

Problem I was struggling at my company trying to write some tests for RxJS operations. Most of the network requests were managed by RxJS…

The shortcuts I like

January 08, 2020| ⏳ 7 min read

Before we get started These are the keys usually used in combination with normal keys on Mac: Command (or Cmd) ⌘ Shift ⇧ (I will write as…

The rise of low-level programming (feat. WebAssembly)

December 07, 2019| ⏳ 4 min read

Back in the old days There was no alternative to javascript. It was just javascript. Javascript just happened to be with the web when it got…

[...].forEach(saveFromZombies) does not always save people

October 12, 2019| ⏳ 4 min read

The first encounter with the problem I was coding as usual. And I faced an odd encounter with how works. Here goes the code to give an…

Making stupid react smart in re-rendering

October 02, 2019| ⏳ 12 min read

I believed React is smart enough to know this… but aye? We had a bunch of large component trees in our company’s web application. And I saw…

New javascript specifications in 2019 (What's new in Javascript - Google I/O '19)

September 10, 2019| ⏳ 10 min read

Must must must watch! I got so much great insight from this video in Google IO 2019 detailing latest javascript specs. Improvements from the…

Using docker and docker-compose to read data from excel to put it into mongodb

August 17, 2019| ⏳ 14 min read

What am I going to do? So I have my university’s timetable data in excel: I want to read this data using exceljs + node + typescript, and…

100BooksProject: (3): When the breath becomes air (숨결이 바람 될 때)

August 03, 2019| ⏳ 4 min read

1 2 3 4 1 면접이 끝나고 책 하나를 마음 놓고 읽었다. 죽음에 관한.. 책. 폴 칼라니티란 사람은 천재. 스탠포드, 캐임브리지, 예일대에서 문학, 생물학, 의예과 학위를 섭렵한 사람. 근데 36세에 암을 선고받고 살아가게 된다. 선고받은지…

Memoization in python using a decorator: getting a prime number

May 05, 2019| ⏳ 4 min read

Rationale I was creating an function: Time complexity For now, forget about the condition in the while loop: . You know that you are going…

Deploying a serverless crawler with python + lambda

March 18, 2019| ⏳ 5 min read

What I’m gonna build I’m gonna build a simple crawler that will send HTTP request to zigbang.com’s api server to receive data on estates…

Updating python 3 on an outdated Ubuntu

March 13, 2019| ⏳ 4 min read

The problem Sometimes you cannot easily change the version of Ubuntu and python if you are running them on cloud. I am using cloud9, as you…

100 Books Project (2): Crossroads (갈림길)

March 13, 2019| ⏳ 23 min read

Encountering the book Sudden encounter over this book just walking by a random bookshelf in the library led me to spend the whole afternoon…

100 Books Project (1): Reflection and reading notes on Remember Who You Are: Life Stories That Inspire the Heart and Mind

March 09, 2019| ⏳ 6 min read

Reflections Again, another big encouragement from a book. Things I got to know: Success is not everything. Things after success are…

random hacking notes

November 02, 2018| ⏳ 15 min read

Hacking-notes Rationale As a programmer, I thought it’d be nice to know the basics of hacking for security purposes. For now, we will focus…

Vue

October 19, 2018| ⏳ 10 min read

No time Really, I’ve got no time to learn Vue. I need to get basic concepts in the shortest length of time. Vue Cli 3.0 We are going to use…

Async, await, promise

September 05, 2018| ⏳ 4 min read

Async and await See javascript.info Mozilla Basics Ok. This is not ES6. Its ES8 (ECMAScript 2017) syntax. The async function declaration…

This & Object prototypes (2): this All Makes Sense Now!

June 30, 2018| ⏳ 6 min read

1. Default binding without the strict mode in effect, the global object is eligible for the default binding: however, with the strict mode…

This & Object prototypes (3): Objects

June 30, 2018| ⏳ 9 min read

Types and objects Types in JS string number boolean null undefined object Built-in Objects String Number Boolean Object Function Array Date…

Some hacking notes

May 29, 2018| ⏳ 9 min read

detecting security incidents event logs by SANS Regular Expressions to look for Keys to Check Domain controller Monitoring Active…

Using foresic tools (1): process explorer, process monitor, and autoruns

May 19, 2018| ⏳ 3 min read

Sources CSO microsoft gist Corrie Erk Process explorer Submit to VirusTotal to check hash Process Explorer -> Options -> VirusTotal.com…

MD5 and SHA

May 12, 2018| ⏳ 2 min read

Sources Lifewire SSLStore Hash A hashing algorithm is a mathematical function that condenses data to a fixed size. It is easier for the…

Three-way handshake in TCP & ACK and SYN flood attack

May 12, 2018| ⏳ 2 min read

Sources mazebolt microsoft geeksforgeeks ddosguard flowguard wikipedia Three-way handshake: how does it work When is it used TCP three-way…

Base64, Unicode, ASCII, URL

May 11, 2018| ⏳ 7 min read

Base64 Sources lifewire mozilla oracle base64 helper Definition “Base64 is a group of similar binary-to-text encoding schemes that represent…

Listening, Established, Close_wait and Time_wait in netstat

May 06, 2018| ⏳ 2 min read

Sources superuser askubuntu Due to the way TCP/IP works, connections can not be closed immediately. Packets may arrive out of order or be…

Basic system status checkups for windows

May 06, 2018| ⏳ 2 min read

Helpful link asecurity Windows (.bat) First, download and add to path: handle pstools autoruns listdlls uptime ntlast And run this batch…

preparing for DEF CON@COMROKFLT 2018

May 05, 2018| ⏳ 3 min read

Problem Well, obviously there will be a DEF CON@COMROKFLT about three weeks later. This one will be particular; it is a joint DEF CON…

Meta tags

April 29, 2018| ⏳ 3 min read

Sources 1&1 Stackoverflow post CSS-Tricks Google buildwebsite4u Facebook’s open graph protocol gaijin, a metatag generator List of useful…

My story (1): changes that occurred from high school to the Navy

April 20, 2018| ⏳ 27 min read

Before starting to read this People change. And so do I. This is a story of how I, as a developer and social being, changed over a course of…

This & Object prototypes (1): this or That?

April 09, 2018| ⏳ 3 min read

Using is confusing The first confusion Avoiding using Or, using The method allows you to point to the object in the first argument…

Scope and closure (6): Dynamic scope, Polyfilling Block Scope, and Lexical-this

April 08, 2018| ⏳ 2 min read

Dynamic scope “Dynamic scope seems to imply, and for good reason, that there’s a model whereby scope can be determined dynamically at…

Presentation design: Marketing project on Spotify

April 08, 2018| ⏳ 1 min read

1 diff --git a/page-data/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/page-data.json b/page-data/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/page-data.json index 2f0e3b4c..c5bc05f6 100644 --- a/page-data/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/page-data.json +++ b/page-data/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-blog-post-js","path":"/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/","result":{"data":{"site":{"siteMetadata":{"title":"Joel's dev blog","author":"Joel Mun","siteUrl":"https://9oelm.github.io"}},"markdownRemark":{"id":"ed52fd21-2ed5-5b1f-8b99-60b5581d9e7c","excerpt":"I noticed that my previous summaries are too comprehensive and inclusive, so from now on I will try to be more succinct. Here’s the summary for Life 3.0: Being…","html":"

I noticed that my previous summaries are too comprehensive and inclusive, so from now on I will try to be more succinct. Here’s the summary for Life 3.0: Being Human in the Age of Artificial Intelligence by Max Tegmark.\nInitially, upon the introduction of ChatGPT and realizing how powerful this is, I was curious if my job is really going to disappear in the future, and if so, how and when. I also wanted to know how difficult it is and what is required to build a general AI that knows and learns about general human behavior and needs. In essence, I was in a deep search of the progression and potential of AI towards the future.

\n

\n \n \n \n \n \n

  • Is consciousness beyond science?: Any theory predicting which physical systems are conscious is scientific (the pretty hard problem)
  • \n
  • What behaviors are conscious: although we are conscious, we ignore most parts of the sensory inputs and selectively focus on a few of them.
  • \n
  • Where is consciousness?: organs responsible for taking the sensory inputs are not only related to consciousness. Retina takes the same input on visual tricks, but the brain thinks differently as it looks at them. Hunger and nausea don’t stem from the stomach but the brain.
  • \n
  • When is consciousness?: human consciousness lives in the past by a quarter second. Reflexes are unconscious.
  • \n
  • How might AI consciousness feel?: it’s impossible to answer this question fully with the state-of-the-art theories.
  • \n
  • Meaning: the future of intelligence is important, but the future of consciousness is ever more important than that because it gives meaning. Without consciousness, there is no meaning. If machines get smarter than us, we need to value the ability to subjectively experience qualia more than the ability to think intelligently.
  • \n\n

    Thoughts

    \n
      \n
    • \n

      Adaptability. As the author mentions, no one knows what is going to happen next for AI, and when it is going to happen, if anything happens. This gives a sufficient reason for me to think more about adaptability and wisdom. If you don’t know where the ball is going to be thrown at in dodgeball, make yourself more agile and shrewd. Essentially, this goes back to Think Again as well, because it’s the best when you constantly adapt you to the ever-changing environment. And the biggest threat to adaptability, in my opinion, is actually laziness and comfort. It’s fundamental human nature that we want to take a rest and chill. But we need to do beyond that in order to do well. This is why I think I should keep up with the macro trend in several big factors that run the world, like tech, economy, politics and so on.

      \n
    • \n
    • \n

      So am I losing my job? For the reasons previously laid out, the answer is that I don’t know yet, but I should closely monitor the development of AI so that I can change my path accordingly. But it’s quite true that already some professions are being eclipsed by AI. One day I stumbled on a JD of copywriter, and it actually has ‘experience with ChatGPT or other AI tools’ as something good to have. But the next step might just be AI replacing the role of copywriter completely, because it already is so good at this kind of task. For example:\n\n \n \n \n \n \n\n \n I noticed that my previous summaries are too comprehensive and inclusive, so from now on I will try to be more succinct. Here’s the summary for Life 3.0: Being Human in the Age of Artificial Intelligence by Max Tegmark.\nInitially, upon the introduction of ChatGPT and realizing how powerful this is, I was curious if my job is really going to disappear in the future, and if so, how and when. I also wanted to know how difficult it is and what is required to build a general AI that knows and learns about general human behavior and needs. In essence, I was in a deep search of the progression and potential of AI towards the future.

      \n

      \n \n \n \n \n

    • \n
    • Is consciousness beyond science?: Any theory predicting which physical systems are conscious is scientific (the pretty hard problem)
    • \n
    • What behaviors are conscious: although we are conscious, we ignore most parts of the sensory inputs and selectively focus on a few of them.
    • \n
    • Where is consciousness?: organs responsible for taking the sensory inputs are not only related to consciousness. Retina takes the same input on visual tricks, but the brain thinks differently as it looks at them. Hunger and nausea don’t stem from the stomach but the brain.
    • \n
    • When is consciousness?: human consciousness lives in the past by a quarter second. Reflexes are unconscious.
    • \n
    • How might AI consciousness feel?: it’s impossible to answer this question fully with the state-of-the-art theories.
    • \n
    • Meaning: the future of intelligence is important, but the future of consciousness is ever more important than that because it gives meaning. Without consciousness, there is no meaning. If machines get smarter than us, we need to value the ability to subjectively experience qualia more than the ability to think intelligently.
    • \n
    \n

    Thoughts

    \n
    \n

    Here’s an extensive intro to DeFi lending protocols, especially for a dummy like me. We will look at fundamental concepts and theory, primarily complemented by zklend smart contract codebase at the commit hash of 10dfb3d1f01b1177744b038f8417a5a9c3e94185.

    \n

    How a lending protocol works

    \n

    Functionalities

    \n

    There are 4 major functionalities that a lending protocol provides to the users:

    \n

    Deposit

    \n

    Users can deposit tokens into the smart contract as much as they want. For example, I can deposit 1000 USDC into the smart contract.

    \n

    This will accrue interest over time. The interest accrual can only happen when there is at least one borrower, because the borrower will need to pay for the depositor’s interests. Otherwise, the interest rate stays at 0%. Deposit is the beginning of every other functionalities.

    \n

    Borrow

    \n

    Users are able to borrow certain amount of token, but only based on the amount of collaterals by depositing. For example, you may supply 1.1 ETH to borrow 500 USDC, only if 1.1 ETH is sufficiently more than the value of 500 USDC in USD.

    \n

    For example, 1.1 ETH is 1,813.79 USD today, and 500 USDC is approximately 500 USD. The collateralization factor, which denotes the value of borrowed amount that can be covered with an amount of collateralized token, is 80%. This means 1813.79×0.8=1451.0321813.79 \\times 0.8 = 1451.0321813.79×0.8=1451.032 USD worth of other assets can be borrowed from the protocol. This way, all of the borrowings in the protocol are backed by overly sufficient amount of tokens, which is called overcollateralization.

    \n

    But why borrow in the first place, if you already have as much as (or even more than) the amount of token that you want to borrow?

    \n

    Below are the possible reasons:

    \n

    Leverage

    \n

    Here’s a table describing the variables for this example:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    namevalue
    Price of 1 ETH in USD1200
    Price of 1 USDC in USD1
    Bob’s initial ETH balance2
    Collateral factor of ETH80%
    Gas fees, interests0
    \n

    Bob thinks the value of ETH will dramatically increase over time, so just a simple x1 long position is not enough for him. He is so sure that he want to get more than x1 profit when the price of ETH increases. This is how he would position himself at a leveraged long position:

    \n
      \n
    1. Bob deposits 2 ETH into the protocol
    2. \n
    3. Bob borrows 1200×2×0.5=1200<1200×2×0.8=19201200 \\times 2 \\times 0.5 = 1200 < 1200 \\times 2 \\times 0.8 = 19201200×2×0.5=1200<1200×2×0.8=1920 worth of USDC from the protocol. Now he can spend 1200 USDC for whatever he wants.
    4. \n
    5. Bob goes to Uniswap, and swaps 1200 USDC for 1 ETH.
    6. \n
    7. Now Bob owns 3 ETH, although he has a debt of 1200 USDC.
    8. \n
    9. A few days later, the price of 1 ETH in USD increases to 1600 USD. Now his total profit off of this increase is 1600×31200×3=12001600 \\times 3 - 1200 \\times 3 = 12001600×31200×3=1200:\n
        \n
      1. Bob now swaps his 1 ETH with 1600 USDC. Then he repays for his 1200 USDC debt. He still has 400 USDC left in his wallet.
      2. \n
      3. Then Bob withdraws 2 ETH from the protocol, and swaps that for 1600×2=32001600 \\times 2 = 32001600×2=3200 USDC at Uniswap.
      4. \n
      5. 3200+400=36003200 + 400 = 36003200+400=3600 USDC is the total he now has.
      6. \n
      7. Bob started with 2 ETH that was equivalent to 2400 USD, but after the price increase, he made additional 400 USD in addition to 800 USD that is the increase in the USD value of 2 ETH.
      8. \n
      \n
    10. \n
    11. Imagine if Bob held 3 ETH from the beginning. Then this would be valued at 4800 USD after the price increase, leaving Bob with 1200 USD of profit as well. Without the protocol, he wouldn’t have been able to achieve the same profit.
    12. \n
    \n

    Other usages

    \n

    Other use cases include shorting, liquidity management, arbitraging, etc. The list is not exhausitive and readily available on the Internet.

    \n

    Liquidate

    \n

    The value of tokens supplied as collaterals fluctuates over time. For example, if you have deposited 1.1 ETH as a collateral, it might be 1790 USD today, but it could have been 1809.47 USD yesterday. In such a case, the total value of collaterals might not be able to cover the total amount of tokens borrowed by a user.

    \n

    Then other users get a chance to liquidate that user’s position. In liquidation, a substantial amount of the user’s debt can be repaid by the liquidator, and the borrower will take an additional percentage of the user’s collateral as a liquidation bonus (or a penalty, from POV of the borrower). More on this later too.

    \n

    Repay

    \n

    Repay for the amount of token you borrowed, plus the accrued interest.

    \n

    Over-collateralization

    \n

    Any lending protocol would want to assume over-colleteralization for maximum measure of security. Typically, the more volatile/unstable the price of an asset is, the lower collateral factor it would count for.

    \n

    Usually, lending protocols would restrict which assets can be used as a collateral. However, it’s also worth noting that there are some protocols that allow users to create their own pairs of asset to be borrowed and collateralized.

    \n

    An example of overcollateralization can be seen in any lending protocols; For example, $LINK has a 79% of collateral factor on Compound today. This means if you deposit $100 worth of $LINK, you will be able to borrow $79 worth of any assets without being liquidated.

    \n

    Utilization rate

    \n

    We will need to define a few key terms to further understand other concepts.

    \n

    Utilization rate denotes how much of a single asset deposited in the pool is being borrowed at a time.

    \n
    \n

    The utilisation rate of each pool is a function of the current outstanding loan amount over the total capital supply in the specific liquidity pool (from zklend whitepaper)

    \n
    \n

    Here’s an example:

    \n
      \n
    • Bob deposits 100 USDT (worth 100 USD) in USDT pool
    • \n
    • Alice deposits 100 DAI (worth 100 USD) in DAI pool
    • \n
    • The collateral factor of DAI is 80%, so Alice borrows 80 USDT, which is the maximum of what she can safely borrow without getting liquidated
    • \n
    • Now, the utilization rate of USDT is 80100=80%\\frac{80}{100} = 80\\%10080=80% and that of DAI is 0100=0%\\frac{0}{100} = 0\\%1000=0%
    • \n
    \n

    Utilization rate of an asset will change whenever there is a new borrowing or supply.

    \n

    Here’s calculate_utilization_rate from zklend contract:

    \n
    fn calculate_utilization_rate(reserve_balance: felt252, total_debt: felt252) -> felt252 {\n    if total_debt == 0 {\n        0\n    } else {\n        let total_liquidity = safe_math::add(reserve_balance, total_debt);\n        let utilization_rate = safe_decimal_math::div(total_debt, total_liquidity);\n        utilization_rate\n    }\n}
    \n

    The function’s calculation is as follows: total debtreserve+total debt\\frac{\\text{total debt}}{reserve + \\text{total debt}}reserve+total debttotal debt. Here, reservereservereserve denotes the cash available in the liquidity pool that is not being utilized.

    \n

    Note that when in used in practice, total_debt will have to reflect not only the principal that was borrowed by the user, but also the interests accrued. Otherwise the calculation will result in a wrong value that does not take any interests accrued into account.

    \n

    Interest rate

    \n

    For the interest rate, we need to talk about the interest rate model first. An interest rate model is meant to encourage borrowing when there is enough capital, and discourage borrowing (encourage repayment) and additional supply when there is not enough capital. Logically, we arrive at the conclusion that the interest rate must be high when there is high utilization rate, and low when there is low utilization rate.

    \n

    We would also want to set an optimal utilization rate, because we generally would not want the utilization rate to be too low (no one’s using it) or too high (no one can borrow anymore).

    \n

    It would be worth noting that the interest rates will change when every single transaction that affects the balance of supply or debt happens. The scope of the interest rates to be discussed in this post, therefore, are only variable interest rates.

    \n

    Borrow interest rate

    \n

    With all of these in mind, we can introduce this interest rate model for borrowing:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    VariableMeaningDomain (or Range)
    UUUUtilization rateU[0,1]U \\in [0, 1]U[0,1]
    UoptimalU_{\\text{optimal}}UoptimalOptimal utilization rateUoptimal[0,1]U_{\\text{optimal}} \\in [0, 1]Uoptimal[0,1]
    RRRInterest rate at utiliation rate UUUR[0,]R \\in [0, \\infty]R[0,]
    Rslope1R_{\\text{slope1}}Rslope1Variable Rate Slope 1 (the slope that is going to be applied on the left side of the graph)Rslope1[0,]R_{\\text{slope1}} \\in [0, \\infty]Rslope1[0,]
    Rslope2R_{\\text{slope2}}Rslope2Variable Rate Slope 2 (the slope that is going to be applied on the right side of the graph)Rslope2[0,]R_{\\text{slope2}} \\in [0, \\infty]Rslope2[0,]
    R0R_0R0Base interest rate. This adjusts the y-intercept of the interest rate curve.R0[0,1]R_0 \\in [0, 1]R0[0,1]
    \n
    U<=UoptimalR=R0+UUoptimal(Rslope1)U <= U_{optimal} \\rArr R = R_0 + \\frac{U}{U_{\\text{optimal}}}(R_{\\text{slope1}})U<=UoptimalR=R0+UoptimalU(Rslope1)
    \n
    U>UoptimalR=R0+Rslope1+Rslope2UUoptimal1UoptimalU > U_{optimal} \\rArr R = R_0 + R_{\\text{slope1}} + R_{\\text{slope2}}\\frac{U - U_{\\text{optimal}}}{1 - U_{\\text{optimal}}}U>UoptimalR=R0+Rslope1+Rslope21UoptimalUUoptimal
    \n

    It may just be easy to use a graph to find out what it means rather than looking at the equations.

    \n

    You can interact with the graphs I created here.

    \n

    Below graph is the interest rate model for ‘Rate Strategy Stable One’ on Aave. This refers to “Low liquidity stablecoins have lower Optimal Utilisation Ratio than those with higher liquidity”. A rate strategy is just a list of variables used for the interest rate model. You would want to use different strategies for different crypto assets, owing to their stability/volatility. Every lending protocol has their own interest rate strategy for different assets; Zklend has also has one.

    \n

    \n \n \n \n \n \n\n \n \n \n \n

    \n

    But how is it done under the hood? Let’s read code on Zklend.

    \n

    Below are functions to calculate the balance of a ZToken.

    \n
    fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 {\n    felt_balance_of(self, account).into()\n}\n\nfn felt_balance_of(self: @ContractState, account: ContractAddress) -> felt252 {\n    let accumulator = internal::get_accumulator(self);\n\n    let balance = self.raw_balances.read(account);\n    let scaled_up_balance = safe_decimal_math::mul(balance, accumulator);\n\n    scaled_up_balance\n}
    \n

    felt_balance_of reads from ContractState the raw balance of the token, but it times that with accumulator. So that’s the secret. If the value of accumulator is somehow dynamic, every time your Metamask wallet calls balanceOf from the blockchain, the token balance should also be dynamic.

    \n

    But what does accumulator do? First of all, get_accumulator will get the corresponding accumulator that is in the Market contract by calling get_lending_accumulator.

    \n
    fn get_accumulator(self: @ContractState) -> felt252 {\n    let market_addr = self.market.read();\n    let underlying_addr = self.underlying.read();\n    IMarketDispatcher { contract_address: market_addr }.get_lending_accumulator(underlying_addr)\n}
    \n

    This is get_lending_accumulator, where the compound interest is calculated:

    \n
    fn get_lending_accumulator(self: @ContractState, token: ContractAddress) -> felt252 {\n    internal::assert_reserve_enabled(self, token);\n    let reserve = self.reserves.read_for_get_lending_accumulator(token);\n\n    let block_timestamp: felt252 = get_block_timestamp().into();\n    if reserve.last_update_timestamp == block_timestamp {\n        // Accumulator already updated on the same block\n        reserve.lending_accumulator\n    } else {\n        // Apply simple interest\n        let time_diff = safe_math::sub(block_timestamp, reserve.last_update_timestamp);\n\n        // Treats reserve factor as zero if treasury address is not set\n        let treasury_addr = self.treasury.read();\n        let effective_reserve_factor = if treasury_addr.is_zero() {\n            0\n        } else {\n            reserve.reserve_factor\n        };\n\n        let one_minus_reserve_factor = safe_math::sub(\n            safe_decimal_math::SCALE, effective_reserve_factor\n        );\n\n        // New accumulator\n        // (current_lending_rate * (1 - reserve_factor) * time_diff / SECONDS_PER_YEAR + 1) * accumulator\n        let temp_1 = safe_math::mul(reserve.current_lending_rate, time_diff);\n        let temp_2 = safe_math::mul(temp_1, one_minus_reserve_factor);\n        let temp_3 = safe_math::div(temp_2, SECONDS_PER_YEAR);\n        let temp_4 = safe_math::div(temp_3, safe_decimal_math::SCALE);\n        let temp_5 = safe_math::add(temp_4, safe_decimal_math::SCALE);\n        let latest_accumulator = safe_decimal_math::mul(temp_5, reserve.lending_accumulator);\n\n        latest_accumulator\n    }\n}
    \n

    Below is the description of the variables being used from self.reserves:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    namedescription
    last_update_timestampthe timestamp at which lending_accumulator was updated for the last time, in seconds since the epoch
    lending_accumulatorcumulated liquidity index (or simply interest index). Tracks the interest cumulated by the reserve during the time interval, updated whenever a borrow, deposit, repay, redeem, swap, liquidation event occurs.
    current_lending_ratethe current lending rate that was calculated by get_interest_rates of DefaultInterestRateModel
    \n

    The equation for calculating a cumulated liquidity/borrow index is as follows:

    \n
    Indexn=Indexn1×(1+r×t)Index_n = Index_{n-1} \\times (1 + r \\times t)Indexn=Indexn1×(1+r×t)
    \n

    where

    \n
    t=Time Interval (length of time since the last index calculation)t = \\text{Time Interval} \\text{ (length of time since the last index calculation)}t=Time Interval (length of time since the last index calculation)
    \n
    r=Interest Rater = \\text{Interest Rate}r=Interest Rate
    \n
    n=nth index calculatedn = n^{th}\\text{ index calculated}n=nth index calculated
    \n
    Index0=1Index_0 = 1Index0=1
    \n

    we can see that rrr is represented by current_lending_rate * (1 - reserve_factor), and ttt by time_diff / SECONDS_PER_YEAR (look at the comment in the code), and Indexn1Index_{n-1}Indexn1 by reserve.lending_accumulator.

    \n

    This works exactly the same way for get_debt_accumulator:

    \n
    fn get_debt_accumulator(self: @ContractState, token: ContractAddress) -> felt252 {\n    internal::assert_reserve_enabled(self, token);\n    let reserve = self.reserves.read_for_get_debt_accumulator(token);\n\n    let block_timestamp: felt252 = get_block_timestamp().into();\n    if (reserve.last_update_timestamp == block_timestamp) {\n        // Accumulator already updated on the same block\n        reserve.debt_accumulator\n    } else {\n        // Apply simple interest\n        let time_diff = safe_math::sub(block_timestamp, reserve.last_update_timestamp);\n\n        // (current_borrowing_rate * time_diff / SECONDS_PER_YEAR + 1) * accumulator\n        let temp_1 = safe_math::mul(reserve.current_borrowing_rate, time_diff);\n        let temp_2 = safe_math::div(temp_1, SECONDS_PER_YEAR);\n        let temp_3 = safe_math::add(temp_2, safe_decimal_math::SCALE);\n        let latest_accumulator = safe_decimal_math::mul(temp_3, reserve.debt_accumulator);\n\n        latest_accumulator\n    }\n}
    \n

    It is the same logic as get_lending_accumulator, except that the reserve factor is not a part of the equation because we are taking profit from the debtors, not for lending. That means we want to take the full borrow index as it is from the debt, and instead lower the liquidity index for lending so that the protocol can take certain % as a profit.

    \n

    Example: interest rate calculation

    \n

    To find the final amount of borrowing or deposit with the accrued interests considered, all you need to do is to multiply the raw principal value with the cumulated liquidity/borrow index. But calculating the index requires calculating interest rates. So let’s dive into one example. This example is based on zklend’s smart contract tests.

    \n

    Here’s an example for calculating accrued interests for borrowing and deposit.

    \n

    Let’s say we got $SIS and $BRO tokens:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    TokenOracle Price (USD)Collateral factorRslope1R_{\\text{slope1}}Rslope1Rslope2R_{\\text{slope2}}Rslope2R0R_0R0UoptimalU_{\\text{optimal}}UoptimalReserve factor
    $SIS5050%0.10.51%60%10%
    $BRO10075%0.20.35%80%20%
    \n

    Bob deposits 100001000010000 $BRO, Alice deposits 100100100 $SIS.

    \n

    Alice borrows

    \n

    22.5 $BRO =

    \n
    22.5×$100=$2250<100×50×0.5=$250022.5 \\times \\$100 = \\$2250 < 100 \\times 50 \\times 0.5 = \\$250022.5×$100=$2250<100×50×0.5=$2500
    \n

    which is well within the value of collateral supplied.

    \n

    Now,

    \n
    UBRO=22.510,000=0.00225=0.225%<UBROOptimal=80%U_{BRO} = \\frac{22.5}{10,000} = 0.00225 \\newline = \n0.225\\% < U_{{BRO}{_{Optimal}}} = 80\\% \\newlineUBRO=10,00022.5=0.00225=0.225%<UBROOptimal=80%
    \n
    UBRO<=UBROOptimalRBROBorrow=RBRO0+UBROUBROOptimal(UBROslope1)=0.05+0.002250.8×0.2=0.0505625U_{BRO} <= U_{{BRO}{_{Optimal}}} \\rArr \\newline R_{{\\text{BRO}}_\\text{Borrow}} = R_{{\\text{BRO}}_\\text{0}} + \\frac{U_{BRO}}{U_{{BRO}{_{Optimal}}}}(U_{{BRO}{_{slope1}}}) \\newline = 0.05 + \\frac{0.00225}{0.8} \\times 0.2 = 0.0505625UBRO<=UBROOptimalRBROBorrow=RBRO0+UBROOptimalUBRO(UBROslope1)=0.05+0.80.00225×0.2=0.0505625
    \n

    Now we calculate the supply interest rate, but without considering the reserve factor for now.

    \n
    RBROSupply (no reserve)=RBROBorrow×UBRO=0.0505625×0.00225=0.000113765625R_{{BRO}{_{\\text{Supply (no reserve)}}}} = R_{{\\text{BRO}}_\\text{Borrow}} \\times U_{BRO} = 0.0505625 \\times 0.00225 = 0.000113765625RBROSupply (no reserve)=RBROBorrow×UBRO=0.0505625×0.00225=0.000113765625
    \n

    So there we have it:

    \n
    RBROBorrow=0.0505625RBROSupply (no reserve)=0.000113765625R_{{\\text{BRO}}_\\text{Borrow}} = 0.0505625 \\newline\nR_{{BRO}{_{\\text{Supply (no reserve)}}}} = 0.000113765625RBROBorrow=0.0505625RBROSupply (no reserve)=0.000113765625
    \n

    Example (continued): cumulated liquidity index & cumulated borrow index calculation

    \n

    And let’s say:

    \n
      \n
    • 100 seconds have elapsed, starting from timestamp 0
    • \n
    • no cumulated liquidity index and cumulated borrow index were calculated before, making them 1 respectively by default
    • \n
    \n

    Then you can calculate them as follows:

    \n
    Cumulated Liquidity Indexn=Cumulated Liquidity Indexn1×(1+r×t)=1×(1+(0.000113765625×(10.2))×100365×86400)=1.000000000288598744292237442\\text{Cumulated Liquidity Index}_n = \\newline \n\\text{Cumulated Liquidity Index}_{n-1} \\times (1 + r \\times t) = \\newline\n1 \\times (1 + (0.000113765625 \\times (1 - 0.2)) \\times \\frac{100}{365 \\times 86400}) = 1.000000000288598744292237442\n\\newlineCumulated Liquidity Indexn=Cumulated Liquidity Indexn1×(1+r×t)=1×(1+(0.000113765625×(10.2))×365×86400100)=1.000000000288598744292237442
    \n
    Cumulated Borrow Indexn=Cumulated Borrow Indexn1×(1+r×t)=1×(1+0.0505625×100365×86400)=1.000000160332635717909690512\\text{Cumulated Borrow Index}_n = \\newline\n\\text{Cumulated Borrow Index}_{n-1} \\times (1 + r \\times t) = \\newline\n1 \\times (1 + 0.0505625 \\times \\frac{100}{365 \\times 86400}) = 1.000000160332635717909690512Cumulated Borrow Indexn=Cumulated Borrow Indexn1×(1+r×t)=1×(1+0.0505625×365×86400100)=1.000000160332635717909690512
    \n

    Instead of factoring the reserve factor into the lending rate, we instead do it when we calculate it in the cumulated liquidity index.

    \n

    ttt is calculated as 100 seconds divided by the number of seconds per year (without caring about leap years).

    \n

    Other than this, the calculation above should be straightforward.

    \n

    Liquidation

    \n

    A few more terms need to be defined to be able to understand liquidation.

    \n

    Health factor. Health factor=Collaterali×USD value of Collaterali×Collateral factoriLiabilityi×USD value of Liabilityi\\text{Health factor} = \\frac{\\sum{Collateral_i \\times \\text{USD value of Collateral}_i \\times \\text{Collateral factor}_i}}{\\sum{Liability_i \\times \\text{USD value of Liability}_i}}Health factor=Liabilityi×USD value of LiabilityiCollaterali×USD value of Collaterali×Collateral factori. It denotes the status of user’s position. If the health factor is lower than 1, the position may be liquidated, because that would mean the value of collaterals is not enough to back the value of borrowings.

    \n

    Liquidation bonus (or penalty, for debtors). Additional % applied on the collateral deposited by the debtor, which in turn is received by the liquidator after the repayment. This is to incentivize liquidators to liquidate.

    \n

    Potential liquidators will be monitoring the market closely and frequently, and try to call liquidate() earlier than their competitors with suitable amount of gas fee that might get their transaction get through earlier than others. Once the transaction gets through, the debtor’s position will be liquidated and the corresponding amount of collateral in USD plus the liquidation bonus will be paid back to the liquidator. Liquidators are only allowed to repay no more than the amount that will bring the borrower’s Health Factor back to 1.

    \n

    Liquidation is essential to the healthy operation of a lending protocol; it removes unhealthy, undercollateralized positions to keep it healthy.

    \n

    Technical review

    \n

    Deposit

    \n

    All typical user interactions will be done at market.cairo file.

    \n

    The deposit function in market.cairo will call deposit function from market/external.cairo:

    \n
    // deposit in market.cairo\n\nfn deposit(ref self: ContractState, token: ContractAddress, amount: felt252) {\n   external::deposit(ref self, token, amount)\n}
    \n

    Again, that will call internal::deposit from market/internal.cairo.

    \n
    // deposit in market/external.cairo\nfn deposit(ref self: ContractState, token: ContractAddress, amount: felt252) {\n    reentrancy_guard::start(ref self);\n    internal::deposit(ref self, token, amount);\n    reentrancy_guard::end(ref self);\n}
    \n
    // deposit in market/internal.cairo\nfn deposit(ref self: ContractState, token: ContractAddress, amount: felt252) {\n    assert(amount.is_non_zero(), errors::ZERO_AMOUNT);\n\n    let caller = get_caller_address();\n    let this_address = get_contract_address();\n\n    let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n        ref self, token\n    );\n\n    assert_reserve_enabled(@self, token);\n    let z_token_address = self.reserves.read_z_token_address(token);\n\n    // Updates interest rate\n    update_rates_and_raw_total_debt(\n        ref self,\n        token, // token\n        updated_debt_accumulator, // updated_debt_accumulator\n        false, // is_delta_reserve_balance_negative\n        amount, // abs_delta_reserve_balance\n        false, // is_delta_raw_total_debt_negative\n        0 // abs_delta_raw_total_debt\n    );\n\n    self\n        .emit(\n            contract::Event::Deposit(\n                contract::Deposit { user: caller, token: token, face_amount: amount }\n            )\n        );\n\n    // Takes token from user\n\n    let amount_u256: u256 = amount.into();\n    let transfer_success = IERC20Dispatcher {\n        contract_address: token, \n    }.transferFrom(caller, this_address, amount_u256);\n    assert(transfer_success, errors::TRANSFERFROM_FAILED);\n\n    // Mints ZToken to user\n    IZTokenDispatcher { contract_address: z_token_address }.mint(caller, amount);\n}
    \n

    The first thing that this deposit function does is to call update_accumulators:

    \n
    fn update_accumulators(ref self: ContractState, token: ContractAddress) -> UpdatedAccumulators {\n    let block_timestamp: felt252 = get_block_timestamp().into();\n\n    let updated_lending_accumulator = view::get_lending_accumulator(@self, token);\n    let updated_debt_accumulator = view::get_debt_accumulator(@self, token);\n\n    self\n        .emit(\n            contract::Event::AccumulatorsSync(\n                contract::AccumulatorsSync {\n                    token,\n                    lending_accumulator: updated_lending_accumulator,\n                    debt_accumulator: updated_debt_accumulator\n                }\n            )\n        );\n\n    // It's okay to call this function here as the updated accumulators haven't been written into\n    // storage yet\n    let amount_to_treasury = view::get_pending_treasury_amount(@self, token);\n\n    // No need to check reserve existence since it's done in `get_lending_accumulator` and\n    // `get_debt_accumulator`\n    let z_token_address = self.reserves.read_z_token_address(token);\n\n    self\n        .reserves\n        .write_accumulators(\n            token, block_timestamp, updated_lending_accumulator, updated_debt_accumulator\n        );\n\n    // No need to check whether treasury address is zero as amount would be zero anyways\n    if amount_to_treasury.is_non_zero() {\n        let treasury_addr = self.treasury.read();\n        IZTokenDispatcher {\n            contract_address: z_token_address\n        }.mint(treasury_addr, amount_to_treasury);\n    }\n\n    UpdatedAccumulators {\n        lending_accumulator: updated_lending_accumulator, debt_accumulator: updated_debt_accumulator\n    }\n}
    \n

    And this respectively calls accumulators for debt and lending:

    \n
    fn get_lending_accumulator(self: @ContractState, token: ContractAddress) -> felt252 {\n    internal::assert_reserve_enabled(self, token);\n    let reserve = self.reserves.read_for_get_lending_accumulator(token);\n\n    let block_timestamp: felt252 = get_block_timestamp().into();\n    if reserve.last_update_timestamp == block_timestamp {\n        // Accumulator already updated on the same block\n        reserve.lending_accumulator\n    } else {\n        // Apply simple interest\n        let time_diff = safe_math::sub(block_timestamp, reserve.last_update_timestamp);\n\n        // Treats reserve factor as zero if treasury address is not set\n        let treasury_addr = self.treasury.read();\n        let effective_reserve_factor = if treasury_addr.is_zero() {\n            0\n        } else {\n            reserve.reserve_factor\n        };\n\n        let one_minus_reserve_factor = safe_math::sub(\n            safe_decimal_math::SCALE, effective_reserve_factor\n        );\n\n        // New accumulator\n        // (current_lending_rate * (1 - reserve_factor) * time_diff / SECONDS_PER_YEAR + 1) * accumulator\n        let temp_1 = safe_math::mul(reserve.current_lending_rate, time_diff);\n        let temp_2 = safe_math::mul(temp_1, one_minus_reserve_factor);\n        let temp_3 = safe_math::div(temp_2, SECONDS_PER_YEAR);\n        let temp_4 = safe_math::div(temp_3, safe_decimal_math::SCALE);\n        let temp_5 = safe_math::add(temp_4, safe_decimal_math::SCALE);\n        let latest_accumulator = safe_decimal_math::mul(temp_5, reserve.lending_accumulator);\n\n        latest_accumulator\n    }\n}\n\nfn get_debt_accumulator(self: @ContractState, token: ContractAddress) -> felt252 {\n    internal::assert_reserve_enabled(self, token);\n    let reserve = self.reserves.read_for_get_debt_accumulator(token);\n\n    let block_timestamp: felt252 = get_block_timestamp().into();\n    if (reserve.last_update_timestamp == block_timestamp) {\n        // Accumulator already updated on the same block\n        reserve.debt_accumulator\n    } else {\n        // Apply simple interest\n        let time_diff = safe_math::sub(block_timestamp, reserve.last_update_timestamp);\n\n        // (current_borrowing_rate * time_diff / SECONDS_PER_YEAR + 1) * accumulator\n        let temp_1 = safe_math::mul(reserve.current_borrowing_rate, time_diff);\n        let temp_2 = safe_math::div(temp_1, SECONDS_PER_YEAR);\n        let temp_3 = safe_math::add(temp_2, safe_decimal_math::SCALE);\n        let latest_accumulator = safe_decimal_math::mul(temp_3, reserve.debt_accumulator);\n\n        latest_accumulator\n    }\n}
    \n

    We already discussed in depth what an accumulator is and why it needs to be used. They need to be updated every single time the corresponding interest rate needs to change.

    \n

    After updating the accumulators, amount_to_treasury is calculated and that amount is sent to the treasury, in the form of zToken. Remember that zToken is redeemable at 1:1 rate with the underlying asset, so it’s the same as sending real token to the reasury.

    \n

    Next in internal::deposit, update_rates_and_raw_total_debt is called. This function does two things as it name suggests; but it will only update the rate for deposit operation, as you can tell from the argument of 0 for abs_delta_raw_total_debt.

    \n

    However if reserve.last_update_timestamp is not the current block timestamp, the debt accumulator would still get updated and will affect the interest rate calculated inside update_rates_and_raw_total_debt, because the ‘scaled’ debt (that is, principal value of debt multiplied by the latest debt accumulator) would be slightly larger than the one calculated at the previous block timestamp.

    \n

    Here is update_rates_and_raw_total_debt:

    \n
    fn update_rates_and_raw_total_debt(\n    ref self: ContractState,\n    token: ContractAddress,\n    updated_debt_accumulator: felt252,\n    is_delta_reserve_balance_negative: bool,\n    abs_delta_reserve_balance: felt252,\n    is_delta_raw_total_debt_negative: bool,\n    abs_delta_raw_total_debt: felt252,\n) {\n    let this_address = get_contract_address();\n\n    let StorageBatch1{\n        interest_rate_model, raw_total_debt: raw_total_debt_before } = self\n        .reserves\n        .read_interest_rate_model_and_raw_total_debt(token);\n\n    // Makes sure reserve exists\n    // (the caller must check it's enabled if needed since it's not validated here)\n    assert(interest_rate_model.is_non_zero(), errors::RESERVE_NOT_FOUND);\n\n    let reserve_balance_before: felt252 = IERC20Dispatcher {\n        contract_address: token\n    }.balanceOf(this_address).try_into().expect(errors::BALANCE_OVERFLOW);\n\n    let reserve_balance_after = if is_delta_reserve_balance_negative {\n        safe_math::sub(reserve_balance_before, abs_delta_reserve_balance)\n    } else {\n        safe_math::add(reserve_balance_before, abs_delta_reserve_balance)\n    };\n\n    let raw_total_debt_after = if is_delta_raw_total_debt_negative {\n        safe_math::sub(raw_total_debt_before, abs_delta_raw_total_debt)\n    } else {\n        safe_math::add(raw_total_debt_before, abs_delta_raw_total_debt)\n    };\n\n    let scaled_up_total_debt_after = safe_decimal_math::mul(\n        raw_total_debt_after, updated_debt_accumulator\n    );\n    let ModelRates{lending_rate: new_lending_rate, borrowing_rate: new_borrowing_rate } =\n        IInterestRateModelDispatcher {\n        contract_address: interest_rate_model\n    }.get_interest_rates(reserve_balance_after, scaled_up_total_debt_after);\n\n    // Writes to storage\n    self.reserves.write_rates(token, new_lending_rate, new_borrowing_rate);\n    if raw_total_debt_before != raw_total_debt_after {\n        self.reserves.write_raw_total_debt(token, raw_total_debt_after);\n    }\n\n    self\n        .emit(\n            contract::Event::InterestRatesSync(\n                contract::InterestRatesSync {\n                    token, lending_rate: new_lending_rate, borrowing_rate: new_borrowing_rate\n                }\n            )\n        );\n}
    \n

    It reads existing interest_rate_model, raw_total_debt: raw_total_debt_before from the storage. And then runs calculations to get parameters for IInterestRateModelDispatcher.get_interest_rates.

    \n

    After that, the newly calculated interest rates and debt are updated.

    \n

    Finally, transferFrom of the underlying ERC20 token is called in internal::deposit function. This is where the actual transfer happens.

    \n

    Lastly, the interest bearing zToken of the exact same deposit amount for that specific ERC20 token is minted back to the user, so they can track the sum of their principal and interest at any time.

    \n

    Withdraw

    \n

    Everything that is meant to be accessed on zklend frontend is defined in src/market/external.cairo, just like deposit. So is withdraw, and it again calls internal::withdraw.

    \n

    withdraw in src/market/internal.cairo:

    \n
    fn withdraw(ref self: ContractState, token: ContractAddress, amount: felt252) {\n    assert(amount.is_non_zero(), errors::ZERO_AMOUNT);\n\n    let caller = get_caller_address();\n    withdraw_internal(ref self, caller, token, amount);\n}
    \n

    So the real meat must be in withdraw_internal.

    \n

    withdraw_internal in src/market/internal.cairo:

    \n
    fn withdraw_internal(\n    ref self: ContractState, user: ContractAddress, token: ContractAddress, amount: felt252\n) {\n    let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n        ref self, token\n    );\n\n    assert_reserve_enabled(@self, token);\n    let z_token_address = self.reserves.read_z_token_address(token);\n\n    // NOTE: it's fine to call out to external contract here before state update since it's trusted\n    let amount_burnt = burn_z_token_internal(ref self, z_token_address, user, amount);\n\n    // Updates interest rate\n    update_rates_and_raw_total_debt(\n        ref self,\n        token, // token\n        updated_debt_accumulator, // updated_debt_accumulator\n        true, // is_delta_reserve_balance_negative\n        amount_burnt, // abs_delta_reserve_balance\n        false, // is_delta_raw_total_debt_negative\n        0, // abs_delta_raw_total_debt\n    );\n\n    self\n        .emit(\n            contract::Event::Withdrawal(\n                contract::Withdrawal { user, token, face_amount: amount_burnt }\n            )\n        );\n\n    // Gives underlying tokens to user\n    let amount_burnt: u256 = amount_burnt.into();\n    let transfer_success = IERC20Dispatcher {\n        contract_address: token\n    }.transfer(user, amount_burnt);\n    assert(transfer_success, errors::TRANSFER_FAILED);\n\n    // It's easier to post-check collateralization factor, at the cost of making failed\n    // transactions more expensive.\n    let is_asset_used_as_collateral = is_used_as_collateral(@self, user, token);\n\n    // No need to check if the asset is not used as collateral at all\n    if is_asset_used_as_collateral {\n        assert_not_undercollateralized(@self, user, true);\n    }\n}
    \n

    Basically, the exact opposite of internal::deposit.

    \n

    The first bit is the same; It starts off by calling update_accumulators to get updated_debt_accumulator which will be used as an argument to update_rates_and_raw_total_debt:

    \n
    let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n        ref self, token\n    );
    \n

    Then, the corresponding amount of zToken is burnt:

    \n
    let z_token_address = self.reserves.read_z_token_address(token);\n\n// NOTE: it's fine to call out to external contract here before state update since it's trusted\nlet amount_burnt = burn_z_token_internal(ref self, z_token_address, user, amount);
    \n

    If amount is not zero, zToken’s external::burn will be called:

    \n
    fn burn(ref self: ContractState, user: ContractAddress, amount: felt252) {\n    internal::only_market(@self);\n\n    let accumulator = internal::get_accumulator(@self);\n\n    let scaled_down_amount = safe_decimal_math::div(amount, accumulator);\n    assert(scaled_down_amount.is_non_zero(), errors::INVALID_BURN_AMOUNT);\n\n    let raw_balance_before = self.raw_balances.read(user);\n    let raw_balance_after = safe_math::sub(raw_balance_before, scaled_down_amount);\n    self.raw_balances.write(user, raw_balance_after);\n\n    let raw_supply_before = self.raw_total_supply.read();\n    let raw_supply_after = safe_math::sub(raw_supply_before, scaled_down_amount);\n    self.raw_total_supply.write(raw_supply_after);\n\n    let amount: u256 = amount.into();\n    self\n        .emit(\n            contract::Event::Transfer(\n                contract::Transfer { from: user, to: contract_address_const::<0>(), value: amount }\n            )\n        );\n}
    \n

    let accumulator = internal::get_accumulator(@self); gets the current lending accumulator so it can be used for further calculations. Remember, zToken has a dynamic balance due to the accumulator.

    \n

    So why let scaled_down_amount = safe_decimal_math::div(amount, accumulator);? The reason is that the amount that the user requests to withdraw, passed down as amount: felt252 argument, already assumes that the amount has the interest index (the accumulator) factored in.

    \n

    Let’s go back to the example presented before.

    \n

    \n \n \n \n \n

    \n

    If you see aDAI (just imagine it’s a zToken, it’s the same thing essentially anyways) balance of 1011.4130020694421009831011.4130020694421009831011.413002069442100983 and let’s say you want to withdraw everything that you have.

    \n

    Then you know that the amount that you are looking at on MetaMask is the principal amount multiplied by the lending accumulator.

    \n

    However, in fn burn, we want to subtract the amount from the principal (denoted as raw_* in the code) which does not have the accumulator factored in; it is literally the exact amount that the user had deposited before. To be able to do that, we need to convert amount into the same scale. This is because we only store the principal value on the blockchain instead of the principal multiplied by the accumulator.

    \n

    Now we understand why it has to be:

    \n
    let raw_balance_after = safe_math::sub(raw_balance_before, scaled_down_amount);
    \n

    and

    \n
    let raw_supply_after = safe_math::sub(raw_supply_before, scaled_down_amount);
    \n

    Lastly, Transfer event where the token is sent to the null address is emitted, and burn function exits.

    \n

    Next up is update_rates_and_raw_total_debt:

    \n
    update_rates_and_raw_total_debt(\n    ref self,\n    token, // token\n    updated_debt_accumulator, // updated_debt_accumulator\n    true, // is_delta_reserve_balance_negative\n    amount_burnt, // abs_delta_reserve_balance\n    false, // is_delta_raw_total_debt_negative\n    0, // abs_delta_raw_total_debt\n);
    \n

    Similar to deposit function, we are running this because we know that the supply of a token has changed, and this must affect the position on the interest rate curve as per the earlier discussion. Notice true, // is_delta_reserve_balance_negative because we know that the reserve balance (the supply of the token being withdrawn) has decreased.

    \n

    Calling update_rates_and_raw_total_debt will update borrowing and lending interest rates.

    \n

    Then, the corresponding amount of ERC20 token is transferred to the user. Remember amount_burnt of zToken must be sent because a pair of zToken and ERC20 should be 1:1 in terms of their amounts.

    \n
    // Gives underlying tokens to user\nlet amount_burnt: u256 = amount_burnt.into();\nlet transfer_success = IERC20Dispatcher {\n    contract_address: token\n}.transfer(user, amount_burnt);
    \n

    After that, collateralization checks are done. If the user withdraws too much that his borrowing is undercollateralized, the transaction would fail.

    \n
    // It's easier to post-check collateralization factor, at the cost of making failed\n// transactions more expensive.\nlet is_asset_used_as_collateral = is_used_as_collateral(@self, user, token);\n\n// No need to check if the asset is not used as collateral at all\nif is_asset_used_as_collateral {\n    assert_not_undercollateralized(@self, user, true);\n}
    \n

    It’s easier to check collateralization after all calculations regarding everything else are finished, because that way all variables such as the amount of token after withdrawal are already available for use. We will look at collateralization checks in more details in later sections.

    \n

    Borrow

    \n

    At this point, we know how src/market/external.cairo and src/market/internal.cairo work, so we will go straight into internal::borrow:

    \n
    fn borrow(ref self: ContractState, token: ContractAddress, amount: felt252) {\n    let caller = get_caller_address();\n\n    let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n        ref self, token\n    );\n\n    assert_reserve_enabled(@self, token);\n\n    let scaled_down_amount = safe_decimal_math::div(amount, updated_debt_accumulator);\n    assert(scaled_down_amount.is_non_zero(), errors::INVALID_AMOUNT);\n\n    // Updates user debt data\n    let raw_user_debt_before = self.raw_user_debts.read((caller, token));\n    let raw_user_debt_after = safe_math::add(raw_user_debt_before, scaled_down_amount);\n    self.raw_user_debts.write((caller, token), raw_user_debt_after);\n\n    set_user_has_debt(ref self, caller, token, raw_user_debt_before, raw_user_debt_after);\n\n    // Updates interest rate\n    update_rates_and_raw_total_debt(\n        ref self,\n        token, // token\n        updated_debt_accumulator, // updated_debt_accumulator\n        true, // is_delta_reserve_balance_negative\n        amount, // abs_delta_reserve_balance\n        false, // is_delta_raw_total_debt_negative\n        scaled_down_amount // abs_delta_raw_total_debt\n    );\n\n    // Enforces token debt limit\n    assert_debt_limit_satisfied(@self, token);\n\n    self\n        .emit(\n            contract::Event::Borrowing(\n                contract::Borrowing {\n                    user: caller, token: token, raw_amount: scaled_down_amount, face_amount: amount\n                }\n            )\n        );\n\n    // It's easier to post-check collateralization factor\n    assert_not_undercollateralized(@self, caller, true);\n\n    let amount_u256: u256 = amount.into();\n    let transfer_success = IERC20Dispatcher {\n        contract_address: token\n    }.transfer(caller, amount_u256);\n    assert(transfer_success, errors::TRANSFER_FAILED);\n}
    \n

    First, we get the latest debt accumulator by running update_accumulators.

    \n

    Then again we need to ‘scale down’ the amount argument at let scaled_down_amount = safe_decimal_math::div(amount, updated_debt_accumulator), because the amount that the user is requesting already has the borrowing accumulator factored in.

    \n

    The scaled down amount can now be used to subtract from or add to the principal of the user’s debt:

    \n
    // Updates user debt data\nlet raw_user_debt_before = self.raw_user_debts.read((caller, token));\nlet raw_user_debt_after = safe_math::add(raw_user_debt_before, scaled_down_amount);\nself.raw_user_debts.write((caller, token), raw_user_debt_after);\n\nset_user_has_debt(ref self, caller, token, raw_user_debt_before, raw_user_debt_after);
    \n

    Now we update the interest rate again by calling update_rates_and_raw_total_debt:

    \n
    // Updates interest rate\nupdate_rates_and_raw_total_debt(\n    ref self,\n    token, // token\n    updated_debt_accumulator, // updated_debt_accumulator\n    true, // is_delta_reserve_balance_negative\n    amount, // abs_delta_reserve_balance\n    false, // is_delta_raw_total_debt_negative\n    scaled_down_amount // abs_delta_raw_total_debt\n);
    \n

    Notice the difference in the parameters passed in compared to deposit or withdraw:

    \n

    true, // is_delta_reserve_balance_negative because the contract is borrowing a portion of the reserve to the user;

    \n

    amount, // abs_delta_reserve_balance because amount is the actual amount that the user will receive as a result of borrowing;

    \n

    false, // is_delta_raw_total_debt_negative because the total debt increases as a result of borrowing;

    \n

    scaled_down_amount // abs_delta_raw_total_debt because the absolute amount of the difference in total debt that does not consider the borrowing interest index is scaled_down_amount.

    \n

    Then, the debt limit is checked:

    \n
    // Enforces token debt limit\nassert_debt_limit_satisfied(@self, token);
    \n

    On zklend documentation, this is described as a “borrow cap”. The debt limit is not per individual user; It is imposed at the individual liquidity pool level:

    \n

    \n \n \n \n \n

    \n

    When the amount of total borrowing per token reaches the limit specified by the protocol, users won’t be able to borrow anymore until someone repays his debt.

    \n

    The debt limit can be checked at https://starkscan.co/contract/0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05#read-write-contract. For example, for USDT of contract address 0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8:

    \n

    \n \n \n/// ASSUMPTION: raw_amount is non zero.\nfn repay_debt_internal(\n ref self: ContractState,\n repayer: ContractAddress,\n beneficiary: ContractAddress,\n token: ContractAddress,\n repay_amount: felt252,\n raw_amount: felt252\n) {\n let this_address = get_contract_address();\n\n let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n ref self, token\n );\n\n // No need to check if user is overpaying, as `safe_math::sub` below will fail anyways\n // No need to check collateral value. Always allow repaying even if it's undercollateralized\n\n // Updates user debt data\n let raw_user_debt_before = self.raw_user_debts.read((beneficiary, token));\n let raw_user_debt_after = safe_math::sub(raw_user_debt_before, raw_amount);\n self.raw_user_debts.write((beneficiary, token), raw_user_debt_after);\n\n set_user_has_debt(ref self, beneficiary, token, raw_user_debt_before, raw_user_debt_after);\n\n // Updates interest rate\n update_rates_and_raw_total_debt(\n ref self,\n token, // token\n updated_debt_accumulator, // updated_debt_accumulator\n false, // is_delta_reserve_balance_negative\n repay_amount, // abs_delta_reserve_balance\n true, // is_delta_raw_total_debt_negative\n raw_amount // abs_delta_raw_total_debt\n );\n\n // Takes token from user\n let repay_amount: u256 = repay_amount.into();\n let transfer_success = IERC20Dispatcher {\n contract_address: token\n }.transferFrom(repayer, this_address, repay_amount);\n assert(transfer_success, errors::TRANSFER_FAILED);\n}

    \n

    Liquidate

    \n

    internal::liquidate:

    \n
    fn liquidate(\n    ref self: ContractState,\n    user: ContractAddress,\n    debt_token: ContractAddress,\n    amount: felt252,\n    collateral_token: ContractAddress\n) {\n    let caller = get_caller_address();\n\n    // Validates input\n    assert(amount.is_non_zero(), errors::ZERO_AMOUNT);\n\n    assert_reserve_enabled(@self, debt_token);\n    assert_reserve_enabled(@self, collateral_token);\n    let debt_reserve_decimals = self.reserves.read_decimals(debt_token);\n    let collateral_reserve = self.reserves.read(collateral_token);\n\n    // Liquidator repays debt for user\n    let DebtRepaid{raw_amount, .. } = repay_debt_route_internal(\n        ref self, caller, user, debt_token, amount\n    );\n\n    // Can only take from assets being used as collateral\n    let is_collateral = is_used_as_collateral(@self, user, collateral_token);\n    assert(is_collateral, errors::NONCOLLATERAL_TOKEN);\n\n    // Liquidator withdraws collateral from user\n    let oracle_addr = self.oracle.read();\n    let debt_token_price = IPriceOracleDispatcher {\n        contract_address: oracle_addr\n    }.get_price(debt_token);\n    let collateral_token_price = IPriceOracleDispatcher {\n        contract_address: oracle_addr\n    }.get_price(collateral_token);\n    let debt_value_repaid = safe_decimal_math::mul_decimals(\n        debt_token_price, amount, debt_reserve_decimals\n    );\n    let equivalent_collateral_amount = safe_decimal_math::div_decimals(\n        debt_value_repaid, collateral_token_price, collateral_reserve.decimals\n    );\n    let one_plus_liquidation_bonus = safe_math::add(\n        safe_decimal_math::SCALE, collateral_reserve.liquidation_bonus\n    );\n    let collateral_amount_after_bonus = safe_decimal_math::mul(\n        equivalent_collateral_amount, one_plus_liquidation_bonus\n    );\n\n    IZTokenDispatcher {\n        contract_address: collateral_reserve.z_token_address\n    }.move(user, caller, collateral_amount_after_bonus);\n\n    // Checks user collateralization factor after liquidation\n    assert_not_overcollateralized(@self, user, false);\n\n    self\n        .emit(\n            contract::Event::Liquidation(\n                contract::Liquidation {\n                    liquidator: caller,\n                    user,\n                    debt_token,\n                    debt_raw_amount: raw_amount,\n                    debt_face_amount: amount,\n                    collateral_token,\n                    collateral_amount: collateral_amount_after_bonus,\n                }\n            )\n        );\n}
    \n

    This is a publicly visible function that can be called directly by any liquidators.

    \n

    The first step is the same as the repay function; The liquidator will repay for the undercollateralized asset for the debtor.

    \n

    The next step is to take the collateral away from the debtor as a liquidator.

    \n

    The amount that the liquidator is able to take away from the debtor is always Amount of the collateral in USD equivalent to the amount being repaid by the liquidator×(1+Liquidation bonus)\\text{Amount of the collateral in USD equivalent to the amount being repaid by the liquidator} \\times (1 + \\text{Liquidation bonus})Amount of the collateral in USD equivalent to the amount being repaid by the liquidator×(1+Liquidation bonus), where Liquidation bonus\\text{Liquidation bonus}Liquidation bonus varies from an asset to asset, and is decided by the protocol.

    \n

    Currently, zklend’s liquidation bonus ranges from 10% to 15%:

    \n

    \n \n \n \n \n

    \n

    After calculating the amount of collateral to be transferred to the liquidator, the function simply moves the zToken of the corresponding collateral from the debtor to the liquidator:

    \n
    IZTokenDispatcher {\n    contract_address: collateral_reserve.z_token_address\n}.move(user, caller, collateral_amount_after_bonus);
    \n

    Lastly, since zklend only allows the liquidator to recover the debtor’s position back to the health factor of 1 at maximum, it checks if the debtor is overcollateralized:

    \n
    assert_not_overcollateralized(@self, user, false);
    \n

    Here’s an example of liquidation that works, adopted from zklend’s test case.

    \n

    The setup is the same as the example used in the interest rate calculation:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    TokenOracle Price (USD)Collateral factorRslope1R_{\\text{slope1}}Rslope1Rslope2R_{\\text{slope2}}Rslope2R0R_0R0UoptimalU_{\\text{optimal}}UoptimalReserve factorLiquidation bonus
    $SIS5050%0.10.51%60%10%20%
    $BRO10075%0.20.35%80%20%10%
    \n

    Bob deposits 100001000010000 $BRO, Alice deposits 100100100 $SIS.

    \n

    Alice borrows

    \n

    22.5 $BRO =

    \n
    22.5×$100=$2250<100×50×0.5=$250022.5 \\times \\$100 = \\$2250 < 100 \\times 50 \\times 0.5 = \\$250022.5×$100=$2250<100×50×0.5=$2500
    \n

    So initially Alice is in a healthy position, because her health factor would be

    \n
    =25002250>1= \\frac{2500}{2250} > 1=22502500>1
    \n

    Now, let’s suppose the price of $SIS declines to $40. Then Alice’s health factor will be:

    \n
    100×40×0.52250=20002250=89<1\\frac{\n 100 \\times 40 \\times 0.5\n}{\n 2250\n} = \\frac{\n 2000\n}{\n 2250\n} = \\frac{\n 8\n}{9} < 12250100×40×0.5=22502000=98<1
    \n

    This means 19\\frac{1}{9}91 of Alice’s liabilities = $250 worth of $BRO is now undercollateralized and can be readily liquidated by anyone.

    \n

    While monitoring the market, Bob notices Alice is in an undercollateralized position, so he calls liquidate() function, repaying 6.25 $BRO for Alice instead.

    \n

    Bob expects to receive:

    \n
    (Amount repaidBRO×Price in USDBROPrice in USDSIS)×(1+Liquidation bonusSIS)=(6.25×10040)×(1+0.2)=18.75(\\frac{\n \\text{Amount repaid}_\\text{BRO} \\times \\text{Price in USD}_\\text{BRO}\n}{\n \\text{Price in USD}_\\text{SIS}\n}) \\times (1 + \\text{Liquidation bonus}_\\text{SIS}) = \\newline\n(\\frac{\n 6.25 \\times 100\n}{\n 40\n}) \\times (1 + 0.2) = \\newline\n18.75(Price in USDSISAmount repaidBRO×Price in USDBRO)×(1+Liquidation bonusSIS)=(406.25×100)×(1+0.2)=18.75
    \n

    … 18.75 $SIS, which is worth 18.75×40=75018.75 \\times 40 = 75018.75×40=750 dollars. He spent 6.25×100=6256.25 \\times 100 = 6256.25×100=625 dollars to repay, so as long as the gas fee is low enough, he’s made a profit of 750625gas750 - 625 - \\text{gas}750625gas dollars.

    \n

    After liquidation, Alice’s health factor is:

    \n
    81.25×40×0.5(22.56.25)×100=16251625=1\\frac{\n 81.25 \\times 40 \\times 0.5\n}\n{\n (22.5 - 6.25) \\times 100\n} = \\newline \n\\frac{\n 1625\n}\n{\n 1625\n} = 1(22.56.25)×10081.25×40×0.5=16251625=1
    \n

    which means Alice is not overcollateralized, which if it was the case make the transaction rejected.

    \n

    Liquidation should only work for undercollateralized positions. Liquidators cannot liquidate healthy positions.

    \n

    Storage

    \n

    WIP

    \n

    Precision

    \n

    WIP

    \n","frontmatter":{"title":"Technical intro to DeFi lending protocols with zkLend codebase as an example","date":"October 26, 2023","keywords":["zklend","cairo","defi","lending","code"]},"fields":{"readingTime":{"text":"36 min read"}}}},"pageContext":{"slug":"/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/","previous":{"fields":{"slug":"/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/","readingTime":{"text":"18 min read"}},"frontmatter":{"title":"Full summary of (14 / 100)","tab":"post","tags":["books"],"keywords":["life 3.0","artificial intelligence","book"]}},"next":{"fields":{"slug":"/2023-12-02-한국이-망할-수밖에-없는-이유-(1)-사라진-능력주의/","readingTime":{"text":"25 min read"}},"frontmatter":{"title":"한국이 망할 수밖에 없는 이유 (1): 사라진 능력주의","tab":"post","tags":["한국","South Korea"],"keywords":null}}}},"staticQueryHashes":["3128451518","426816048"]} \ No newline at end of file +{"componentChunkName":"component---src-templates-blog-post-js","path":"/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/","result":{"data":{"site":{"siteMetadata":{"title":"Joel's dev blog","author":"Joel Mun","siteUrl":"https://9oelm.github.io"}},"markdownRemark":{"id":"948de1a1-c622-51ea-b545-248979eb45a7","excerpt":"How a lending protocol works Functionalities Deposit Borrow Leverage Other usages Liquidate Repay Over-collateralization Utilization rate Interest rate Borrow…","html":"\n

    Here’s an extensive intro to DeFi lending protocols, especially for a dummy like me. We will look at fundamental concepts and theory, primarily complemented by zklend smart contract codebase at the commit hash of 10dfb3d1f01b1177744b038f8417a5a9c3e94185.

    \n

    How a lending protocol works

    \n

    Functionalities

    \n

    There are 4 major functionalities that a lending protocol provides to the users:

    \n

    Deposit

    \n

    Users can deposit tokens into the smart contract as much as they want. For example, I can deposit 1000 USDC into the smart contract.

    \n

    This will accrue interest over time. The interest accrual can only happen when there is at least one borrower, because the borrower will need to pay for the depositor’s interests. Otherwise, the interest rate stays at 0%. Deposit is the beginning of every other functionalities.

    \n

    Borrow

    \n

    Users are able to borrow certain amount of token, but only based on the amount of collaterals by depositing. For example, you may supply 1.1 ETH to borrow 500 USDC, only if 1.1 ETH is sufficiently more than the value of 500 USDC in USD.

    \n

    For example, 1.1 ETH is 1,813.79 USD today, and 500 USDC is approximately 500 USD. The collateralization factor, which denotes the value of borrowed amount that can be covered with an amount of collateralized token, is 80%. This means 1813.79×0.8=1451.0321813.79 \\times 0.8 = 1451.0321813.79×0.8=1451.032 USD worth of other assets can be borrowed from the protocol. This way, all of the borrowings in the protocol are backed by overly sufficient amount of tokens, which is called overcollateralization.

    \n

    But why borrow in the first place, if you already have as much as (or even more than) the amount of token that you want to borrow?

    \n

    Below are the possible reasons:

    \n

    Leverage

    \n

    Here’s a table describing the variables for this example:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    namevalue
    Price of 1 ETH in USD1200
    Price of 1 USDC in USD1
    Bob’s initial ETH balance2
    Collateral factor of ETH80%
    Gas fees, interests0
    \n

    Bob thinks the value of ETH will dramatically increase over time, so just a simple x1 long position is not enough for him. He is so sure that he want to get more than x1 profit when the price of ETH increases. This is how he would position himself at a leveraged long position:

    \n
      \n
    1. Bob deposits 2 ETH into the protocol
    2. \n
    3. Bob borrows 1200×2×0.5=1200<1200×2×0.8=19201200 \\times 2 \\times 0.5 = 1200 < 1200 \\times 2 \\times 0.8 = 19201200×2×0.5=1200<1200×2×0.8=1920 worth of USDC from the protocol. Now he can spend 1200 USDC for whatever he wants.
    4. \n
    5. Bob goes to Uniswap, and swaps 1200 USDC for 1 ETH.
    6. \n
    7. Now Bob owns 3 ETH, although he has a debt of 1200 USDC.
    8. \n
    9. A few days later, the price of 1 ETH in USD increases to 1600 USD. Now his total profit off of this increase is 1600×31200×3=12001600 \\times 3 - 1200 \\times 3 = 12001600×31200×3=1200:\n
        \n
      1. Bob now swaps his 1 ETH with 1600 USDC. Then he repays for his 1200 USDC debt. He still has 400 USDC left in his wallet.
      2. \n
      3. Then Bob withdraws 2 ETH from the protocol, and swaps that for 1600×2=32001600 \\times 2 = 32001600×2=3200 USDC at Uniswap.
      4. \n
      5. 3200+400=36003200 + 400 = 36003200+400=3600 USDC is the total he now has.
      6. \n
      7. Bob started with 2 ETH that was equivalent to 2400 USD, but after the price increase, he made additional 400 USD in addition to 800 USD that is the increase in the USD value of 2 ETH.
      8. \n
      \n
    10. \n
    11. Imagine if Bob held 3 ETH from the beginning. Then this would be valued at 4800 USD after the price increase, leaving Bob with 1200 USD of profit as well. Without the protocol, he wouldn’t have been able to achieve the same profit.
    12. \n
    \n

    Other usages

    \n

    Other use cases include shorting, liquidity management, arbitraging, etc. The list is not exhausitive and readily available on the Internet.

    \n

    Liquidate

    \n

    The value of tokens supplied as collaterals fluctuates over time. For example, if you have deposited 1.1 ETH as a collateral, it might be 1790 USD today, but it could have been 1809.47 USD yesterday. In such a case, the total value of collaterals might not be able to cover the total amount of tokens borrowed by a user.

    \n

    Then other users get a chance to liquidate that user’s position. In liquidation, a substantial amount of the user’s debt can be repaid by the liquidator, and the borrower will take an additional percentage of the user’s collateral as a liquidation bonus (or a penalty, from POV of the borrower). More on this later too.

    \n

    Repay

    \n

    Repay for the amount of token you borrowed, plus the accrued interest.

    \n

    Over-collateralization

    \n

    Any lending protocol would want to assume over-colleteralization for maximum measure of security. Typically, the more volatile/unstable the price of an asset is, the lower collateral factor it would count for.

    \n

    Usually, lending protocols would restrict which assets can be used as a collateral. However, it’s also worth noting that there are some protocols that allow users to create their own pairs of asset to be borrowed and collateralized.

    \n

    An example of overcollateralization can be seen in any lending protocols; For example, $LINK has a 79% of collateral factor on Compound today. This means if you deposit $100 worth of $LINK, you will be able to borrow $79 worth of any assets without being liquidated.

    \n

    Utilization rate

    \n

    We will need to define a few key terms to further understand other concepts.

    \n

    Utilization rate denotes how much of a single asset deposited in the pool is being borrowed at a time.

    \n
    \n

    The utilisation rate of each pool is a function of the current outstanding loan amount over the total capital supply in the specific liquidity pool (from zklend whitepaper)

    \n
    \n

    Here’s an example:

    \n
      \n
    • Bob deposits 100 USDT (worth 100 USD) in USDT pool
    • \n
    • Alice deposits 100 DAI (worth 100 USD) in DAI pool
    • \n
    • The collateral factor of DAI is 80%, so Alice borrows 80 USDT, which is the maximum of what she can safely borrow without getting liquidated
    • \n
    • Now, the utilization rate of USDT is 80100=80%\\frac{80}{100} = 80\\%10080=80% and that of DAI is 0100=0%\\frac{0}{100} = 0\\%1000=0%
    • \n
    \n

    Utilization rate of an asset will change whenever there is a new borrowing or supply.

    \n

    Here’s calculate_utilization_rate from zklend contract:

    \n
    fn calculate_utilization_rate(reserve_balance: felt252, total_debt: felt252) -> felt252 {\n    if total_debt == 0 {\n        0\n    } else {\n        let total_liquidity = safe_math::add(reserve_balance, total_debt);\n        let utilization_rate = safe_decimal_math::div(total_debt, total_liquidity);\n        utilization_rate\n    }\n}
    \n

    The function’s calculation is as follows: total debtreserve+total debt\\frac{\\text{total debt}}{reserve + \\text{total debt}}reserve+total debttotal debt. Here, reservereservereserve denotes the cash available in the liquidity pool that is not being utilized.

    \n

    Note that when in used in practice, total_debt will have to reflect not only the principal that was borrowed by the user, but also the interests accrued. Otherwise the calculation will result in a wrong value that does not take any interests accrued into account.

    \n

    Interest rate

    \n

    For the interest rate, we need to talk about the interest rate model first. An interest rate model is meant to encourage borrowing when there is enough capital, and discourage borrowing (encourage repayment) and additional supply when there is not enough capital. Logically, we arrive at the conclusion that the interest rate must be high when there is high utilization rate, and low when there is low utilization rate.

    \n

    We would also want to set an optimal utilization rate, because we generally would not want the utilization rate to be too low (no one’s using it) or too high (no one can borrow anymore).

    \n

    It would be worth noting that the interest rates will change when every single transaction that affects the balance of supply or debt happens. The scope of the interest rates to be discussed in this post, therefore, are only variable interest rates.

    \n

    Borrow interest rate

    \n

    With all of these in mind, we can introduce this interest rate model for borrowing:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    VariableMeaningDomain (or Range)
    UUUUtilization rateU[0,1]U \\in [0, 1]U[0,1]
    UoptimalU_{\\text{optimal}}UoptimalOptimal utilization rateUoptimal[0,1]U_{\\text{optimal}} \\in [0, 1]Uoptimal[0,1]
    RRRInterest rate at utiliation rate UUUR[0,]R \\in [0, \\infty]R[0,]
    Rslope1R_{\\text{slope1}}Rslope1Variable Rate Slope 1 (the slope that is going to be applied on the left side of the graph)Rslope1[0,]R_{\\text{slope1}} \\in [0, \\infty]Rslope1[0,]
    Rslope2R_{\\text{slope2}}Rslope2Variable Rate Slope 2 (the slope that is going to be applied on the right side of the graph)Rslope2[0,]R_{\\text{slope2}} \\in [0, \\infty]Rslope2[0,]
    R0R_0R0Base interest rate. This adjusts the y-intercept of the interest rate curve.R0[0,1]R_0 \\in [0, 1]R0[0,1]
    \n
    U<=UoptimalR=R0+UUoptimal(Rslope1)U <= U_{optimal} \\rArr R = R_0 + \\frac{U}{U_{\\text{optimal}}}(R_{\\text{slope1}})U<=UoptimalR=R0+UoptimalU(Rslope1)
    \n
    U>UoptimalR=R0+Rslope1+Rslope2UUoptimal1UoptimalU > U_{optimal} \\rArr R = R_0 + R_{\\text{slope1}} + R_{\\text{slope2}}\\frac{U - U_{\\text{optimal}}}{1 - U_{\\text{optimal}}}U>UoptimalR=R0+Rslope1+Rslope21UoptimalUUoptimal
    \n

    It may just be easy to use a graph to find out what it means rather than looking at the equations.

    \n

    You can interact with the graphs I created here.

    \n

    Below graph is the interest rate model for ‘Rate Strategy Stable One’ on Aave. This refers to “Low liquidity stablecoins have lower Optimal Utilisation Ratio than those with higher liquidity”. A rate strategy is just a list of variables used for the interest rate model. You would want to use different strategies for different crypto assets, owing to their stability/volatility. Every lending protocol has their own interest rate strategy for different assets; Zklend has also has one.

    \n

    \n \n \n \n \n \n\n \n \n \n \n

    \n

    But how is it done under the hood? Let’s read code on Zklend.

    \n

    Below are functions to calculate the balance of a ZToken.

    \n
    fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 {\n    felt_balance_of(self, account).into()\n}\n\nfn felt_balance_of(self: @ContractState, account: ContractAddress) -> felt252 {\n    let accumulator = internal::get_accumulator(self);\n\n    let balance = self.raw_balances.read(account);\n    let scaled_up_balance = safe_decimal_math::mul(balance, accumulator);\n\n    scaled_up_balance\n}
    \n

    felt_balance_of reads from ContractState the raw balance of the token, but it times that with accumulator. So that’s the secret. If the value of accumulator is somehow dynamic, every time your Metamask wallet calls balanceOf from the blockchain, the token balance should also be dynamic.

    \n

    But what does accumulator do? First of all, get_accumulator will get the corresponding accumulator that is in the Market contract by calling get_lending_accumulator.

    \n
    fn get_accumulator(self: @ContractState) -> felt252 {\n    let market_addr = self.market.read();\n    let underlying_addr = self.underlying.read();\n    IMarketDispatcher { contract_address: market_addr }.get_lending_accumulator(underlying_addr)\n}
    \n

    This is get_lending_accumulator, where the compound interest is calculated:

    \n
    fn get_lending_accumulator(self: @ContractState, token: ContractAddress) -> felt252 {\n    internal::assert_reserve_enabled(self, token);\n    let reserve = self.reserves.read_for_get_lending_accumulator(token);\n\n    let block_timestamp: felt252 = get_block_timestamp().into();\n    if reserve.last_update_timestamp == block_timestamp {\n        // Accumulator already updated on the same block\n        reserve.lending_accumulator\n    } else {\n        // Apply simple interest\n        let time_diff = safe_math::sub(block_timestamp, reserve.last_update_timestamp);\n\n        // Treats reserve factor as zero if treasury address is not set\n        let treasury_addr = self.treasury.read();\n        let effective_reserve_factor = if treasury_addr.is_zero() {\n            0\n        } else {\n            reserve.reserve_factor\n        };\n\n        let one_minus_reserve_factor = safe_math::sub(\n            safe_decimal_math::SCALE, effective_reserve_factor\n        );\n\n        // New accumulator\n        // (current_lending_rate * (1 - reserve_factor) * time_diff / SECONDS_PER_YEAR + 1) * accumulator\n        let temp_1 = safe_math::mul(reserve.current_lending_rate, time_diff);\n        let temp_2 = safe_math::mul(temp_1, one_minus_reserve_factor);\n        let temp_3 = safe_math::div(temp_2, SECONDS_PER_YEAR);\n        let temp_4 = safe_math::div(temp_3, safe_decimal_math::SCALE);\n        let temp_5 = safe_math::add(temp_4, safe_decimal_math::SCALE);\n        let latest_accumulator = safe_decimal_math::mul(temp_5, reserve.lending_accumulator);\n\n        latest_accumulator\n    }\n}
    \n

    Below is the description of the variables being used from self.reserves:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    namedescription
    last_update_timestampthe timestamp at which lending_accumulator was updated for the last time, in seconds since the epoch
    lending_accumulatorcumulated liquidity index (or simply interest index). Tracks the interest cumulated by the reserve during the time interval, updated whenever a borrow, deposit, repay, redeem, swap, liquidation event occurs.
    current_lending_ratethe current lending rate that was calculated by get_interest_rates of DefaultInterestRateModel
    \n

    The equation for calculating a cumulated liquidity/borrow index is as follows:

    \n
    Indexn=Indexn1×(1+r×t)Index_n = Index_{n-1} \\times (1 + r \\times t)Indexn=Indexn1×(1+r×t)
    \n

    where

    \n
    t=Time Interval (length of time since the last index calculation)t = \\text{Time Interval} \\text{ (length of time since the last index calculation)}t=Time Interval (length of time since the last index calculation)
    \n
    r=Interest Rater = \\text{Interest Rate}r=Interest Rate
    \n
    n=nth index calculatedn = n^{th}\\text{ index calculated}n=nth index calculated
    \n
    Index0=1Index_0 = 1Index0=1
    \n

    we can see that rrr is represented by current_lending_rate * (1 - reserve_factor), and ttt by time_diff / SECONDS_PER_YEAR (look at the comment in the code), and Indexn1Index_{n-1}Indexn1 by reserve.lending_accumulator.

    \n

    This works exactly the same way for get_debt_accumulator:

    \n
    fn get_debt_accumulator(self: @ContractState, token: ContractAddress) -> felt252 {\n    internal::assert_reserve_enabled(self, token);\n    let reserve = self.reserves.read_for_get_debt_accumulator(token);\n\n    let block_timestamp: felt252 = get_block_timestamp().into();\n    if (reserve.last_update_timestamp == block_timestamp) {\n        // Accumulator already updated on the same block\n        reserve.debt_accumulator\n    } else {\n        // Apply simple interest\n        let time_diff = safe_math::sub(block_timestamp, reserve.last_update_timestamp);\n\n        // (current_borrowing_rate * time_diff / SECONDS_PER_YEAR + 1) * accumulator\n        let temp_1 = safe_math::mul(reserve.current_borrowing_rate, time_diff);\n        let temp_2 = safe_math::div(temp_1, SECONDS_PER_YEAR);\n        let temp_3 = safe_math::add(temp_2, safe_decimal_math::SCALE);\n        let latest_accumulator = safe_decimal_math::mul(temp_3, reserve.debt_accumulator);\n\n        latest_accumulator\n    }\n}
    \n

    It is the same logic as get_lending_accumulator, except that the reserve factor is not a part of the equation because we are taking profit from the debtors, not for lending. That means we want to take the full borrow index as it is from the debt, and instead lower the liquidity index for lending so that the protocol can take certain % as a profit.

    \n

    Example: interest rate calculation

    \n

    To find the final amount of borrowing or deposit with the accrued interests considered, all you need to do is to multiply the raw principal value with the cumulated liquidity/borrow index. But calculating the index requires calculating interest rates. So let’s dive into one example. This example is based on zklend’s smart contract tests.

    \n

    Here’s an example for calculating accrued interests for borrowing and deposit.

    \n

    Let’s say we got $SIS and $BRO tokens:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    TokenOracle Price (USD)Collateral factorRslope1R_{\\text{slope1}}Rslope1Rslope2R_{\\text{slope2}}Rslope2R0R_0R0UoptimalU_{\\text{optimal}}UoptimalReserve factor
    $SIS5050%0.10.51%60%10%
    $BRO10075%0.20.35%80%20%
    \n

    Bob deposits 100001000010000 $BRO, Alice deposits 100100100 $SIS.

    \n

    Alice borrows

    \n

    22.5 $BRO =

    \n
    22.5×$100=$2250<100×50×0.5=$250022.5 \\times \\$100 = \\$2250 < 100 \\times 50 \\times 0.5 = \\$250022.5×$100=$2250<100×50×0.5=$2500
    \n

    which is well within the value of collateral supplied.

    \n

    Now,

    \n
    UBRO=22.510,000=0.00225=0.225%<UBROOptimal=80%U_{BRO} = \\frac{22.5}{10,000} = 0.00225 \\newline = \n0.225\\% < U_{{BRO}{_{Optimal}}} = 80\\% \\newlineUBRO=10,00022.5=0.00225=0.225%<UBROOptimal=80%
    \n
    UBRO<=UBROOptimalRBROBorrow=RBRO0+UBROUBROOptimal(UBROslope1)=0.05+0.002250.8×0.2=0.0505625U_{BRO} <= U_{{BRO}{_{Optimal}}} \\rArr \\newline R_{{\\text{BRO}}_\\text{Borrow}} = R_{{\\text{BRO}}_\\text{0}} + \\frac{U_{BRO}}{U_{{BRO}{_{Optimal}}}}(U_{{BRO}{_{slope1}}}) \\newline = 0.05 + \\frac{0.00225}{0.8} \\times 0.2 = 0.0505625UBRO<=UBROOptimalRBROBorrow=RBRO0+UBROOptimalUBRO(UBROslope1)=0.05+0.80.00225×0.2=0.0505625
    \n

    Now we calculate the supply interest rate, but without considering the reserve factor for now.

    \n
    RBROSupply (no reserve)=RBROBorrow×UBRO=0.0505625×0.00225=0.000113765625R_{{BRO}{_{\\text{Supply (no reserve)}}}} = R_{{\\text{BRO}}_\\text{Borrow}} \\times U_{BRO} = 0.0505625 \\times 0.00225 = 0.000113765625RBROSupply (no reserve)=RBROBorrow×UBRO=0.0505625×0.00225=0.000113765625
    \n

    So there we have it:

    \n
    RBROBorrow=0.0505625RBROSupply (no reserve)=0.000113765625R_{{\\text{BRO}}_\\text{Borrow}} = 0.0505625 \\newline\nR_{{BRO}{_{\\text{Supply (no reserve)}}}} = 0.000113765625RBROBorrow=0.0505625RBROSupply (no reserve)=0.000113765625
    \n

    Example (continued): cumulated liquidity index & cumulated borrow index calculation

    \n

    And let’s say:

    \n
      \n
    • 100 seconds have elapsed, starting from timestamp 0
    • \n
    • no cumulated liquidity index and cumulated borrow index were calculated before, making them 1 respectively by default
    • \n
    \n

    Then you can calculate them as follows:

    \n
    Cumulated Liquidity Indexn=Cumulated Liquidity Indexn1×(1+r×t)=1×(1+(0.000113765625×(10.2))×100365×86400)=1.000000000288598744292237442\\text{Cumulated Liquidity Index}_n = \\newline \n\\text{Cumulated Liquidity Index}_{n-1} \\times (1 + r \\times t) = \\newline\n1 \\times (1 + (0.000113765625 \\times (1 - 0.2)) \\times \\frac{100}{365 \\times 86400}) = 1.000000000288598744292237442\n\\newlineCumulated Liquidity Indexn=Cumulated Liquidity Indexn1×(1+r×t)=1×(1+(0.000113765625×(10.2))×365×86400100)=1.000000000288598744292237442
    \n
    Cumulated Borrow Indexn=Cumulated Borrow Indexn1×(1+r×t)=1×(1+0.0505625×100365×86400)=1.000000160332635717909690512\\text{Cumulated Borrow Index}_n = \\newline\n\\text{Cumulated Borrow Index}_{n-1} \\times (1 + r \\times t) = \\newline\n1 \\times (1 + 0.0505625 \\times \\frac{100}{365 \\times 86400}) = 1.000000160332635717909690512Cumulated Borrow Indexn=Cumulated Borrow Indexn1×(1+r×t)=1×(1+0.0505625×365×86400100)=1.000000160332635717909690512
    \n

    Instead of factoring the reserve factor into the lending rate, we instead do it when we calculate it in the cumulated liquidity index.

    \n

    ttt is calculated as 100 seconds divided by the number of seconds per year (without caring about leap years).

    \n

    Other than this, the calculation above should be straightforward.

    \n

    Update frequency of cumulated liquidity index and interest rates

    \n

    The cumulated liquidity index is updated once per block only. You can go back and check this line:

    \n
    if (reserve.last_update_timestamp == block_timestamp) {\n    // Accumulator already updated on the same block\n    reserve.debt_accumulator\n}
    \n

    So even if further transactions take place in the same block, the accumulator will change only once for the first transaction in the block and that will be it.

    \n

    However, interest rates will update every transaction that relates to the change in the amount of value borrowed/lent. This means that the accumulator will take the last available interest rate into consideration, which should be from more than or equal to one previous block from the current block.

    \n

    This makes sense because accumulators use the time difference between the last update time and now. So it’s not possible to update accumulators more than once in the same block.

    \n

    But the liquidity in the same block can change multiple times, which means there can be varying interest rates per block. The last interest rate of that block will be used for the next accumulator calculation in the later block.

    \n

    Accumulator example:

    \n\n

    Two different AccumulatorsSync events for the same token 0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7, but the value of accumulators stays the same.

    \n

    Interest rates example:

    \n\n

    Two different InterestRatesSync events, in the same block 10000, for the same asset 0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 but different lending_rate and borrowing_rate.

    \n

    Approximation of compound interest over a time period

    \n

    It is impossible to predictably calculate the compound interest accrued/earned from the protocol because the interest rate is variable every block, and the movement of interest rates cannot be predicted without users’ actions.

    \n

    There is a way to run an approximate calculation: continuous compounding interest formula.

    \n
    P(t)=P0ertP(t) = P_0e^{rt}P(t)=P0ert
    \n

    where

    \n

    P(t)=value at time tP(t) = \\text{value at time }tP(t)=value at time t

    \n

    P0=original principalP_0 = \\text{original principal}P0=original principal

    \n

    e=Euler’s numbere = \\text{Euler's number}e=Euler’s number

    \n

    r=annual interest rater = \\text{annual interest rate}r=annual interest rate

    \n

    t=length of time the interest rate is applied per yeart = \\text{length of time the interest rate is applied per year}t=length of time the interest rate is applied per year

    \n

    To elaborate, let’s take an example: you deposit 25 USDC and wait for a month. The projected average APY over the month is 5%, so we just take a stable 5% APY for example.

    \n

    Then,

    \n
    P(t)=P0×er×t=25e0.05×112=25.1043839823P(t) = P_0 × e^{r \\times t} = 25 * e^{0.05 \\times \\frac{1}{12}} = 25.1043839823P(t)=P0×er×t=25e0.05×121=25.1043839823
    \n

    This means the compound interest for an average of 5% APY over a month approx 0.10 zUSDC.

    \n

    The reason we use continuous compounding is that the frequency of compounding is simply impossible to be predicted in the case of lending protocols on blockchain and the time difference between each compounding is always different. The frequency of compounding under a given time period is the number of blocks that contain at least one transaction regarding a particular token on the protocol\\text{the number of blocks that contain at least one transaction regarding a particular token on the protocol}the number of blocks that contain at least one transaction regarding a particular token on the protocol.

    \n

    Let’s give an example of AccumulatorsSync event for token 0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8 from block 61993 to 61998:

    \n
      \n
    • at block 61993
    • \n
    • at block 61997
    • \n
    • at block 61998
    • \n
    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    blockeventtimestamptime diff (secs) from previous compounding
    61993link1684912486-
    61997link1684912643157
    61998link168491267835
    \n

    Given that there were already existing accumulators before block 61993, the lending and borrowing accumulators compounded 3 times within the specified block range.

    \n

    And the time difference between each compounding event is different, making us unable to use the traditional compound interest formula (A=P(1+rn)ntA = P(1 + \\frac{r}{n})^{nt}A=P(1+nr)nt), where you need to specify n = number of compounding periods per unit of time\\text{n = number of compounding periods per unit of time}n = number of compounding periods per unit of time, which is always variable as explained in above example. Also, each compounding period isn’t constant; so this equation can’t really be used, because it assumes that each compounding period is the same. Therefore, a fair approximation can be better deduced by using continuous compounding interest formula.

    \n

    Liquidation

    \n

    A few more terms need to be defined to be able to understand liquidation.

    \n

    Health factor. Health factor=Collaterali×USD value of Collaterali×Collateral factoriLiabilityi×USD value of Liabilityi\\text{Health factor} = \\frac{\\sum{Collateral_i \\times \\text{USD value of Collateral}_i \\times \\text{Collateral factor}_i}}{\\sum{Liability_i \\times \\text{USD value of Liability}_i}}Health factor=Liabilityi×USD value of LiabilityiCollaterali×USD value of Collaterali×Collateral factori. It denotes the status of user’s position. If the health factor is lower than 1, the position may be liquidated, because that would mean the value of collaterals is not enough to back the value of borrowings.

    \n

    Liquidation bonus (or penalty, for debtors). Additional % applied on the collateral deposited by the debtor, which in turn is received by the liquidator after the repayment. This is to incentivize liquidators to liquidate.

    \n

    Potential liquidators will be monitoring the market closely and frequently, and try to call liquidate() earlier than their competitors with suitable amount of gas fee that might get their transaction get through earlier than others. Once the transaction gets through, the debtor’s position will be liquidated and the corresponding amount of collateral in USD plus the liquidation bonus will be paid back to the liquidator. Liquidators are only allowed to repay no more than the amount that will bring the borrower’s Health Factor back to 1.

    \n

    Liquidation is essential to the healthy operation of a lending protocol; it removes unhealthy, undercollateralized positions to keep it healthy.

    \n

    Technical review

    \n

    Deposit

    \n

    All typical user interactions will be done at market.cairo file.

    \n

    The deposit function in market.cairo will call deposit function from market/external.cairo:

    \n
    // deposit in market.cairo\n\nfn deposit(ref self: ContractState, token: ContractAddress, amount: felt252) {\n   external::deposit(ref self, token, amount)\n}
    \n

    Again, that will call internal::deposit from market/internal.cairo.

    \n
    // deposit in market/external.cairo\nfn deposit(ref self: ContractState, token: ContractAddress, amount: felt252) {\n    reentrancy_guard::start(ref self);\n    internal::deposit(ref self, token, amount);\n    reentrancy_guard::end(ref self);\n}
    \n
    // deposit in market/internal.cairo\nfn deposit(ref self: ContractState, token: ContractAddress, amount: felt252) {\n    assert(amount.is_non_zero(), errors::ZERO_AMOUNT);\n\n    let caller = get_caller_address();\n    let this_address = get_contract_address();\n\n    let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n        ref self, token\n    );\n\n    assert_reserve_enabled(@self, token);\n    let z_token_address = self.reserves.read_z_token_address(token);\n\n    // Updates interest rate\n    update_rates_and_raw_total_debt(\n        ref self,\n        token, // token\n        updated_debt_accumulator, // updated_debt_accumulator\n        false, // is_delta_reserve_balance_negative\n        amount, // abs_delta_reserve_balance\n        false, // is_delta_raw_total_debt_negative\n        0 // abs_delta_raw_total_debt\n    );\n\n    self\n        .emit(\n            contract::Event::Deposit(\n                contract::Deposit { user: caller, token: token, face_amount: amount }\n            )\n        );\n\n    // Takes token from user\n\n    let amount_u256: u256 = amount.into();\n    let transfer_success = IERC20Dispatcher {\n        contract_address: token, \n    }.transferFrom(caller, this_address, amount_u256);\n    assert(transfer_success, errors::TRANSFERFROM_FAILED);\n\n    // Mints ZToken to user\n    IZTokenDispatcher { contract_address: z_token_address }.mint(caller, amount);\n}
    \n

    The first thing that this deposit function does is to call update_accumulators:

    \n
    fn update_accumulators(ref self: ContractState, token: ContractAddress) -> UpdatedAccumulators {\n    let block_timestamp: felt252 = get_block_timestamp().into();\n\n    let updated_lending_accumulator = view::get_lending_accumulator(@self, token);\n    let updated_debt_accumulator = view::get_debt_accumulator(@self, token);\n\n    self\n        .emit(\n            contract::Event::AccumulatorsSync(\n                contract::AccumulatorsSync {\n                    token,\n                    lending_accumulator: updated_lending_accumulator,\n                    debt_accumulator: updated_debt_accumulator\n                }\n            )\n        );\n\n    // It's okay to call this function here as the updated accumulators haven't been written into\n    // storage yet\n    let amount_to_treasury = view::get_pending_treasury_amount(@self, token);\n\n    // No need to check reserve existence since it's done in `get_lending_accumulator` and\n    // `get_debt_accumulator`\n    let z_token_address = self.reserves.read_z_token_address(token);\n\n    self\n        .reserves\n        .write_accumulators(\n            token, block_timestamp, updated_lending_accumulator, updated_debt_accumulator\n        );\n\n    // No need to check whether treasury address is zero as amount would be zero anyways\n    if amount_to_treasury.is_non_zero() {\n        let treasury_addr = self.treasury.read();\n        IZTokenDispatcher {\n            contract_address: z_token_address\n        }.mint(treasury_addr, amount_to_treasury);\n    }\n\n    UpdatedAccumulators {\n        lending_accumulator: updated_lending_accumulator, debt_accumulator: updated_debt_accumulator\n    }\n}
    \n

    And this respectively calls accumulators for debt and lending:

    \n
    fn get_lending_accumulator(self: @ContractState, token: ContractAddress) -> felt252 {\n    internal::assert_reserve_enabled(self, token);\n    let reserve = self.reserves.read_for_get_lending_accumulator(token);\n\n    let block_timestamp: felt252 = get_block_timestamp().into();\n    if reserve.last_update_timestamp == block_timestamp {\n        // Accumulator already updated on the same block\n        reserve.lending_accumulator\n    } else {\n        // Apply simple interest\n        let time_diff = safe_math::sub(block_timestamp, reserve.last_update_timestamp);\n\n        // Treats reserve factor as zero if treasury address is not set\n        let treasury_addr = self.treasury.read();\n        let effective_reserve_factor = if treasury_addr.is_zero() {\n            0\n        } else {\n            reserve.reserve_factor\n        };\n\n        let one_minus_reserve_factor = safe_math::sub(\n            safe_decimal_math::SCALE, effective_reserve_factor\n        );\n\n        // New accumulator\n        // (current_lending_rate * (1 - reserve_factor) * time_diff / SECONDS_PER_YEAR + 1) * accumulator\n        let temp_1 = safe_math::mul(reserve.current_lending_rate, time_diff);\n        let temp_2 = safe_math::mul(temp_1, one_minus_reserve_factor);\n        let temp_3 = safe_math::div(temp_2, SECONDS_PER_YEAR);\n        let temp_4 = safe_math::div(temp_3, safe_decimal_math::SCALE);\n        let temp_5 = safe_math::add(temp_4, safe_decimal_math::SCALE);\n        let latest_accumulator = safe_decimal_math::mul(temp_5, reserve.lending_accumulator);\n\n        latest_accumulator\n    }\n}\n\nfn get_debt_accumulator(self: @ContractState, token: ContractAddress) -> felt252 {\n    internal::assert_reserve_enabled(self, token);\n    let reserve = self.reserves.read_for_get_debt_accumulator(token);\n\n    let block_timestamp: felt252 = get_block_timestamp().into();\n    if (reserve.last_update_timestamp == block_timestamp) {\n        // Accumulator already updated on the same block\n        reserve.debt_accumulator\n    } else {\n        // Apply simple interest\n        let time_diff = safe_math::sub(block_timestamp, reserve.last_update_timestamp);\n\n        // (current_borrowing_rate * time_diff / SECONDS_PER_YEAR + 1) * accumulator\n        let temp_1 = safe_math::mul(reserve.current_borrowing_rate, time_diff);\n        let temp_2 = safe_math::div(temp_1, SECONDS_PER_YEAR);\n        let temp_3 = safe_math::add(temp_2, safe_decimal_math::SCALE);\n        let latest_accumulator = safe_decimal_math::mul(temp_3, reserve.debt_accumulator);\n\n        latest_accumulator\n    }\n}
    \n

    We already discussed in depth what an accumulator is and why it needs to be used. They need to be updated every single time the corresponding interest rate needs to change.

    \n

    After updating the accumulators, amount_to_treasury is calculated and that amount is sent to the treasury, in the form of zToken. Remember that zToken is redeemable at 1:1 rate with the underlying asset, so it’s the same as sending real token to the reasury.

    \n

    Next in internal::deposit, update_rates_and_raw_total_debt is called. This function does two things as it name suggests; but it will only update the rate for deposit operation, as you can tell from the argument of 0 for abs_delta_raw_total_debt.

    \n

    However if reserve.last_update_timestamp is not the current block timestamp, the debt accumulator would still get updated and will affect the interest rate calculated inside update_rates_and_raw_total_debt, because the ‘scaled’ debt (that is, principal value of debt multiplied by the latest debt accumulator) would be slightly larger than the one calculated at the previous block timestamp.

    \n

    Here is update_rates_and_raw_total_debt:

    \n
    fn update_rates_and_raw_total_debt(\n    ref self: ContractState,\n    token: ContractAddress,\n    updated_debt_accumulator: felt252,\n    is_delta_reserve_balance_negative: bool,\n    abs_delta_reserve_balance: felt252,\n    is_delta_raw_total_debt_negative: bool,\n    abs_delta_raw_total_debt: felt252,\n) {\n    let this_address = get_contract_address();\n\n    let StorageBatch1{\n        interest_rate_model, raw_total_debt: raw_total_debt_before } = self\n        .reserves\n        .read_interest_rate_model_and_raw_total_debt(token);\n\n    // Makes sure reserve exists\n    // (the caller must check it's enabled if needed since it's not validated here)\n    assert(interest_rate_model.is_non_zero(), errors::RESERVE_NOT_FOUND);\n\n    let reserve_balance_before: felt252 = IERC20Dispatcher {\n        contract_address: token\n    }.balanceOf(this_address).try_into().expect(errors::BALANCE_OVERFLOW);\n\n    let reserve_balance_after = if is_delta_reserve_balance_negative {\n        safe_math::sub(reserve_balance_before, abs_delta_reserve_balance)\n    } else {\n        safe_math::add(reserve_balance_before, abs_delta_reserve_balance)\n    };\n\n    let raw_total_debt_after = if is_delta_raw_total_debt_negative {\n        safe_math::sub(raw_total_debt_before, abs_delta_raw_total_debt)\n    } else {\n        safe_math::add(raw_total_debt_before, abs_delta_raw_total_debt)\n    };\n\n    let scaled_up_total_debt_after = safe_decimal_math::mul(\n        raw_total_debt_after, updated_debt_accumulator\n    );\n    let ModelRates{lending_rate: new_lending_rate, borrowing_rate: new_borrowing_rate } =\n        IInterestRateModelDispatcher {\n        contract_address: interest_rate_model\n    }.get_interest_rates(reserve_balance_after, scaled_up_total_debt_after);\n\n    // Writes to storage\n    self.reserves.write_rates(token, new_lending_rate, new_borrowing_rate);\n    if raw_total_debt_before != raw_total_debt_after {\n        self.reserves.write_raw_total_debt(token, raw_total_debt_after);\n    }\n\n    self\n        .emit(\n            contract::Event::InterestRatesSync(\n                contract::InterestRatesSync {\n                    token, lending_rate: new_lending_rate, borrowing_rate: new_borrowing_rate\n                }\n            )\n        );\n}
    \n

    It reads existing interest_rate_model, raw_total_debt: raw_total_debt_before from the storage. And then runs calculations to get parameters for IInterestRateModelDispatcher.get_interest_rates.

    \n

    After that, the newly calculated interest rates and debt are updated.

    \n

    Finally, transferFrom of the underlying ERC20 token is called in internal::deposit function. This is where the actual transfer happens.

    \n

    Lastly, the interest bearing zToken of the exact same deposit amount for that specific ERC20 token is minted back to the user, so they can track the sum of their principal and interest at any time.

    \n

    Withdraw

    \n

    Everything that is meant to be accessed on zklend frontend is defined in src/market/external.cairo, just like deposit. So is withdraw, and it again calls internal::withdraw.

    \n

    withdraw in src/market/internal.cairo:

    \n
    fn withdraw(ref self: ContractState, token: ContractAddress, amount: felt252) {\n    assert(amount.is_non_zero(), errors::ZERO_AMOUNT);\n\n    let caller = get_caller_address();\n    withdraw_internal(ref self, caller, token, amount);\n}
    \n

    So the real meat must be in withdraw_internal.

    \n

    withdraw_internal in src/market/internal.cairo:

    \n
    fn withdraw_internal(\n    ref self: ContractState, user: ContractAddress, token: ContractAddress, amount: felt252\n) {\n    let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n        ref self, token\n    );\n\n    assert_reserve_enabled(@self, token);\n    let z_token_address = self.reserves.read_z_token_address(token);\n\n    // NOTE: it's fine to call out to external contract here before state update since it's trusted\n    let amount_burnt = burn_z_token_internal(ref self, z_token_address, user, amount);\n\n    // Updates interest rate\n    update_rates_and_raw_total_debt(\n        ref self,\n        token, // token\n        updated_debt_accumulator, // updated_debt_accumulator\n        true, // is_delta_reserve_balance_negative\n        amount_burnt, // abs_delta_reserve_balance\n        false, // is_delta_raw_total_debt_negative\n        0, // abs_delta_raw_total_debt\n    );\n\n    self\n        .emit(\n            contract::Event::Withdrawal(\n                contract::Withdrawal { user, token, face_amount: amount_burnt }\n            )\n        );\n\n    // Gives underlying tokens to user\n    let amount_burnt: u256 = amount_burnt.into();\n    let transfer_success = IERC20Dispatcher {\n        contract_address: token\n    }.transfer(user, amount_burnt);\n    assert(transfer_success, errors::TRANSFER_FAILED);\n\n    // It's easier to post-check collateralization factor, at the cost of making failed\n    // transactions more expensive.\n    let is_asset_used_as_collateral = is_used_as_collateral(@self, user, token);\n\n    // No need to check if the asset is not used as collateral at all\n    if is_asset_used_as_collateral {\n        assert_not_undercollateralized(@self, user, true);\n    }\n}
    \n

    Basically, the exact opposite of internal::deposit.

    \n

    The first bit is the same; It starts off by calling update_accumulators to get updated_debt_accumulator which will be used as an argument to update_rates_and_raw_total_debt:

    \n
    let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n        ref self, token\n    );
    \n

    Then, the corresponding amount of zToken is burnt:

    \n
    let z_token_address = self.reserves.read_z_token_address(token);\n\n// NOTE: it's fine to call out to external contract here before state update since it's trusted\nlet amount_burnt = burn_z_token_internal(ref self, z_token_address, user, amount);
    \n

    If amount is not zero, zToken’s external::burn will be called:

    \n
    fn burn(ref self: ContractState, user: ContractAddress, amount: felt252) {\n    internal::only_market(@self);\n\n    let accumulator = internal::get_accumulator(@self);\n\n    let scaled_down_amount = safe_decimal_math::div(amount, accumulator);\n    assert(scaled_down_amount.is_non_zero(), errors::INVALID_BURN_AMOUNT);\n\n    let raw_balance_before = self.raw_balances.read(user);\n    let raw_balance_after = safe_math::sub(raw_balance_before, scaled_down_amount);\n    self.raw_balances.write(user, raw_balance_after);\n\n    let raw_supply_before = self.raw_total_supply.read();\n    let raw_supply_after = safe_math::sub(raw_supply_before, scaled_down_amount);\n    self.raw_total_supply.write(raw_supply_after);\n\n    let amount: u256 = amount.into();\n    self\n        .emit(\n            contract::Event::Transfer(\n                contract::Transfer { from: user, to: contract_address_const::<0>(), value: amount }\n            )\n        );\n}
    \n

    let accumulator = internal::get_accumulator(@self); gets the current lending accumulator so it can be used for further calculations. Remember, zToken has a dynamic balance due to the accumulator.

    \n

    So why let scaled_down_amount = safe_decimal_math::div(amount, accumulator);? The reason is that the amount that the user requests to withdraw, passed down as amount: felt252 argument, already assumes that the amount has the interest index (the accumulator) factored in.

    \n

    Let’s go back to the example presented before.

    \n

    \n \n \n \n \n

    \n

    If you see aDAI (just imagine it’s a zToken, it’s the same thing essentially anyways) balance of 1011.4130020694421009831011.4130020694421009831011.413002069442100983 and let’s say you want to withdraw everything that you have.

    \n

    Then you know that the amount that you are looking at on MetaMask is the principal amount multiplied by the lending accumulator.

    \n

    However, in fn burn, we want to subtract the amount from the principal (denoted as raw_* in the code) which does not have the accumulator factored in; it is literally the exact amount that the user had deposited before. To be able to do that, we need to convert amount into the same scale. This is because we only store the principal value on the blockchain instead of the principal multiplied by the accumulator.

    \n

    Now we understand why it has to be:

    \n
    let raw_balance_after = safe_math::sub(raw_balance_before, scaled_down_amount);
    \n

    and

    \n
    let raw_supply_after = safe_math::sub(raw_supply_before, scaled_down_amount);
    \n

    Lastly, Transfer event where the token is sent to the null address is emitted, and burn function exits.

    \n

    Next up is update_rates_and_raw_total_debt:

    \n
    update_rates_and_raw_total_debt(\n    ref self,\n    token, // token\n    updated_debt_accumulator, // updated_debt_accumulator\n    true, // is_delta_reserve_balance_negative\n    amount_burnt, // abs_delta_reserve_balance\n    false, // is_delta_raw_total_debt_negative\n    0, // abs_delta_raw_total_debt\n);
    \n

    Similar to deposit function, we are running this because we know that the supply of a token has changed, and this must affect the position on the interest rate curve as per the earlier discussion. Notice true, // is_delta_reserve_balance_negative because we know that the reserve balance (the supply of the token being withdrawn) has decreased.

    \n

    Calling update_rates_and_raw_total_debt will update borrowing and lending interest rates.

    \n

    Then, the corresponding amount of ERC20 token is transferred to the user. Remember amount_burnt of zToken must be sent because a pair of zToken and ERC20 should be 1:1 in terms of their amounts.

    \n
    // Gives underlying tokens to user\nlet amount_burnt: u256 = amount_burnt.into();\nlet transfer_success = IERC20Dispatcher {\n    contract_address: token\n}.transfer(user, amount_burnt);
    \n

    After that, collateralization checks are done. If the user withdraws too much that his borrowing is undercollateralized, the transaction would fail.

    \n
    // It's easier to post-check collateralization factor, at the cost of making failed\n// transactions more expensive.\nlet is_asset_used_as_collateral = is_used_as_collateral(@self, user, token);\n\n// No need to check if the asset is not used as collateral at all\nif is_asset_used_as_collateral {\n    assert_not_undercollateralized(@self, user, true);\n}
    \n

    It’s easier to check collateralization after all calculations regarding everything else are finished, because that way all variables such as the amount of token after withdrawal are already available for use. We will look at collateralization checks in more details in later sections.

    \n

    Borrow

    \n

    At this point, we know how src/market/external.cairo and src/market/internal.cairo work, so we will go straight into internal::borrow:

    \n
    fn borrow(ref self: ContractState, token: ContractAddress, amount: felt252) {\n    let caller = get_caller_address();\n\n    let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n        ref self, token\n    );\n\n    assert_reserve_enabled(@self, token);\n\n    let scaled_down_amount = safe_decimal_math::div(amount, updated_debt_accumulator);\n    assert(scaled_down_amount.is_non_zero(), errors::INVALID_AMOUNT);\n\n    // Updates user debt data\n    let raw_user_debt_before = self.raw_user_debts.read((caller, token));\n    let raw_user_debt_after = safe_math::add(raw_user_debt_before, scaled_down_amount);\n    self.raw_user_debts.write((caller, token), raw_user_debt_after);\n\n    set_user_has_debt(ref self, caller, token, raw_user_debt_before, raw_user_debt_after);\n\n    // Updates interest rate\n    update_rates_and_raw_total_debt(\n        ref self,\n        token, // token\n        updated_debt_accumulator, // updated_debt_accumulator\n        true, // is_delta_reserve_balance_negative\n        amount, // abs_delta_reserve_balance\n        false, // is_delta_raw_total_debt_negative\n        scaled_down_amount // abs_delta_raw_total_debt\n    );\n\n    // Enforces token debt limit\n    assert_debt_limit_satisfied(@self, token);\n\n    self\n        .emit(\n            contract::Event::Borrowing(\n                contract::Borrowing {\n                    user: caller, token: token, raw_amount: scaled_down_amount, face_amount: amount\n                }\n            )\n        );\n\n    // It's easier to post-check collateralization factor\n    assert_not_undercollateralized(@self, caller, true);\n\n    let amount_u256: u256 = amount.into();\n    let transfer_success = IERC20Dispatcher {\n        contract_address: token\n    }.transfer(caller, amount_u256);\n    assert(transfer_success, errors::TRANSFER_FAILED);\n}
    \n

    First, we get the latest debt accumulator by running update_accumulators.

    \n

    Then again we need to ‘scale down’ the amount argument at let scaled_down_amount = safe_decimal_math::div(amount, updated_debt_accumulator), because the amount that the user is requesting already has the borrowing accumulator factored in.

    \n

    The scaled down amount can now be used to subtract from or add to the principal of the user’s debt:

    \n
    // Updates user debt data\nlet raw_user_debt_before = self.raw_user_debts.read((caller, token));\nlet raw_user_debt_after = safe_math::add(raw_user_debt_before, scaled_down_amount);\nself.raw_user_debts.write((caller, token), raw_user_debt_after);\n\nset_user_has_debt(ref self, caller, token, raw_user_debt_before, raw_user_debt_after);
    \n

    Now we update the interest rate again by calling update_rates_and_raw_total_debt:

    \n
    // Updates interest rate\nupdate_rates_and_raw_total_debt(\n    ref self,\n    token, // token\n    updated_debt_accumulator, // updated_debt_accumulator\n    true, // is_delta_reserve_balance_negative\n    amount, // abs_delta_reserve_balance\n    false, // is_delta_raw_total_debt_negative\n    scaled_down_amount // abs_delta_raw_total_debt\n);
    \n

    Notice the difference in the parameters passed in compared to deposit or withdraw:

    \n

    true, // is_delta_reserve_balance_negative because the contract is borrowing a portion of the reserve to the user;

    \n

    amount, // abs_delta_reserve_balance because amount is the actual amount that the user will receive as a result of borrowing;

    \n

    false, // is_delta_raw_total_debt_negative because the total debt increases as a result of borrowing;

    \n

    scaled_down_amount // abs_delta_raw_total_debt because the absolute amount of the difference in total debt that does not consider the borrowing interest index is scaled_down_amount.

    \n

    Then, the debt limit is checked:

    \n
    // Enforces token debt limit\nassert_debt_limit_satisfied(@self, token);
    \n

    On zklend documentation, this is described as a “borrow cap”. The debt limit is not per individual user; It is imposed at the individual liquidity pool level:

    \n

    \n \n \n \n \n

    \n

    When the amount of total borrowing per token reaches the limit specified by the protocol, users won’t be able to borrow anymore until someone repays his debt.

    \n

    The debt limit can be checked at https://starkscan.co/contract/0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05#read-write-contract. For example, for USDT of contract address 0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8:

    \n

    \n \n \n/// ASSUMPTION: raw_amount is non zero.\nfn repay_debt_internal(\n ref self: ContractState,\n repayer: ContractAddress,\n beneficiary: ContractAddress,\n token: ContractAddress,\n repay_amount: felt252,\n raw_amount: felt252\n) {\n let this_address = get_contract_address();\n\n let UpdatedAccumulators{debt_accumulator: updated_debt_accumulator, .. } = update_accumulators(\n ref self, token\n );\n\n // No need to check if user is overpaying, as `safe_math::sub` below will fail anyways\n // No need to check collateral value. Always allow repaying even if it's undercollateralized\n\n // Updates user debt data\n let raw_user_debt_before = self.raw_user_debts.read((beneficiary, token));\n let raw_user_debt_after = safe_math::sub(raw_user_debt_before, raw_amount);\n self.raw_user_debts.write((beneficiary, token), raw_user_debt_after);\n\n set_user_has_debt(ref self, beneficiary, token, raw_user_debt_before, raw_user_debt_after);\n\n // Updates interest rate\n update_rates_and_raw_total_debt(\n ref self,\n token, // token\n updated_debt_accumulator, // updated_debt_accumulator\n false, // is_delta_reserve_balance_negative\n repay_amount, // abs_delta_reserve_balance\n true, // is_delta_raw_total_debt_negative\n raw_amount // abs_delta_raw_total_debt\n );\n\n // Takes token from user\n let repay_amount: u256 = repay_amount.into();\n let transfer_success = IERC20Dispatcher {\n contract_address: token\n }.transferFrom(repayer, this_address, repay_amount);\n assert(transfer_success, errors::TRANSFER_FAILED);\n}

    \n

    Liquidate

    \n

    internal::liquidate:

    \n
    fn liquidate(\n    ref self: ContractState,\n    user: ContractAddress,\n    debt_token: ContractAddress,\n    amount: felt252,\n    collateral_token: ContractAddress\n) {\n    let caller = get_caller_address();\n\n    // Validates input\n    assert(amount.is_non_zero(), errors::ZERO_AMOUNT);\n\n    assert_reserve_enabled(@self, debt_token);\n    assert_reserve_enabled(@self, collateral_token);\n    let debt_reserve_decimals = self.reserves.read_decimals(debt_token);\n    let collateral_reserve = self.reserves.read(collateral_token);\n\n    // Liquidator repays debt for user\n    let DebtRepaid{raw_amount, .. } = repay_debt_route_internal(\n        ref self, caller, user, debt_token, amount\n    );\n\n    // Can only take from assets being used as collateral\n    let is_collateral = is_used_as_collateral(@self, user, collateral_token);\n    assert(is_collateral, errors::NONCOLLATERAL_TOKEN);\n\n    // Liquidator withdraws collateral from user\n    let oracle_addr = self.oracle.read();\n    let debt_token_price = IPriceOracleDispatcher {\n        contract_address: oracle_addr\n    }.get_price(debt_token);\n    let collateral_token_price = IPriceOracleDispatcher {\n        contract_address: oracle_addr\n    }.get_price(collateral_token);\n    let debt_value_repaid = safe_decimal_math::mul_decimals(\n        debt_token_price, amount, debt_reserve_decimals\n    );\n    let equivalent_collateral_amount = safe_decimal_math::div_decimals(\n        debt_value_repaid, collateral_token_price, collateral_reserve.decimals\n    );\n    let one_plus_liquidation_bonus = safe_math::add(\n        safe_decimal_math::SCALE, collateral_reserve.liquidation_bonus\n    );\n    let collateral_amount_after_bonus = safe_decimal_math::mul(\n        equivalent_collateral_amount, one_plus_liquidation_bonus\n    );\n\n    IZTokenDispatcher {\n        contract_address: collateral_reserve.z_token_address\n    }.move(user, caller, collateral_amount_after_bonus);\n\n    // Checks user collateralization factor after liquidation\n    assert_not_overcollateralized(@self, user, false);\n\n    self\n        .emit(\n            contract::Event::Liquidation(\n                contract::Liquidation {\n                    liquidator: caller,\n                    user,\n                    debt_token,\n                    debt_raw_amount: raw_amount,\n                    debt_face_amount: amount,\n                    collateral_token,\n                    collateral_amount: collateral_amount_after_bonus,\n                }\n            )\n        );\n}
    \n

    This is a publicly visible function that can be called directly by any liquidators.

    \n

    The first step is the same as the repay function; The liquidator will repay for the undercollateralized asset for the debtor.

    \n

    The next step is to take the collateral away from the debtor as a liquidator.

    \n

    The amount that the liquidator is able to take away from the debtor is always Amount of the collateral in USD equivalent to the amount being repaid by the liquidator×(1+Liquidation bonus)\\text{Amount of the collateral in USD equivalent to the amount being repaid by the liquidator} \\times (1 + \\text{Liquidation bonus})Amount of the collateral in USD equivalent to the amount being repaid by the liquidator×(1+Liquidation bonus), where Liquidation bonus\\text{Liquidation bonus}Liquidation bonus varies from an asset to asset, and is decided by the protocol.

    \n

    Currently, zklend’s liquidation bonus ranges from 10% to 15%:

    \n

    \n \n \n \n \n

    \n

    After calculating the amount of collateral to be transferred to the liquidator, the function simply moves the zToken of the corresponding collateral from the debtor to the liquidator:

    \n
    IZTokenDispatcher {\n    contract_address: collateral_reserve.z_token_address\n}.move(user, caller, collateral_amount_after_bonus);
    \n

    Lastly, since zklend only allows the liquidator to recover the debtor’s position back to the health factor of 1 at maximum, it checks if the debtor is overcollateralized:

    \n
    assert_not_overcollateralized(@self, user, false);
    \n

    Here’s an example of liquidation that works, adopted from zklend’s test case.

    \n

    The setup is the same as the example used in the interest rate calculation:

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    TokenOracle Price (USD)Collateral factorRslope1R_{\\text{slope1}}Rslope1Rslope2R_{\\text{slope2}}Rslope2R0R_0R0UoptimalU_{\\text{optimal}}UoptimalReserve factorLiquidation bonus
    $SIS5050%0.10.51%60%10%20%
    $BRO10075%0.20.35%80%20%10%
    \n

    Bob deposits 100001000010000 $BRO, Alice deposits 100100100 $SIS.

    \n

    Alice borrows

    \n

    22.5 $BRO =

    \n
    22.5×$100=$2250<100×50×0.5=$250022.5 \\times \\$100 = \\$2250 < 100 \\times 50 \\times 0.5 = \\$250022.5×$100=$2250<100×50×0.5=$2500
    \n

    So initially Alice is in a healthy position, because her health factor would be

    \n
    =25002250>1= \\frac{2500}{2250} > 1=22502500>1
    \n

    Now, let’s suppose the price of $SIS declines to $40. Then Alice’s health factor will be:

    \n
    100×40×0.52250=20002250=89<1\\frac{\n 100 \\times 40 \\times 0.5\n}{\n 2250\n} = \\frac{\n 2000\n}{\n 2250\n} = \\frac{\n 8\n}{9} < 12250100×40×0.5=22502000=98<1
    \n

    This means 19\\frac{1}{9}91 of Alice’s liabilities = $250 worth of $BRO is now undercollateralized and can be readily liquidated by anyone.

    \n

    While monitoring the market, Bob notices Alice is in an undercollateralized position, so he calls liquidate() function, repaying 6.25 $BRO for Alice instead.

    \n

    Bob expects to receive:

    \n
    (Amount repaidBRO×Price in USDBROPrice in USDSIS)×(1+Liquidation bonusSIS)=(6.25×10040)×(1+0.2)=18.75(\\frac{\n \\text{Amount repaid}_\\text{BRO} \\times \\text{Price in USD}_\\text{BRO}\n}{\n \\text{Price in USD}_\\text{SIS}\n}) \\times (1 + \\text{Liquidation bonus}_\\text{SIS}) = \\newline\n(\\frac{\n 6.25 \\times 100\n}{\n 40\n}) \\times (1 + 0.2) = \\newline\n18.75(Price in USDSISAmount repaidBRO×Price in USDBRO)×(1+Liquidation bonusSIS)=(406.25×100)×(1+0.2)=18.75
    \n

    … 18.75 $SIS, which is worth 18.75×40=75018.75 \\times 40 = 75018.75×40=750 dollars. He spent 6.25×100=6256.25 \\times 100 = 6256.25×100=625 dollars to repay, so as long as the gas fee is low enough, he’s made a profit of 750625gas750 - 625 - \\text{gas}750625gas dollars.

    \n

    After liquidation, Alice’s health factor is:

    \n
    81.25×40×0.5(22.56.25)×100=16251625=1\\frac{\n 81.25 \\times 40 \\times 0.5\n}\n{\n (22.5 - 6.25) \\times 100\n} = \\newline \n\\frac{\n 1625\n}\n{\n 1625\n} = 1(22.56.25)×10081.25×40×0.5=16251625=1
    \n

    which means Alice is not overcollateralized, which if it was the case make the transaction rejected.

    \n

    Liquidation should only work for undercollateralized positions. Liquidators cannot liquidate healthy positions.

    \n

    Storage

    \n

    WIP

    \n

    Precision

    \n

    WIP

    \n","frontmatter":{"title":"Technical intro to DeFi lending protocols with zkLend codebase as an example","date":"October 26, 2023","keywords":["zklend","cairo","defi","lending","code"]},"fields":{"readingTime":{"text":"40 min read"}}}},"pageContext":{"slug":"/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/","previous":{"fields":{"slug":"/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/","readingTime":{"text":"18 min read"}},"frontmatter":{"title":"Full summary of (14 / 100)","tab":"post","tags":["books"],"keywords":["life 3.0","artificial intelligence","book"]}},"next":{"fields":{"slug":"/2023-12-02-한국이-망할-수밖에-없는-이유-(1)-사라진-능력주의/","readingTime":{"text":"25 min read"}},"frontmatter":{"title":"한국이 망할 수밖에 없는 이유 (1): 사라진 능력주의","tab":"post","tags":["한국","South Korea"],"keywords":null}}}},"staticQueryHashes":["3128451518","426816048"]} \ No newline at end of file diff --git "a/page-data/2023-12-02-\355\225\234\352\265\255\354\235\264-\353\247\235\355\225\240-\354\210\230\353\260\226\354\227\220-\354\227\206\353\212\224-\354\235\264\354\234\240-(1)-\354\202\254\353\235\274\354\247\204-\353\212\245\353\240\245\354\243\274\354\235\230/page-data.json" "b/page-data/2023-12-02-\355\225\234\352\265\255\354\235\264-\353\247\235\355\225\240-\354\210\230\353\260\226\354\227\220-\354\227\206\353\212\224-\354\235\264\354\234\240-(1)-\354\202\254\353\235\274\354\247\204-\353\212\245\353\240\245\354\243\274\354\235\230/page-data.json" index 48c5b142..2357c92e 100644 --- "a/page-data/2023-12-02-\355\225\234\352\265\255\354\235\264-\353\247\235\355\225\240-\354\210\230\353\260\226\354\227\220-\354\227\206\353\212\224-\354\235\264\354\234\240-(1)-\354\202\254\353\235\274\354\247\204-\353\212\245\353\240\245\354\243\274\354\235\230/page-data.json" +++ "b/page-data/2023-12-02-\355\225\234\352\265\255\354\235\264-\353\247\235\355\225\240-\354\210\230\353\260\226\354\227\220-\354\227\206\353\212\224-\354\235\264\354\234\240-(1)-\354\202\254\353\235\274\354\247\204-\353\212\245\353\240\245\354\243\274\354\235\230/page-data.json" @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-blog-post-js","path":"/2023-12-02-한국이-망할-수밖에-없는-이유-(1)-사라진-능력주의/","result":{"data":{"site":{"siteMetadata":{"title":"Joel's dev blog","author":"Joel Mun","siteUrl":"https://9oelm.github.io"}},"markdownRemark":{"id":"15e17f1f-4136-5313-855a-29e1eb0a1fe7","excerpt":"…","html":"

    몇년동안 적어야지 적어야지 말만 해대다가 드디어 이 주제에 대해서 글을 적어보도록 하겠다. 바라건대 시리즈로 풀어나갈 이 주제는 비단 한국이 왜 망할 수밖에 없는지에 대한 내용은 아니다. 이것은 내가 왜 한국을 싫어하는지에 대한 이유다. 이것은 또한 그 많고 많은 재외한국인이 죽어도 한국에서 일을 하기 싫어하는 이유다. 이것은 내가 한국에서 새로이 자라나는 어린 친구들이 불쌍하다고 생각하는 이유다. 이 글을 읽는 당신도 당신만의 어떤 이유를 찾을 수 있기를 바란다.

    \n

    시간이 되면 영어로 다시 써야겠다고 생각하고 있다. 이유는, 내가 함께 대화해본 그 어떤 외국인 친구도 한국의 이런 문제점뿐만 아니라 한국이 망하는 건 시간문제라는 걸 전혀 모른다. 진짜 하나도 모른다. 이게 정말 놀라웠다. 심지어 한국 여행을 갔다온 친구들도 잘 알지 못했다.

    \n

    원래는 책을 쓰거나 유튜브 동영상을 업로드 해야겠다고 생각했으나 그게 오히려 시간이 너무 오래 걸릴 것 같아서 그냥 일단 블로그에 글을 쓰는 걸로 시작부터 하려고 한다. 생각만 하다가 결국엔 미루고 미루고 지금까지 시작하지 못했다. 하여튼. 고사하고. 시작하자.

    \n

    하고 싶은 얘기가 너무 많다. 하나씩 살펴볼텐데… 오늘은 사라진 능력주의를 살펴보자.

    \n

    능력주의

    \n

    누구나 알다시피 아담 스미스는 보이지 않는 손이라는 개념을 제시했다. 개념은 간단하다. 사람들을 내버려 둔다면 개인의 이기적인 행동들이 서로를 위한 이익의 추구로 연결된다는 것이다. 배고픈 사람들은 빵을 사먹기 위해 회사에 가서 일을 할 것이며, 월세를 지불하고 싶은 사람은 빵을 만들어 팔 것이다. 세입자가 주는 월세로 돈을 벌고 싶은 사람은 집 한 채를 구매하기 위해 큰 사업을 벌릴 것이다.

    \n

    능력주의는 이와 밀접한 연관이 있다. 능력이 좋다면 주어진 시간 안에 돈을 더 많이 벌을 수 있어야 한다. 경쟁하고 있는 빵집보다 비교적 싸고 맛있는 빵을 만들 수 있는 능력만 된다면 빵집 주인은 비교적 더 많은 수익을 얻을 수 있을 것이다. 당연한 결말이다. 고객이 더 싸고 맛있는 빵을 원하기 때문이다. 이것은 빵집 주인이 더 열심히 노동하도록 만들어 주는 긍정적 피드백으로 작용한다. 반면, 비교적 비싸고 맛 없는 빵을 만드는 빵집 주인은 고객을 빼앗긴 나머지 빵집을 유지하지 못할 수준의 수입을 올릴 것이며, 결국엔 빵집이 아닌 수익을 얻을 다른 방법을 찾아야 할 것이다. 결국 돈이 사람을 움직이는 원동력인 것이다.

    \n

    일터에서도 능력주의는 예외가 아니다. 당신이 일을 열심히 하는 이유는 무엇인가? 아니, 당신이 일을 열심히 하게 만드는 이유는 무엇인가? 바로 돈이다.

    \n

    \n

    혹자는 질문할 수도 있다. 그게 돈이라고 어떻게 확신하지? 단순히 열정이나 꿈일 수 있지 않을까? 랜덤한 블로그에서 가져온 평균 소득이 가장 낮은 직업 50개를 예를 들어 설명을 해 보자. “자연 및 문화해설사”가 연봉 783만원으로 가장 최저 평균 소득을 얻는다. 그런데 만약 ‘나는 문화를 다른 이들에게 알려주는 일이 가치있고 보람차다고 생각해 이것에 열정을 쏟고 싶다’라고 말하는 사람이 있다면 나의 주장이 잘못될 수도 있지 않을까?

    \n

    글쎄다. 일단 현실적으로 평균적인 사회 생활을 하고 평균적인 여가와 오락을 즐기며 평균적인 식성 등을 가진 다 큰 독립한 성인이 783만원으로 일 년 생활이 가능할지 의문이다. 78312=65.25\\frac{783}{12}=65.2512783=65.25만원인데, 65.25만원으로, 미래를 위해 저축하고 사회생활 (결혼식 축의금 등)도 잘 하고, 외식도 가끔 하고, 여행도 다니면서 살 수 있을까? 거의 불가능이다. 당신이 783만원 혹은 이하를 한국에서 벌면서 과연 행복할 수 있을까? 나는 아니라고 본다.

    \n

    \n \n \n \n \n

    \n

    \n \n \n \n \n

    \n

    내가 말하고 싶은 건, 돈에 관심이 없다는 사실 자체는 전혀 문제가 되지 않지만, 그것에 상응하는 결과가 따르기 마련이라는 것이다. 결국 평균 혹은 그 이상의 행복을 원한다면 평균 혹은 그 이상의 돈을 벌고 그것에 관심을 가져야 한다는 말에는 흠이 없을 것이다.

    \n

    돈은 열정과 다르다. 돈은 꿈과 다르다. 돈은 무슨 뜬구름 잡는 소리가 아니다. 전 세계 어느 나라에 가도 돈은 사용된다. 모두가 이해하고 모두가 사용하고 모두가 가치를 인정해준다. 돈의 보편성은 누구라도 알아준다. 당신이 게임에 대한 열정이 있는데 그것으로 소고기를 먹고 싶다면 열정으로 소고기를 사는 대신 그 열정으로 프로게이머가 돼서 돈을 벌고 그 돈으로 소고기를 살 것이다. 당신이 시간이 있는데 그 시간으로 소고기를 사고 싶다면 카페에서 알바를 해서 돈을 받고 그 돈으로 소고기를 살 것이다. 이런 의미에서 돈은 보편적이고 유용한 사회적 합의이다.

    \n

    노동의 자유시장경제

    \n

    그렇기 때문에 당신이 회사에서 일을 하면 시간으로 월급을 받지 않는다. 소고기로 월급을 받지 않는다. 귀여운 토끼 인형으로 월급을 받지 않는다. 왜? 돈이라는 사회적 합의로 모든 것을 사고 팔기로 동의했기 때문이다.

    \n

    그렇다면 여기까지 왜 노동을 해서 돈을 벌을 수밖에 없는지에 대해 설명해봤다. 그런데 문제가 생긴다.

    \n

    빵집에서 일을 하고 돈을 받으려고 하는데, 동일하게 주어진 시간 내에 나보다 빵을 더 맛있고 완성도 있게 만드는 사람이 천 명이 더 있다. 내가 만든 빵을 먹으면 바로 토만 나오는데 그들이 만드는 빵은 입에서 살살 녹아 잘 팔리기까지 한다.

    \n

    당신이 빵집 사장이라면 나를 고용하겠는가, 그 천 명중 한 명을 고용하겠는가? 당연히 나를 버려야 맞다. 나는 안중에도 없어야 한다.

    \n

    이게 바로 고등/대학교에서 배웠을 기본 중에 기본인 자유시장경제의 원리다.

    \n

    \n \n \n \n \n

    \n

    좀 더 쉽게 설명해보자. 공급 먼저. P1=5000P1 = 5000P1=5000원의 시급을 준다고 했을 때 100명이 제빵사를 하고 싶은 마음이 있다고 가정하자. 그게 Q1Q1Q1 정도의 위치가 될 것이다. 그런데 만약 빵을 굽는 일이 모종의 이유로 매우 중요해져서 P2=100,000P2 = 100,000P2=100,000원의 시급을 준다고 해 보자. 그러면 제빵사 하기 싫은 사람도 하고 싶은 마음이 샘솟을 것이다. 그래서 하고 싶은 사람이 10만명으로 늘어난다. 그게 Q2Q2Q2의 위치라고 가정하자.

    \n

    이것도 복잡하다면 뉴스로 쉽게 이해하자. ‘평균 연봉 1억’ 기아 킹산직 300명 뽑는다…10만명 지원 대란? 그냥 돈 많이 주면 그 일을 하고 싶은 사람이 많이 생기기 마련이다. 평균 연봉이 2천이었으면 10만명이나 지원하고 싶겠는가.

    \n

    다음, 수요. 시급이 5000원이라면 싸다고 생각해서 더 많은 제빵사를 채용해서 (Q2Q2Q2) 사업을 확장하고 잘 굴러가게 하고 싶을 것이다. 그런데 시급이 만약에 10만원이라면 아마 한 명 (Q1Q1Q1)만 채용하고 말 것이다. 기아차도 마찬가지. 평균 연봉이 1억이니까 300명만 뽑았지, 5만명을 뽑겠는가.

    \n

    그래서 왜 자유시장경제냐고? 아무도 방해하거나 참견하지 않으니까 자유하다. 시급이 너무 비싸서 한 명만 채용하는 사장님에게 너무 적게 채용하니까 더 채용해달라는 사회의 압박감이나 정부의 요청은 터무니없다. 이미 채용된 제빵사가 일은 못하지만 근속을 오래했다는 이유만으로 본래 시장에서 받아야 할 시급의 두 배를 받는 것은 (예를 들자면 P1P1P1을 받아야 하는 사람이 P2P2P2를 받고 있다면) 시스템의 특정 부분이 잘못되어 자유시장이 제 역할을 하지 않는다는 증거다.

    \n

    앞서 말했듯이 모든 제빵사의 실력이 동일할리가 없다. 제빵사 노동시장이 상식적이고 자유롭다면 제빵사의 실력은 Y축인 시급과 비례해야만 한다. 시급이 P2P2P2만큼 늘어난다면 물론 Q2Q2Q2만큼의 제빵사가 되고 싶어하는 사람이 있겠지만, Q2Q2Q2중 제빵 초보도 있을 것이고 고수도 있을 것이고 유명 베이커리에서 이미 20년동안 일한 장인이 있을 수도 있을 것이다. 그러면 아마 그 중 잘 하는 사람이 P2P2P2를 차지하고, 못 하는 사람은 P2P2P2보다 적은 시급을 주는 포지션으로 밀려나게 될 것이다. 공정하고 행복하다. 못 하면 덜 벌고, 잘 하면 더 번다.

    \n

    파괴된 능력주의

    \n
    \n

    못 하면 덜 벌고, 잘 하면 더 번다.

    \n
    \n

    아쉽게도 이 말, 즉 노동의 자유시장경제는 한국에서 대부분의 경우 사실이었던 적이 없다.

    \n

    호봉제를 들어봤는가? 호봉제는 능력주의와 가장 반대되는 개념이다. 나라를 운영하는 공무원, 군인, 교사 등의 직업뿐만 아니라 많은 기업들에게 적용된다. “국내 300인 이상 기업 60%가 호봉제”. 한마디로 요약하자면 해고를 당하지 않는 이상, 근속을 더 오래 할수록 아무 노력을 하지 않아도 받는 돈이 늘어난다. 일을 해 본 누구나라면 쉽게 대답할 수 있다. 회사에 오래 다녔다고 해서 일을 잘 하는 것이 절대 아니다. ‘철밥통’이라는 말이 그냥 나오는 게 아니다. 여러 큰 회사에서 주는 월급부터 이렇게 잘못되었다면 어디서부터 다시 시작해야만 할까. 나라를 운영하는 일이 나라에서 가장 중요한데 공무원 월급부터 이렇다면 어디서부터 잘못된걸까.

    \n

    그래서 뭐가 문제냐고? 인재들이 일을 할 동기가 사라진다. 그 동기가 없어지면 나라의 성장이 저하되고 혁신이 없어진다. 돈으로 보상받아야 하는 이유는 이미 충분히 앞서 설명했다. 돈은 일할 동기를 부여한다. 당신의 직업 윤리와 열정 등도 물론 중요하겠지만, 돈에 비해서는 하찮은 동기부여일 뿐이다. 당신의 직업 윤리와 열정은 의식주를 마련해주지 않는다.

    \n

    더 짜증나는 점은 일을 할 동기는 없어지고, 불법적인 방법으로 돈을 벌 동기가 더 부여된다는 것이다. 회사에서 일을 열심히 하든 열심히 하지 않든 들어오는 돈은 똑같다는 걸 알게 되면 어떤 일이 벌어질까? 일도 하지 않으면서 주말에 설렁설렁 들어와서 초과근무 신청하고 소파에 누워서 쉬는 짓거리를 하게 된다. 그래야 일을 더 열심히 한다고 해서 보상받지 못해 아쉬운만큼의 양을 벌을 수 있다. 초과 근무수당 3184만원 챙겼다…부정수령 공무원 1789명. 군대 다녀온 사람이면 다 안다. 주말에 부사관, 장교들 들어와서 초과근무 일지는 대충 채워넣고 하루종일 휴대폰 보다가 집 가면 꽁돈 번다. 물론 근본적으로 부정부패와 직업윤리에 대한 문제도 있겠으나, 과연 그 사람들이 열심히 일하는만큼 보상받기만 했다면 이렇게까지 하고 싶을지는 당신도 깊게 생각해 봐야 할 주제다.

    \n

    ‘꿀 빤다’. 처리한 일에 비해 그에 합당하지 않은 경제적 (혹은 다른 종류의) 이득을 얻는 행위를 말한다. 2010년대 초중반쯤부터 많이 유행한 단어다. 그냥 듣고 웃어 넘길수도 있지만, 난 이것이 능력주의의 현주소를 조명하는 어휘라고 생각한다. 일을 하지 않거나 일을 못하면 돈은 벌지 못해야 한다. 반대면 돈을 많이 벌어야만 한다.

    \n

    경영 세습과 낙하산 인사. 3·4대 세습이 판치는 재계…다른 나라들은?. 끝이 안 보이는 재벌의 ‘세습 경영’. 호봉제와 비슷하다. 노력하지 않아도 당신이 재벌의 가정에서 태어나기만 했다면 두뇌가 비상한지의 여부에 관련없이 어쨌든 대기업의 한 자리를 할 수 있게 된다. 아무리 능력이 좋아도 당신이 그 집의 아들이, 딸이 아니라면 더 높은 자리(더 높은 보상)를 차지할 수 없다. 낙하산 인사도 마찬가지. [대기업 낙하산채용] 낙하산도 모자라 직장 내 특혜까지 누려. [단독] 지원서도 안 냈는데 연봉 1억…산업부 공공기관 낙하산 백태. 당신은 열심히 일해서 겨우 올라왔는데 옆자리 사람은 위에서 내려왔다. 일을 열심히 해서 더 보상받을 수 있겠다는 확신이 퍽이나도 들겠다.

    \n

    나이와 경력. 나이 문제에 대해서는 독립적인 기고문 형식으로 또 남기겠지만, 여기서 간단하게 짚고 넘어가자. 한국은 나이가 많거나 경력이 길다는 뜻은 능력이 좋고, 나이가 적고 경력이 짧으면 능력이 좋지 않다는 해석이 자주 되는 경향이 있다. 직장 다녀본 사람이면 정~말 알겠지만 하.나.도. 사실이 아니다. 그러면서 어린 사원에게는 ‘막내’ 꼬리표가 따라다니기 마련이다. ‘막내’가, ‘신입사원’이 뭘 할 수 있겠냐라는 인식이 팽배한 건 사실인 걸 인정해야 한다. 결국 나이가 어리거나 경력이 짧은 사람은 아무리 출중하다고 한들 빠르게 능력을 인정받아 보상을 더 받기에는 어려운 구조라고 할 수 있다. 물론 ‘초고속 승진’ 케이스들도 있겠지만 극히 일부다. 이건 어떤 기사를 따와서 객관적으로 증명하기보단.. 당신이 헬조선 회사에 조금이라도 취직해서 다녀봤으면 알 수 있는 점이라 당신에게 그 일을 남겨두겠다.

    \n

    이전 연봉 비교. 진짜 웃긴게 한국은 보통의 경우 이전 직장에서의 연봉을 물어보게 되어있고, 그것에 따라 마지막 숫자가 달라지기 마련이다. 근데 현재의 직장이 요구하는 일의 복잡도와 수준에 따라 연봉이 결정되어야 올바르지 않을까? 막말로 연 100억 벌던 대기업 회장님이 스타트업 주니어 마케터를 갑자기 하고 싶어서 연봉 협상을 하게 되면 100억이 기준점이 되지 않아야 하는 것처럼. 그럼 왜 반대는 말이 되냐는 말이다. 내가 이전에 얼마를 벌었든 지금 하게 될 일이 얼마나 복잡하고 어려운지에 따라 보상하는게 이치에 맞지 않는가?

    \n

    반면 미국의 특정 주에서는 ‘salary range transparency laws’라는 것도 있다. 한마디로 포지션에 지원하기 전에 고용주가 그 포지션에 보상하기 원하는 금액을 채용 공고에 적어놓아야 하는 법이다.

    \n

    \n \n \n \n \n

    \n

    이게 당연한 게 아닐까?…

    \n

    개인적으로, 나는 이것이 정치와 이념의 문제가 아니라고 생각한다. 보통 우파가 능력주의를 지지한다는 말에는 모두가 동의할 것이다. 그런데 정치를 떠나서, 사람들의 관심사와 장단점이 각양각생인데 어떻게 이들을 능력이 아닌 다른 수단으로 평가할 수 있는가?

    \n

    1시간 내에 내 친구는 빵을 한 조각만 굽고 나는 동일한 품질의 빵을 열 조각을 구울 수 있다면 내가 당연히 더 높은 보수를 받아야 하는 게 아닌가? 그리고 그 친구는 빵 굽는 일 말고 나보다 더 잘 하는 일이 있을 테니 그 일을 찾아 하면 된다. 이게 바로 본인의 비교우위(comparative advantage)를 찾는 자연스러운 과정이다.

    \n

    그래서?

    \n

    그래서 어쨌냐고? 합당한 보상을 받지 못하니, 아무도 가치있는 문제를 해결하려고 들지 않는다. 가치있는 문제를 해결하지 않으면 나라의 경쟁력이 하락한다. 그 나라 혹은 다른 나라의 문제를 해결하지 못하는 나라는 다른 나라가 잘 대해줄 이유가 없다. 그렇게 나라는 망조의 탄탄대로(ㅋ..)를 가게 되는거다.

    \n

    남은 이야기

    \n

    앞으로는 대략 이런 주제를 나눠보고자 한다. 아직 확실하게 결정된 건 아니라서 바뀔 수도 있다.

    \n
      \n
    • 한국이 망할 수밖에 없는 이유 (2): 문화 (집단주의, 나이, 노예근성, 체면/겉치레, 교육)
    • \n
    • 한국이 망할 수밖에 없는 이유 (3): 저출산, 고령화시대 (1와 2의 결론을 바탕으로)
    • \n
    • 한국 탈출 마스터플랜: 영어, 경각심, 안일함
    • \n
    ","frontmatter":{"title":"한국이 망할 수밖에 없는 이유 (1): 사라진 능력주의","date":"December 02, 2023","keywords":null},"fields":{"readingTime":{"text":"25 min read"}}}},"pageContext":{"slug":"/2023-12-02-한국이-망할-수밖에-없는-이유-(1)-사라진-능력주의/","previous":{"fields":{"slug":"/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/","readingTime":{"text":"36 min read"}},"frontmatter":{"title":"Technical intro to DeFi lending protocols with zkLend codebase as an example","tab":"post","tags":["blockchain","dev"],"keywords":["zklend","cairo","defi","lending","code"]}},"next":{"fields":{"slug":"/2024-01-01-내맘대로-적는-부의-추월차선-요약-및-리뷰/","readingTime":{"text":"37 min read"}},"frontmatter":{"title":"내맘대로 적는 <부의 추월차선> 요약 및 리뷰 (15 / 100)","tab":"post","tags":["books"],"keywords":["부의 추월차선","엠제이 드마코","book"]}}}},"staticQueryHashes":["3128451518","426816048"]} \ No newline at end of file +{"componentChunkName":"component---src-templates-blog-post-js","path":"/2023-12-02-한국이-망할-수밖에-없는-이유-(1)-사라진-능력주의/","result":{"data":{"site":{"siteMetadata":{"title":"Joel's dev blog","author":"Joel Mun","siteUrl":"https://9oelm.github.io"}},"markdownRemark":{"id":"15e17f1f-4136-5313-855a-29e1eb0a1fe7","excerpt":"…","html":"

    몇년동안 적어야지 적어야지 말만 해대다가 드디어 이 주제에 대해서 글을 적어보도록 하겠다. 바라건대 시리즈로 풀어나갈 이 주제는 비단 한국이 왜 망할 수밖에 없는지에 대한 내용은 아니다. 이것은 내가 왜 한국을 싫어하는지에 대한 이유다. 이것은 또한 그 많고 많은 재외한국인이 죽어도 한국에서 일을 하기 싫어하는 이유다. 이것은 내가 한국에서 새로이 자라나는 어린 친구들이 불쌍하다고 생각하는 이유다. 이 글을 읽는 당신도 당신만의 어떤 이유를 찾을 수 있기를 바란다.

    \n

    시간이 되면 영어로 다시 써야겠다고 생각하고 있다. 이유는, 내가 함께 대화해본 그 어떤 외국인 친구도 한국의 이런 문제점뿐만 아니라 한국이 망하는 건 시간문제라는 걸 전혀 모른다. 진짜 하나도 모른다. 이게 정말 놀라웠다. 심지어 한국 여행을 갔다온 친구들도 잘 알지 못했다.

    \n

    원래는 책을 쓰거나 유튜브 동영상을 업로드 해야겠다고 생각했으나 그게 오히려 시간이 너무 오래 걸릴 것 같아서 그냥 일단 블로그에 글을 쓰는 걸로 시작부터 하려고 한다. 생각만 하다가 결국엔 미루고 미루고 지금까지 시작하지 못했다. 하여튼. 고사하고. 시작하자.

    \n

    하고 싶은 얘기가 너무 많다. 하나씩 살펴볼텐데… 오늘은 사라진 능력주의를 살펴보자.

    \n

    능력주의

    \n

    누구나 알다시피 아담 스미스는 보이지 않는 손이라는 개념을 제시했다. 개념은 간단하다. 사람들을 내버려 둔다면 개인의 이기적인 행동들이 서로를 위한 이익의 추구로 연결된다는 것이다. 배고픈 사람들은 빵을 사먹기 위해 회사에 가서 일을 할 것이며, 월세를 지불하고 싶은 사람은 빵을 만들어 팔 것이다. 세입자가 주는 월세로 돈을 벌고 싶은 사람은 집 한 채를 구매하기 위해 큰 사업을 벌릴 것이다.

    \n

    능력주의는 이와 밀접한 연관이 있다. 능력이 좋다면 주어진 시간 안에 돈을 더 많이 벌을 수 있어야 한다. 경쟁하고 있는 빵집보다 비교적 싸고 맛있는 빵을 만들 수 있는 능력만 된다면 빵집 주인은 비교적 더 많은 수익을 얻을 수 있을 것이다. 당연한 결말이다. 고객이 더 싸고 맛있는 빵을 원하기 때문이다. 이것은 빵집 주인이 더 열심히 노동하도록 만들어 주는 긍정적 피드백으로 작용한다. 반면, 비교적 비싸고 맛 없는 빵을 만드는 빵집 주인은 고객을 빼앗긴 나머지 빵집을 유지하지 못할 수준의 수입을 올릴 것이며, 결국엔 빵집이 아닌 수익을 얻을 다른 방법을 찾아야 할 것이다. 결국 돈이 사람을 움직이는 원동력인 것이다.

    \n

    일터에서도 능력주의는 예외가 아니다. 당신이 일을 열심히 하는 이유는 무엇인가? 아니, 당신이 일을 열심히 하게 만드는 이유는 무엇인가? 바로 돈이다.

    \n

    \n

    혹자는 질문할 수도 있다. 그게 돈이라고 어떻게 확신하지? 단순히 열정이나 꿈일 수 있지 않을까? 랜덤한 블로그에서 가져온 평균 소득이 가장 낮은 직업 50개를 예를 들어 설명을 해 보자. “자연 및 문화해설사”가 연봉 783만원으로 가장 최저 평균 소득을 얻는다. 그런데 만약 ‘나는 문화를 다른 이들에게 알려주는 일이 가치있고 보람차다고 생각해 이것에 열정을 쏟고 싶다’라고 말하는 사람이 있다면 나의 주장이 잘못될 수도 있지 않을까?

    \n

    글쎄다. 일단 현실적으로 평균적인 사회 생활을 하고 평균적인 여가와 오락을 즐기며 평균적인 식성 등을 가진 다 큰 독립한 성인이 783만원으로 일 년 생활이 가능할지 의문이다. 78312=65.25\\frac{783}{12}=65.2512783=65.25만원인데, 65.25만원으로, 미래를 위해 저축하고 사회생활 (결혼식 축의금 등)도 잘 하고, 외식도 가끔 하고, 여행도 다니면서 살 수 있을까? 거의 불가능이다. 당신이 783만원 혹은 이하를 한국에서 벌면서 과연 행복할 수 있을까? 나는 아니라고 본다.

    \n

    \n \n \n \n \n

    \n

    \n \n \n \n \n

    \n

    내가 말하고 싶은 건, 돈에 관심이 없다는 사실 자체는 전혀 문제가 되지 않지만, 그것에 상응하는 결과가 따르기 마련이라는 것이다. 결국 평균 혹은 그 이상의 행복을 원한다면 평균 혹은 그 이상의 돈을 벌고 그것에 관심을 가져야 한다는 말에는 흠이 없을 것이다.

    \n

    돈은 열정과 다르다. 돈은 꿈과 다르다. 돈은 무슨 뜬구름 잡는 소리가 아니다. 전 세계 어느 나라에 가도 돈은 사용된다. 모두가 이해하고 모두가 사용하고 모두가 가치를 인정해준다. 돈의 보편성은 누구라도 알아준다. 당신이 게임에 대한 열정이 있는데 그것으로 소고기를 먹고 싶다면 열정으로 소고기를 사는 대신 그 열정으로 프로게이머가 돼서 돈을 벌고 그 돈으로 소고기를 살 것이다. 당신이 시간이 있는데 그 시간으로 소고기를 사고 싶다면 카페에서 알바를 해서 돈을 받고 그 돈으로 소고기를 살 것이다. 이런 의미에서 돈은 보편적이고 유용한 사회적 합의이다.

    \n

    노동의 자유시장경제

    \n

    그렇기 때문에 당신이 회사에서 일을 하면 시간으로 월급을 받지 않는다. 소고기로 월급을 받지 않는다. 귀여운 토끼 인형으로 월급을 받지 않는다. 왜? 돈이라는 사회적 합의로 모든 것을 사고 팔기로 동의했기 때문이다.

    \n

    그렇다면 여기까지 왜 노동을 해서 돈을 벌을 수밖에 없는지에 대해 설명해봤다. 그런데 문제가 생긴다.

    \n

    빵집에서 일을 하고 돈을 받으려고 하는데, 동일하게 주어진 시간 내에 나보다 빵을 더 맛있고 완성도 있게 만드는 사람이 천 명이 더 있다. 내가 만든 빵을 먹으면 바로 토만 나오는데 그들이 만드는 빵은 입에서 살살 녹아 잘 팔리기까지 한다.

    \n

    당신이 빵집 사장이라면 나를 고용하겠는가, 그 천 명중 한 명을 고용하겠는가? 당연히 나를 버려야 맞다. 나는 안중에도 없어야 한다.

    \n

    이게 바로 고등/대학교에서 배웠을 기본 중에 기본인 자유시장경제의 원리다.

    \n

    \n \n \n \n \n

    \n

    좀 더 쉽게 설명해보자. 공급 먼저. P1=5000P1 = 5000P1=5000원의 시급을 준다고 했을 때 100명이 제빵사를 하고 싶은 마음이 있다고 가정하자. 그게 Q1Q1Q1 정도의 위치가 될 것이다. 그런데 만약 빵을 굽는 일이 모종의 이유로 매우 중요해져서 P2=100,000P2 = 100,000P2=100,000원의 시급을 준다고 해 보자. 그러면 제빵사 하기 싫은 사람도 하고 싶은 마음이 샘솟을 것이다. 그래서 하고 싶은 사람이 10만명으로 늘어난다. 그게 Q2Q2Q2의 위치라고 가정하자.

    \n

    이것도 복잡하다면 뉴스로 쉽게 이해하자. ‘평균 연봉 1억’ 기아 킹산직 300명 뽑는다…10만명 지원 대란? 그냥 돈 많이 주면 그 일을 하고 싶은 사람이 많이 생기기 마련이다. 평균 연봉이 2천이었으면 10만명이나 지원하고 싶겠는가.

    \n

    다음, 수요. 시급이 5000원이라면 싸다고 생각해서 더 많은 제빵사를 채용해서 (Q2Q2Q2) 사업을 확장하고 잘 굴러가게 하고 싶을 것이다. 그런데 시급이 만약에 10만원이라면 아마 한 명 (Q1Q1Q1)만 채용하고 말 것이다. 기아차도 마찬가지. 평균 연봉이 1억이니까 300명만 뽑았지, 5만명을 뽑겠는가.

    \n

    그래서 왜 자유시장경제냐고? 아무도 방해하거나 참견하지 않으니까 자유하다. 시급이 너무 비싸서 한 명만 채용하는 사장님에게 너무 적게 채용하니까 더 채용해달라는 사회의 압박감이나 정부의 요청은 터무니없다. 이미 채용된 제빵사가 일은 못하지만 근속을 오래했다는 이유만으로 본래 시장에서 받아야 할 시급의 두 배를 받는 것은 (예를 들자면 P1P1P1을 받아야 하는 사람이 P2P2P2를 받고 있다면) 시스템의 특정 부분이 잘못되어 자유시장이 제 역할을 하지 않는다는 증거다.

    \n

    앞서 말했듯이 모든 제빵사의 실력이 동일할리가 없다. 제빵사 노동시장이 상식적이고 자유롭다면 제빵사의 실력은 Y축인 시급과 비례해야만 한다. 시급이 P2P2P2만큼 늘어난다면 물론 Q2Q2Q2만큼의 제빵사가 되고 싶어하는 사람이 있겠지만, Q2Q2Q2중 제빵 초보도 있을 것이고 고수도 있을 것이고 유명 베이커리에서 이미 20년동안 일한 장인이 있을 수도 있을 것이다. 그러면 아마 그 중 잘 하는 사람이 P2P2P2를 차지하고, 못 하는 사람은 P2P2P2보다 적은 시급을 주는 포지션으로 밀려나게 될 것이다. 공정하고 행복하다. 못 하면 덜 벌고, 잘 하면 더 번다.

    \n

    파괴된 능력주의

    \n
    \n

    못 하면 덜 벌고, 잘 하면 더 번다.

    \n
    \n

    아쉽게도 이 말, 즉 노동의 자유시장경제는 한국에서 대부분의 경우 사실이었던 적이 없다.

    \n

    호봉제를 들어봤는가? 호봉제는 능력주의와 가장 반대되는 개념이다. 나라를 운영하는 공무원, 군인, 교사 등의 직업뿐만 아니라 많은 기업들에게 적용된다. “국내 300인 이상 기업 60%가 호봉제”. 한마디로 요약하자면 해고를 당하지 않는 이상, 근속을 더 오래 할수록 아무 노력을 하지 않아도 받는 돈이 늘어난다. 일을 해 본 누구나라면 쉽게 대답할 수 있다. 회사에 오래 다녔다고 해서 일을 잘 하는 것이 절대 아니다. ‘철밥통’이라는 말이 그냥 나오는 게 아니다. 여러 큰 회사에서 주는 월급부터 이렇게 잘못되었다면 어디서부터 다시 시작해야만 할까. 나라를 운영하는 일이 나라에서 가장 중요한데 공무원 월급부터 이렇다면 어디서부터 잘못된걸까.

    \n

    그래서 뭐가 문제냐고? 인재들이 일을 할 동기가 사라진다. 그 동기가 없어지면 나라의 성장이 저하되고 혁신이 없어진다. 돈으로 보상받아야 하는 이유는 이미 충분히 앞서 설명했다. 돈은 일할 동기를 부여한다. 당신의 직업 윤리와 열정 등도 물론 중요하겠지만, 돈에 비해서는 하찮은 동기부여일 뿐이다. 당신의 직업 윤리와 열정은 의식주를 마련해주지 않는다.

    \n

    더 짜증나는 점은 일을 할 동기는 없어지고, 불법적인 방법으로 돈을 벌 동기가 더 부여된다는 것이다. 회사에서 일을 열심히 하든 열심히 하지 않든 들어오는 돈은 똑같다는 걸 알게 되면 어떤 일이 벌어질까? 일도 하지 않으면서 주말에 설렁설렁 들어와서 초과근무 신청하고 소파에 누워서 쉬는 짓거리를 하게 된다. 그래야 일을 더 열심히 한다고 해서 보상받지 못해 아쉬운만큼의 양을 벌을 수 있다. 초과 근무수당 3184만원 챙겼다…부정수령 공무원 1789명. 군대 다녀온 사람이면 다 안다. 주말에 부사관, 장교들 들어와서 초과근무 일지는 대충 채워넣고 하루종일 휴대폰 보다가 집 가면 꽁돈 번다. 물론 근본적으로 부정부패와 직업윤리에 대한 문제도 있겠으나, 과연 그 사람들이 열심히 일하는만큼 보상받기만 했다면 이렇게까지 하고 싶을지는 당신도 깊게 생각해 봐야 할 주제다.

    \n

    ‘꿀 빤다’. 처리한 일에 비해 그에 합당하지 않은 경제적 (혹은 다른 종류의) 이득을 얻는 행위를 말한다. 2010년대 초중반쯤부터 많이 유행한 단어다. 그냥 듣고 웃어 넘길수도 있지만, 난 이것이 능력주의의 현주소를 조명하는 어휘라고 생각한다. 일을 하지 않거나 일을 못하면 돈은 벌지 못해야 한다. 반대면 돈을 많이 벌어야만 한다.

    \n

    경영 세습과 낙하산 인사. 3·4대 세습이 판치는 재계…다른 나라들은?. 끝이 안 보이는 재벌의 ‘세습 경영’. 호봉제와 비슷하다. 노력하지 않아도 당신이 재벌의 가정에서 태어나기만 했다면 두뇌가 비상한지의 여부에 관련없이 어쨌든 대기업의 한 자리를 할 수 있게 된다. 아무리 능력이 좋아도 당신이 그 집의 아들이, 딸이 아니라면 더 높은 자리(더 높은 보상)를 차지할 수 없다. 낙하산 인사도 마찬가지. [대기업 낙하산채용] 낙하산도 모자라 직장 내 특혜까지 누려. [단독] 지원서도 안 냈는데 연봉 1억…산업부 공공기관 낙하산 백태. 당신은 열심히 일해서 겨우 올라왔는데 옆자리 사람은 위에서 내려왔다. 일을 열심히 해서 더 보상받을 수 있겠다는 확신이 퍽이나도 들겠다.

    \n

    나이와 경력. 나이 문제에 대해서는 독립적인 기고문 형식으로 또 남기겠지만, 여기서 간단하게 짚고 넘어가자. 한국은 나이가 많거나 경력이 길다는 뜻은 능력이 좋고, 나이가 적고 경력이 짧으면 능력이 좋지 않다는 해석이 자주 되는 경향이 있다. 직장 다녀본 사람이면 정~말 알겠지만 하.나.도. 사실이 아니다. 그러면서 어린 사원에게는 ‘막내’ 꼬리표가 따라다니기 마련이다. ‘막내’가, ‘신입사원’이 뭘 할 수 있겠냐라는 인식이 팽배한 건 사실인 걸 인정해야 한다. 결국 나이가 어리거나 경력이 짧은 사람은 아무리 출중하다고 한들 빠르게 능력을 인정받아 보상을 더 받기에는 어려운 구조라고 할 수 있다. 물론 ‘초고속 승진’ 케이스들도 있겠지만 극히 일부다. 이건 어떤 기사를 따와서 객관적으로 증명하기보단.. 당신이 헬조선 회사에 조금이라도 취직해서 다녀봤으면 알 수 있는 점이라 당신에게 그 일을 남겨두겠다.

    \n

    이전 연봉 비교. 진짜 웃긴게 한국은 보통의 경우 이전 직장에서의 연봉을 물어보게 되어있고, 그것에 따라 마지막 숫자가 달라지기 마련이다. 근데 현재의 직장이 요구하는 일의 복잡도와 수준에 따라 연봉이 결정되어야 올바르지 않을까? 막말로 연 100억 벌던 대기업 회장님이 스타트업 주니어 마케터를 갑자기 하고 싶어서 연봉 협상을 하게 되면 100억이 기준점이 되지 않아야 하는 것처럼. 그럼 왜 반대는 말이 되냐는 말이다. 내가 이전에 얼마를 벌었든 지금 하게 될 일이 얼마나 복잡하고 어려운지에 따라 보상하는게 이치에 맞지 않는가?

    \n

    반면 미국의 특정 주에서는 ‘salary range transparency laws’라는 것도 있다. 한마디로 포지션에 지원하기 전에 고용주가 그 포지션에 보상하기 원하는 금액을 채용 공고에 적어놓아야 하는 법이다.

    \n

    \n \n \n \n \n

    \n

    이게 당연한 게 아닐까?…

    \n

    개인적으로, 나는 이것이 정치와 이념의 문제가 아니라고 생각한다. 보통 우파가 능력주의를 지지한다는 말에는 모두가 동의할 것이다. 그런데 정치를 떠나서, 사람들의 관심사와 장단점이 각양각생인데 어떻게 이들을 능력이 아닌 다른 수단으로 평가할 수 있는가?

    \n

    1시간 내에 내 친구는 빵을 한 조각만 굽고 나는 동일한 품질의 빵을 열 조각을 구울 수 있다면 내가 당연히 더 높은 보수를 받아야 하는 게 아닌가? 그리고 그 친구는 빵 굽는 일 말고 나보다 더 잘 하는 일이 있을 테니 그 일을 찾아 하면 된다. 이게 바로 본인의 비교우위(comparative advantage)를 찾는 자연스러운 과정이다.

    \n

    그래서?

    \n

    그래서 어쨌냐고? 합당한 보상을 받지 못하니, 아무도 가치있는 문제를 해결하려고 들지 않는다. 가치있는 문제를 해결하지 않으면 나라의 경쟁력이 하락한다. 그 나라 혹은 다른 나라의 문제를 해결하지 못하는 나라는 다른 나라가 잘 대해줄 이유가 없다. 그렇게 나라는 망조의 탄탄대로(ㅋ..)를 가게 되는거다.

    \n

    남은 이야기

    \n

    앞으로는 대략 이런 주제를 나눠보고자 한다. 아직 확실하게 결정된 건 아니라서 바뀔 수도 있다.

    \n
      \n
    • 한국이 망할 수밖에 없는 이유 (2): 문화 (집단주의, 나이, 노예근성, 체면/겉치레, 교육)
    • \n
    • 한국이 망할 수밖에 없는 이유 (3): 저출산, 고령화시대 (1와 2의 결론을 바탕으로)
    • \n
    • 한국 탈출 마스터플랜: 영어, 경각심, 안일함
    • \n
    ","frontmatter":{"title":"한국이 망할 수밖에 없는 이유 (1): 사라진 능력주의","date":"December 02, 2023","keywords":null},"fields":{"readingTime":{"text":"25 min read"}}}},"pageContext":{"slug":"/2023-12-02-한국이-망할-수밖에-없는-이유-(1)-사라진-능력주의/","previous":{"fields":{"slug":"/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/","readingTime":{"text":"40 min read"}},"frontmatter":{"title":"Technical intro to DeFi lending protocols with zkLend codebase as an example","tab":"post","tags":["blockchain","dev"],"keywords":["zklend","cairo","defi","lending","code"]}},"next":{"fields":{"slug":"/2024-01-01-내맘대로-적는-부의-추월차선-요약-및-리뷰/","readingTime":{"text":"37 min read"}},"frontmatter":{"title":"내맘대로 적는 <부의 추월차선> 요약 및 리뷰 (15 / 100)","tab":"post","tags":["books"],"keywords":["부의 추월차선","엠제이 드마코","book"]}}}},"staticQueryHashes":["3128451518","426816048"]} \ No newline at end of file diff --git a/page-data/index/page-data.json b/page-data/index/page-data.json index 21b97d27..4996db0c 100644 --- a/page-data/index/page-data.json +++ b/page-data/index/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-pages-index-js","path":"/","result":{"data":{"site":{"siteMetadata":{"title":"Joel's dev blog"}},"allMarkdownRemark":{"edges":[{"node":{"excerpt":"This is a brief summary of the anatomy of Starknet’s calldata and how to decode it. The Internet needs more information about how Starknet…","fields":{"slug":"/2024-01-26-decoding-calldata-of-transactions-on-starknet/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"January 26, 2024","title":"Decoding calldata on Starknet","tab":"post"}}},{"node":{"excerpt":"…","fields":{"slug":"/2024-01-01-내맘대로-적는-부의-추월차선-요약-및-리뷰/","readingTime":{"text":"37 min read"}},"frontmatter":{"date":"January 01, 2024","title":"내맘대로 적는 <부의 추월차선> 요약 및 리뷰 (15 / 100)","tab":"post"}}},{"node":{"excerpt":"…","fields":{"slug":"/2023-12-02-한국이-망할-수밖에-없는-이유-(1)-사라진-능력주의/","readingTime":{"text":"25 min read"}},"frontmatter":{"date":"December 02, 2023","title":"한국이 망할 수밖에 없는 이유 (1): 사라진 능력주의","tab":"post"}}},{"node":{"excerpt":"How a lending protocol works Functionalities Deposit Borrow Leverage Other usages Liquidate Repay Over-collateralization Utilization rate…","fields":{"slug":"/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/","readingTime":{"text":"36 min read"}},"frontmatter":{"date":"October 26, 2023","title":"Technical intro to DeFi lending protocols with zkLend codebase as an example","tab":"post"}}},{"node":{"excerpt":"I noticed that my previous summaries are too comprehensive and inclusive, so from now on I will try to be more succinct. Here’s the summary…","fields":{"slug":"/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/","readingTime":{"text":"18 min read"}},"frontmatter":{"date":"January 01, 2023","title":"Full summary of (14 / 100)","tab":"post"}}},{"node":{"excerpt":"This is my summary on Think Again by Adam Grant, who’s already a very renowned professor teaching at Wharton, and also a bestselling author…","fields":{"slug":"/2022-12-30-full-summary-and-reflection-on-Think-again-by-adam-grant/","readingTime":{"text":"15 min read"}},"frontmatter":{"date":"December 30, 2022","title":"Full summary of by Adam Grant (13 / 100)","tab":"post"}}},{"node":{"excerpt":"Preface This is my own curated list of tips and tricks about data structures and algorithms, in Python or TypeScript. Checklist If you are…","fields":{"slug":"/2022-09-15-data-structures-and-algorithms-cheatsheet/","readingTime":{"text":"67 min read"}},"frontmatter":{"date":"September 15, 2022","title":"All-in-one data structures and algorithms cheatsheet","tab":"post"}}},{"node":{"excerpt":"Why does Notion not have a built-in knowledge graph? So I made it myself The libraries: and Why I made How I made it The way forward…","fields":{"slug":"/2022-09-06-announcing-a-performant-knowledge-graph-tool-on-the-web/","readingTime":{"text":"11 min read"}},"frontmatter":{"date":"September 06, 2022","title":"Announcing a performant knowledge graph visualization tool for Notion and its underlying library","tab":"post"}}},{"node":{"excerpt":"This is a brief story of how I chose the best hotel for quarantine upon arrival in Hong Kong I could possibly choose using Github actions…","fields":{"slug":"/2022-06-17-Hong-kong-government-announces-the-list-of-quarantine-designated-hotels-for-the-8th-cycle-and-heres-how-i-became-the-first-person-in-the-world-to-know-it/","readingTime":{"text":"9 min read"}},"frontmatter":{"date":"June 17, 2022","title":"Hong Kong government announces the list of quarantine designated hotels for the 8th cycle, and here's how I became the first person in the world to know it.","tab":"post"}}},{"node":{"excerpt":"So I have enrolled in this computer architecture class for the current semester. The lecture is based on the RISC-V instruction set, which…","fields":{"slug":"/2022-05-13-RISC-V-Simulator-On-The-Web/","readingTime":{"text":"13 min read"}},"frontmatter":{"date":"May 14, 2022","title":"Practical Emscripten & Webassembly: Simple RISC-V Simulator on the web","tab":"post"}}},{"node":{"excerpt":"Rationale I have been trying to port some C++ project to Webassembly for the first time (I have worked on Assemblyscript and Rust projects…","fields":{"slug":"/2022-05-10-How-to-compile-C++-code-into-Webassembly-with-Emscripten-and-use-it-in-Webpack-+-Typescript-+-React-project/","readingTime":{"text":"7 min read"}},"frontmatter":{"date":"May 09, 2022","title":"How to compile C++ code into Webassembly with Emscripten + Docker and use it in Webpack + Typescript + React project","tab":"post"}}},{"node":{"excerpt":"TED…","fields":{"slug":"/2022-02-20-평균의-종말-완전-요약-및-감상평/","readingTime":{"text":"64 min read"}},"frontmatter":{"date":"February 20, 2022","title":"<평균의 종말> 완전 요약 및 감상평 (12 / 100)","tab":"post"}}},{"node":{"excerpt":"12 Rules for Life This post is a very detailed summary and reflection of Jordan Peterson’s book: 12 Rules for Life. Each short summary…","fields":{"slug":"/2022-02-17--detailed-summary-and-reflection-of-12-rules-for-life-a-truly-life-changing-piece/","readingTime":{"text":"75 min read"}},"frontmatter":{"date":"February 17, 2022","title":"12 Rules for Life: a detailed summary and reflection of a truly life-changing book (11 / 100)","tab":"post"}}},{"node":{"excerpt":"This is a personal reflection and review of the book Cosmos. Table of contents Cosmos Cosmos is (and has been) everything Human intellect…","fields":{"slug":"/2022-02-02-100BooksProject-(10):Cosmos/","readingTime":{"text":"11 min read"}},"frontmatter":{"date":"February 02, 2022","title":"100BooksProject: (10): Cosmos","tab":"post"}}},{"node":{"excerpt":"Find the shortest palindrome Inefficient algorithm: bruteforcing Longest Proper Prefix which is Suffix (LPS) The KMP algorithm Finding the…","fields":{"slug":"/2022-01-06--find-the-shortest-palindrome-an-intensive-review-of-kmp(knuth-morris-pratt)-algorithm/","readingTime":{"text":"19 min read"}},"frontmatter":{"date":"January 06, 2022","title":"Find the shortest palindrome: an intensive review of the KMP(Knuth–Morris–Pratt) algorithm","tab":"post"}}},{"node":{"excerpt":"Your Elasticsearch and Kibana instances are open, and that’s a real problem I have been doing some serious research on the public exposure…","fields":{"slug":"/2022-01-02--elasticpwn-how-to-find-and-collect-data-from-exposed-elasticsearch-and-kibana-instances/","readingTime":{"text":"6 min read"}},"frontmatter":{"date":"January 02, 2022","title":"elasticpwn: how to collect and analyse data from exposed Elasticsearch and Kibana instances","tab":"post"}}},{"node":{"excerpt":"I needed to figure out how to set up multiple monitors on my new Kali linux laptop, because it wouldn’t just work automatically like MacOS…","fields":{"slug":"/2021-09-17--set-up-multiple-monitors-on-optimus-laptop-running-kali-linux/","readingTime":{"text":"7 min read"}},"frontmatter":{"date":"September 17, 2021","title":"Set up multiple monitors on Optimus laptop running Kali linux","tab":"post"}}},{"node":{"excerpt":"This is a full guide to locally develop and deploy a backend app with a recently released container image feature for lambda on AWS…","fields":{"slug":"/2021-03-13--complete-end-to-end-guide-for-developing-dockerized-lambda-with-typescript-terraform-and-SAM-cli/","readingTime":{"text":"40 min read"}},"frontmatter":{"date":"March 13, 2021","title":"Complete end-to-end guide for developing dockerized lambda in Typescript, Terraform and SAM CLI","tab":"post"}}},{"node":{"excerpt":"As always, I’ve summarized the book so that anyone can get the main concept in about 20-30 mins. And then I wrote some reflections and…","fields":{"slug":"/2020-10-04-100BooksProject-(9):Atomic-habits/","readingTime":{"text":"30 min read"}},"frontmatter":{"date":"October 04, 2020","title":"100BooksProject: (9): Atomic habits","tab":"post"}}},{"node":{"excerpt":"회사에서 ‘사실상 PM’과 개발자 역할을 같이 수행하게 되면서, 경영에 자꾸 관심이 갔다. 내가 부족하다는 느낌이 많이 들었다. 그래서 무작정 Yes2…","fields":{"slug":"/2020-09-26-100BooksProject-(8):두려움-없는-조직-(The-fearless-organization)/","readingTime":{"text":"45 min read"}},"frontmatter":{"date":"September 26, 2020","title":"100BooksProject: (8): 두려움 없는 조직 (The fearless organization)","tab":"post"}}},{"node":{"excerpt":"Disclaimer: We will focus on itself in this article, rather than third-party libraries like , because it’s out of scope of the article. How…","fields":{"slug":"/2020-09-13--How-to-make-useSelector-not-a-disaster/","readingTime":{"text":"8 min read"}},"frontmatter":{"date":"September 13, 2020","title":"How to make useSelector not a disaster","tab":"post"}}},{"node":{"excerpt":"오랜만에 반디앤루니스에 가서 읽을 만한 책을 고르다가 베스트셀러를 차지하고 있는 돈의 속성을 사 봤다. 매번 그래왔듯이, 20~3…","fields":{"slug":"/2020-08-18--100BooksProject-(7):돈의-속성-(Properties-of-money)/","readingTime":{"text":"47 min read"}},"frontmatter":{"date":"August 18, 2020","title":"100BooksProject: (7): 돈의 속성 (Properties of money)","tab":"post"}}},{"node":{"excerpt":"I wrote a summary of each section and my reflections following it. typo alert: I typed so much that I can’t just review all… if you find a…","fields":{"slug":"/2020-08-17--100BooksProject-(6):How-to-win-friends-and-influence-people/","readingTime":{"text":"40 min read"}},"frontmatter":{"date":"August 17, 2020","title":"100BooksProject: (6): How to Win Friends & Influence People (인간관계론)","tab":"post"}}},{"node":{"excerpt":"Yes, Chrome’s performance tab is overwhelming First time you look into all the charts, you have no idea what means what. What does those…","fields":{"slug":"/2020-08-03--Learn-all-major functionalities-on-Chromes-Performance-tab-and-practice-it-with-a-simple-React-project/","readingTime":{"text":"14 min read"}},"frontmatter":{"date":"August 03, 2020","title":"Learn all major functionalities on Chrome's Performance tab and practice it with a simple React project","tab":"post"}}},{"node":{"excerpt":"초격차 경영에 관련된 책이라 일단은 무작정 사 보게 됐다. 일단 나중에 다시 책 볼 필요 없이 줄거리만 20~30분 안에 읽을 수 있는 정도로 요약했다. 그리고 나서 맨 마지막에 느낀 점과 나에게 적용할 수 있는 점을 적어보았다. 초격차 프롤로그…","fields":{"slug":"/2020-07-29--100BooksProject-(5):초격차(The-Great-Gap)/","readingTime":{"text":"51 min read"}},"frontmatter":{"date":"July 29, 2020","title":"100BooksProject: (5): 초격차 (The Great Gap)","tab":"post"}}},{"node":{"excerpt":"Summary Introduction Craving (열망) Success (성공) 실패 (Failure) Impressions after finishing this book The ways to survive in whichever contexts…","fields":{"slug":"/2020-07-05--100BooksProject-(4):Ego-is-the-enemy-(에고라는-적)/","readingTime":{"text":"19 min read"}},"frontmatter":{"date":"July 05, 2020","title":"100BooksProject: (4): Ego is the enemy (에고라는 적)","tab":"post"}}},{"node":{"excerpt":"I find this problem happening very often. And I never expected it to be so. I thought everyone in the world is able to express his/her…","fields":{"slug":"/2020-06-06--how-to-communicate-better/","readingTime":{"text":"5 min read"}},"frontmatter":{"date":"June 06, 2020","title":"How to communicate better","tab":"post"}}},{"node":{"excerpt":"…","fields":{"slug":"/2020-05-14--NSD-bulbaek-review/","readingTime":{"text":"14 min read"}},"frontmatter":{"date":"May 14, 2020","title":"[개발자도 리뷰 할 수 있다] 낙성대 문기사불백 리뷰","tab":"post"}}},{"node":{"excerpt":"Problem I was struggling at my company trying to write some tests for RxJS operations.\nMost of the network requests were managed by RxJS…","fields":{"slug":"/2020-01-24--Fundamental-yet-extensive-introduction-to-why-and-how-you-might-want-to-use-redux-observable-for-async-actions/","readingTime":{"text":"22 min read"}},"frontmatter":{"date":"January 24, 2020","title":"Extensive introduction to why and how you might want to use and test redux-observable","tab":"post"}}},{"node":{"excerpt":"Before we get started These are the keys usually used in combination with normal keys on Mac: Command (or Cmd) ⌘ Shift ⇧ (I will write as…","fields":{"slug":"/2020-01-08--The-shortcuts-I-like/","readingTime":{"text":"7 min read"}},"frontmatter":{"date":"January 08, 2020","title":"The shortcuts I like","tab":"post"}}},{"node":{"excerpt":"Back in the old days There was no alternative to javascript. It was just javascript. Javascript just happened to be with the web when it got…","fields":{"slug":"/2019-12-07--The-rise-of-low-level-prograamming-(Feat.-WebAssembly)/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"December 07, 2019","title":"The rise of low-level programming (feat. WebAssembly)","tab":"post"}}},{"node":{"excerpt":"The first encounter with the problem I was coding as usual. And I faced an odd encounter with how works. Here goes the code to give an…","fields":{"slug":"/2019-10-12--[...].forEach(sayHello)-does-not-always-say-hello/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"October 12, 2019","title":"[...].forEach(saveFromZombies) does not always save people","tab":"post"}}},{"node":{"excerpt":"I believed React is smart enough to know this… but aye? We had a bunch of large component trees in our company’s web application.\nAnd I saw…","fields":{"slug":"/2019-10-02--Making-stupid-react-smart-in-re-rendering/","readingTime":{"text":"12 min read"}},"frontmatter":{"date":"October 02, 2019","title":"Making stupid react smart in re-rendering","tab":"post"}}},{"node":{"excerpt":"Must must must watch! I got so much great insight from this video in Google IO 2019 detailing latest javascript specs. Improvements from the…","fields":{"slug":"/2019-09-10--Latest-javascript-specs/","readingTime":{"text":"10 min read"}},"frontmatter":{"date":"September 10, 2019","title":"New javascript specifications in 2019 (What's new in Javascript - Google I/O '19)","tab":"post"}}},{"node":{"excerpt":"What am I going to do? So I have my university’s timetable data in excel: I want to read this data using exceljs + node + typescript, and…","fields":{"slug":"/2019-08-17--Using-docker-for-mongodb/","readingTime":{"text":"14 min read"}},"frontmatter":{"date":"August 17, 2019","title":"Using docker and docker-compose to read data from excel to put it into mongodb","tab":"post"}}},{"node":{"excerpt":"1 2 3 4 1 면접이 끝나고 책 하나를 마음 놓고 읽었다. 죽음에 관한.. 책. 폴 칼라니티란 사람은 천재. 스탠포드, 캐임브리지, 예일대에서 문학, 생물학, 의예과 학위를 섭렵한 사람. 근데 36세에 암을 선고받고 살아가게 된다. 선고받은지…","fields":{"slug":"/2019-08-03--100BooksProject-(3):When-the-breath-becomes-air-(숨결이-바람-될-때)/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"August 03, 2019","title":"100BooksProject: (3): When the breath becomes air (숨결이 바람 될 때)","tab":"post"}}},{"node":{"excerpt":"Rationale I was creating an function: Time complexity For now, forget about the condition in the while loop: .\nYou know that you are going…","fields":{"slug":"/2019-05-05--Memoization-in-python-using-a-decorator:-getting-a-prime-number/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"May 05, 2019","title":"Memoization in python using a decorator: getting a prime number","tab":"post"}}},{"node":{"excerpt":"What I’m gonna build I’m gonna build a simple crawler that will send HTTP request to zigbang.com’s api server to receive data on estates…","fields":{"slug":"/2019-03-17--deploying-a-serverless-crawler-with-python-+-lambda+dynamoDB/","readingTime":{"text":"5 min read"}},"frontmatter":{"date":"March 18, 2019","title":"Deploying a serverless crawler with python + lambda","tab":"post"}}},{"node":{"excerpt":"The problem Sometimes you cannot easily change the version of Ubuntu and python if you are running them on cloud. I am using cloud9, as you…","fields":{"slug":"/2019-03-13--Updating-python3-on-an-outdated-ubuntu/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"March 13, 2019","title":"Updating python 3 on an outdated Ubuntu","tab":"post"}}},{"node":{"excerpt":"Encountering the book Sudden encounter over this book just walking by a random bookshelf in the library led me to spend the whole afternoon…","fields":{"slug":"/2019-03-13--100BooksProject-(2):Crossroads-(갈림길)/","readingTime":{"text":"23 min read"}},"frontmatter":{"date":"March 13, 2019","title":"100 Books Project (2): Crossroads (갈림길)","tab":"post"}}},{"node":{"excerpt":"Reflections Again, another big encouragement from a book. Things I got to know: Success is not everything. Things after success are…","fields":{"slug":"/2019-03-09--100BooksProject-(1):Remember-Who-You-Are:-Life-Stories-That-Inspire-the-Heart-and-Mind/","readingTime":{"text":"6 min read"}},"frontmatter":{"date":"March 09, 2019","title":"100 Books Project (1): Reflection and reading notes on Remember Who You Are: Life Stories That Inspire the Heart and Mind","tab":"post"}}},{"node":{"excerpt":"Hacking-notes Rationale As a programmer, I thought it’d be nice to know the basics of hacking for security purposes. For now, we will focus…","fields":{"slug":"/2018-11-02--random-hacking-notes/","readingTime":{"text":"15 min read"}},"frontmatter":{"date":"November 02, 2018","title":"random hacking notes","tab":"post"}}},{"node":{"excerpt":"No time Really, I’ve got no time to learn Vue. I need to get basic concepts in the shortest length of time. Vue Cli 3.0 We are going to use…","fields":{"slug":"/2018-10-19--Vue/","readingTime":{"text":"10 min read"}},"frontmatter":{"date":"October 19, 2018","title":"Vue","tab":"post"}}},{"node":{"excerpt":"Async and await See javascript.info Mozilla Basics Ok. This is not ES6. Its ES8 (ECMAScript 2017) syntax. The async function declaration…","fields":{"slug":"/2018-09-05--Async-and-await/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"September 05, 2018","title":"Async, await, promise","tab":"post"}}},{"node":{"excerpt":"1. Default binding without the strict mode in effect, the global object is eligible for the default binding: however, with the strict mode…","fields":{"slug":"/2018-06-30--This-&-Object-prototypes-(2):-this-All-Makes-Sense-Now!/","readingTime":{"text":"6 min read"}},"frontmatter":{"date":"June 30, 2018","title":"This & Object prototypes (2): this All Makes Sense Now!","tab":"post"}}},{"node":{"excerpt":"Types and objects Types in JS string number boolean null undefined object Built-in Objects String Number Boolean Object Function Array Date…","fields":{"slug":"/2018-06-30--This-&-Object-prototypes-(3):-Objects/","readingTime":{"text":"9 min read"}},"frontmatter":{"date":"June 30, 2018","title":"This & Object prototypes (3): Objects","tab":"post"}}},{"node":{"excerpt":"detecting security incidents event logs by SANS Regular Expressions to look for Keys to Check Domain controller Monitoring Active…","fields":{"slug":"/2018-05-29--Some-hacking-notes/","readingTime":{"text":"9 min read"}},"frontmatter":{"date":"May 29, 2018","title":"Some hacking notes","tab":"post"}}},{"node":{"excerpt":"Sources CSO microsoft gist Corrie Erk Process explorer Submit to VirusTotal to check hash Process Explorer -> Options -> VirusTotal.com…","fields":{"slug":"/2018-05-19--Using-foresic-tools-(1):-process-explorer,-process-monitor,-and-autoruns/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"May 19, 2018","title":"Using foresic tools (1): process explorer, process monitor, and autoruns","tab":"post"}}},{"node":{"excerpt":"Sources Lifewire SSLStore Hash A hashing algorithm is a mathematical function that condenses data to a fixed size. It is easier for the…","fields":{"slug":"/2018-05-12--MD5-and-SHA/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"May 12, 2018","title":"MD5 and SHA","tab":"post"}}},{"node":{"excerpt":"Sources mazebolt microsoft geeksforgeeks ddosguard flowguard wikipedia Three-way handshake: how does it work When is it used TCP three-way…","fields":{"slug":"/2018-05-12--Three-way-handshake-in-TCP-&-ACK-and-SYN-flood-attack/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"May 12, 2018","title":"Three-way handshake in TCP & ACK and SYN flood attack","tab":"post"}}},{"node":{"excerpt":"Base64 Sources lifewire mozilla oracle base64 helper Definition “Base64 is a group of similar binary-to-text encoding schemes that represent…","fields":{"slug":"/2018-05-11--Base64,-Unicode,-ASCII,-URL/","readingTime":{"text":"7 min read"}},"frontmatter":{"date":"May 11, 2018","title":"Base64, Unicode, ASCII, URL","tab":"post"}}},{"node":{"excerpt":"Sources superuser askubuntu Due to the way TCP/IP works, connections can not be closed immediately. Packets may arrive out of order or be…","fields":{"slug":"/2018-05-06--Listening,-Established,-Close_wait-and-Time_wait-in-netstat/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"May 06, 2018","title":"Listening, Established, Close_wait and Time_wait in netstat","tab":"post"}}},{"node":{"excerpt":"Helpful link asecurity Windows (.bat) First, download and add to path: handle pstools autoruns listdlls uptime ntlast And run this batch…","fields":{"slug":"/2018-05-06--Basic-system-checkups-for-windows/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"May 06, 2018","title":"Basic system status checkups for windows","tab":"post"}}},{"node":{"excerpt":"Problem Well, obviously there will be a DEF CON@COMROKFLT about three weeks later. This one will be particular; it is a joint DEF CON…","fields":{"slug":"/2018-05-05--preparing-for-DEF-CON@COMROKFLT-2018/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"May 05, 2018","title":"preparing for DEF CON@COMROKFLT 2018","tab":"post"}}},{"node":{"excerpt":"Sources 1&1 Stackoverflow post CSS-Tricks Google buildwebsite4u Facebook’s open graph protocol gaijin, a metatag generator List of useful…","fields":{"slug":"/2018-04-29--Meta-tags/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"April 29, 2018","title":"Meta tags","tab":"post"}}},{"node":{"excerpt":"Before starting to read this People change. And so do I. This is a story of how I, as a developer and social being, changed over a course of…","fields":{"slug":"/2018-04-20--My-story-(1):-changes-that-occurred-from-high-school-to-the-Navy/","readingTime":{"text":"27 min read"}},"frontmatter":{"date":"April 20, 2018","title":"My story (1): changes that occurred from high school to the Navy","tab":"post"}}},{"node":{"excerpt":"Using is confusing The first confusion Avoiding using Or, using The method allows you to point to the object in the first argument…","fields":{"slug":"/2018-04-09--This-&-Object-prototypes-(1):-this-or-That/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"April 09, 2018","title":"This & Object prototypes (1): this or That?","tab":"post"}}},{"node":{"excerpt":"Dynamic scope “Dynamic scope seems to imply, and for good reason, that there’s a model whereby scope can be determined dynamically at…","fields":{"slug":"/2018-04-08--Scope-and-closure-(6):-Dynamic-scope,-Polyfilling-Block-Scope,-and-Lexical-this/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"April 08, 2018","title":"Scope and closure (6): Dynamic scope, Polyfilling Block Scope, and Lexical-this","tab":"post"}}},{"node":{"excerpt":"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19","fields":{"slug":"/2018-04-08--Presentation-design:-Marketing-project-on-Spotify/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"April 08, 2018","title":"Presentation design: Marketing project on Spotify","tab":"post"}}},{"node":{"excerpt":"GOOP does not have a logo yet. I have to make one. Before doing anything else, I have to know about Jeff’s business clearly. Otherwise, I…","fields":{"slug":"/2018-04-08--GOOP-logs/","readingTime":{"text":"15 min read"}},"frontmatter":{"date":"April 08, 2018","title":"GOOP logs","tab":"post"}}},{"node":{"excerpt":"Getting the hang of it function has a closure over the scope of the function . Nothing really special is going on here. And here are some…","fields":{"slug":"/2018-04-07--Scope-and-closure-(5):-modules/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"April 07, 2018","title":"Scope and closure (5): modules","tab":"post"}}},{"node":{"excerpt":"first\nsecond\nthird\nfourth","fields":{"slug":"/2018-04-07--Error-page-and-no-search-result-page-for-Grafolio-mobile-app/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"April 07, 2018","title":"Error page and no search result page for Grafolio mobile app","tab":"post"}}},{"node":{"excerpt":"You know, you sometimes compress files. And today I needed to send a relatively big folder to my friend so I just compressed it. This folder…","fields":{"slug":"/2018-04-07--How-does-a-file-get-compressed/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"April 07, 2018","title":"How does a file get compressed?","tab":"post"}}},{"node":{"excerpt":"Definition of closure Closure is when a function is able to remember and access its lexical scope even when that function is executing…","fields":{"slug":"/2018-03-31--Scope-and-closure-(4):-closure/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"March 31, 2018","title":"Scope and closure (4): closure","tab":"post"}}},{"node":{"excerpt":"FYI Notes made with You don’t know JS. Important points on hoisting Hoisting is just basic. Just remember hoisting happens per scope and…","fields":{"slug":"/2018-03-24--Scope-and-closure-(3):-hoisting/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"March 24, 2018","title":"Scope and closure (3): hoisting","tab":"post"}}},{"node":{"excerpt":"FYI Notes made with You don’t know JS. Writing good codes Function scope It encourages the idea that all variables belong to the function…","fields":{"slug":"/2018-03-24--Scope-and-closure-(2):-writing-good-codes/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"March 24, 2018","title":"Scope and closure (2): writing good codes","tab":"post"}}},{"node":{"excerpt":"FYI Notes made with You don’t know JS. Simplified steps in compilation Tokenizing: breaking the code into single parts, like to and and…","fields":{"slug":"/2018-03-24--Scope-and-closure-(1):-lexical-scope/","readingTime":{"text":"5 min read"}},"frontmatter":{"date":"March 24, 2018","title":"Scope and closure (1): lexical Scope","tab":"post"}}},{"node":{"excerpt":"How to revert git revert will create a new commit that’s the opposite (or inverse) of the given SHA. If the old commit is “matter”, the new…","fields":{"slug":"/2018-03-21--Using-git/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"March 21, 2018","title":"Using git","tab":"post"}}},{"node":{"excerpt":"Cookies An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to the user’s web browser. The browser may…","fields":{"slug":"/2017-12-31--Difference-between-cookies-and-sessions/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"December 31, 2017","title":"Difference between cookies and sessions","tab":"post"}}},{"node":{"excerpt":"What is VPN and how does it work Virtual Private Network. Microsoft’s Explanation Virtual private networks (VPNs) are point-to-point…","fields":{"slug":"/2017-10-06--VPN/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"October 06, 2017","title":"VPN","tab":"post"}}},{"node":{"excerpt":"How to capture screen The entire screen Partial screen How to make a symlink Create a symlink Create/update a symlink Using curl to get…","fields":{"slug":"/2017-10-05--Misc-useful-operations-on-kali-linux/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"October 05, 2017","title":"Misc useful operations on (kali) linux","tab":"post"}}},{"node":{"excerpt":"Varaiable declaration makes no error. Hoisting Hoisting works for . Scope\nFor , it’s a function scope. and Variable declaration makes an…","fields":{"slug":"/2017-10-01--Difference-among-const-let-and-var/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"October 01, 2017","title":"Difference among const, let, var","tab":"post"}}},{"node":{"excerpt":"Note Below content is just a excerpt from the websites. If you need to see in detail, don’t hesitate to go onto the original website. What…","fields":{"slug":"/2017-09-29--Using-tor/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"September 29, 2017","title":"Using tor","tab":"post"}}},{"node":{"excerpt":"Check bluetooth status And turn it on: This way bluetooth is going to work. will stop bluetooth. Bluetooth is not turned on by default on…","fields":{"slug":"/2017-09-28--Using-bluetooth-on-kali-linux/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 28, 2017","title":"Using bluetooth on kali linux","tab":"post"}}},{"node":{"excerpt":"On This would just work: This also works: To scout the nearby APs, you do: Off For my computer, I do not have to type . It just works. For…","fields":{"slug":"/2017-09-25--Turning-monitor-mode-on-and-off/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 25, 2017","title":"Turning monitor mode on and off","tab":"post"}}},{"node":{"excerpt":"This looks like a pretty awesome boilerplate! Well, I just looked at this repository and it just looks amazing. I cloned the repo to my…","fields":{"slug":"/2017-09-25--Configuring-and-using-vim/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"September 25, 2017","title":"Configuring and using vim","tab":"post"}}},{"node":{"excerpt":"How to (copied from linuxconfig) Checking environment variables type and it will show you everything. Where are they stored (copied from…","fields":{"slug":"/2017-09-23--Editing-environment-path-and-adding-custom-scripts/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"September 23, 2017","title":"Editing environment path and adding custom scripts","tab":"post"}}},{"node":{"excerpt":"What is macchanger Macchanger is a little program that helps you fake (sometimes called spoof) your mac address. Using ifconfig and…","fields":{"slug":"/2017-09-21--using-macchanger/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"September 21, 2017","title":"Using macchanger","tab":"post"}}},{"node":{"excerpt":"Many times you want to look for a process to kill it.\nHere’s how: the default pgrep command shows the PIDs of all matching processes. the -l…","fields":{"slug":"/2017-09-21--finding-a-process-with-a-name/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 21, 2017","title":"Finding a process with a name","tab":"post"}}},{"node":{"excerpt":"How to Dead simple. Open up the file /etc/network/interfaces. It will show you something like: and add at the end of the file, this line…","fields":{"slug":"/2017-09-21--changing-mac-address-semi-permanently/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 21, 2017","title":"Changing mac address semi permanently","tab":"post"}}},{"node":{"excerpt":"Scrolling in tmux Using tmux, you sometimes wanna scroll up and down the window just like you used to in the normal bash shell. It’s dead…","fields":{"slug":"/2017-09-21--Using-tmux/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 21, 2017","title":"Using tmux","tab":"post"}}},{"node":{"excerpt":"Guidance First, download utorrent server from the utorrent website. Then, follow this guide in detail:\nhttps://askubuntu.com/questions…","fields":{"slug":"/2017-09-20--installing-torrent-on-debian/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"September 20, 2017","title":"Installing torrent on debian based linux","tab":"post"}}}]}},"pageContext":{}},"staticQueryHashes":["3128451518","426816048"]} \ No newline at end of file +{"componentChunkName":"component---src-pages-index-js","path":"/","result":{"data":{"site":{"siteMetadata":{"title":"Joel's dev blog"}},"allMarkdownRemark":{"edges":[{"node":{"excerpt":"This is a brief summary of the anatomy of Starknet’s calldata and how to decode it. The Internet needs more information about how Starknet…","fields":{"slug":"/2024-01-26-decoding-calldata-of-transactions-on-starknet/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"January 26, 2024","title":"Decoding calldata on Starknet","tab":"post"}}},{"node":{"excerpt":"…","fields":{"slug":"/2024-01-01-내맘대로-적는-부의-추월차선-요약-및-리뷰/","readingTime":{"text":"37 min read"}},"frontmatter":{"date":"January 01, 2024","title":"내맘대로 적는 <부의 추월차선> 요약 및 리뷰 (15 / 100)","tab":"post"}}},{"node":{"excerpt":"…","fields":{"slug":"/2023-12-02-한국이-망할-수밖에-없는-이유-(1)-사라진-능력주의/","readingTime":{"text":"25 min read"}},"frontmatter":{"date":"December 02, 2023","title":"한국이 망할 수밖에 없는 이유 (1): 사라진 능력주의","tab":"post"}}},{"node":{"excerpt":"How a lending protocol works Functionalities Deposit Borrow Leverage Other usages Liquidate Repay Over-collateralization Utilization rate…","fields":{"slug":"/2023-10-26-technical-intro-to-defi-lending-protocols-with-zklend-codebase-as-an-example/","readingTime":{"text":"40 min read"}},"frontmatter":{"date":"October 26, 2023","title":"Technical intro to DeFi lending protocols with zkLend codebase as an example","tab":"post"}}},{"node":{"excerpt":"I noticed that my previous summaries are too comprehensive and inclusive, so from now on I will try to be more succinct. Here’s the summary…","fields":{"slug":"/2023-01-01-Life-3-0-being-human-in-the-age-of-artificial-intelligence/","readingTime":{"text":"18 min read"}},"frontmatter":{"date":"January 01, 2023","title":"Full summary of (14 / 100)","tab":"post"}}},{"node":{"excerpt":"This is my summary on Think Again by Adam Grant, who’s already a very renowned professor teaching at Wharton, and also a bestselling author…","fields":{"slug":"/2022-12-30-full-summary-and-reflection-on-Think-again-by-adam-grant/","readingTime":{"text":"15 min read"}},"frontmatter":{"date":"December 30, 2022","title":"Full summary of by Adam Grant (13 / 100)","tab":"post"}}},{"node":{"excerpt":"Preface This is my own curated list of tips and tricks about data structures and algorithms, in Python or TypeScript. Checklist If you are…","fields":{"slug":"/2022-09-15-data-structures-and-algorithms-cheatsheet/","readingTime":{"text":"67 min read"}},"frontmatter":{"date":"September 15, 2022","title":"All-in-one data structures and algorithms cheatsheet","tab":"post"}}},{"node":{"excerpt":"Why does Notion not have a built-in knowledge graph? So I made it myself The libraries: and Why I made How I made it The way forward…","fields":{"slug":"/2022-09-06-announcing-a-performant-knowledge-graph-tool-on-the-web/","readingTime":{"text":"11 min read"}},"frontmatter":{"date":"September 06, 2022","title":"Announcing a performant knowledge graph visualization tool for Notion and its underlying library","tab":"post"}}},{"node":{"excerpt":"This is a brief story of how I chose the best hotel for quarantine upon arrival in Hong Kong I could possibly choose using Github actions…","fields":{"slug":"/2022-06-17-Hong-kong-government-announces-the-list-of-quarantine-designated-hotels-for-the-8th-cycle-and-heres-how-i-became-the-first-person-in-the-world-to-know-it/","readingTime":{"text":"9 min read"}},"frontmatter":{"date":"June 17, 2022","title":"Hong Kong government announces the list of quarantine designated hotels for the 8th cycle, and here's how I became the first person in the world to know it.","tab":"post"}}},{"node":{"excerpt":"So I have enrolled in this computer architecture class for the current semester. The lecture is based on the RISC-V instruction set, which…","fields":{"slug":"/2022-05-13-RISC-V-Simulator-On-The-Web/","readingTime":{"text":"13 min read"}},"frontmatter":{"date":"May 14, 2022","title":"Practical Emscripten & Webassembly: Simple RISC-V Simulator on the web","tab":"post"}}},{"node":{"excerpt":"Rationale I have been trying to port some C++ project to Webassembly for the first time (I have worked on Assemblyscript and Rust projects…","fields":{"slug":"/2022-05-10-How-to-compile-C++-code-into-Webassembly-with-Emscripten-and-use-it-in-Webpack-+-Typescript-+-React-project/","readingTime":{"text":"7 min read"}},"frontmatter":{"date":"May 09, 2022","title":"How to compile C++ code into Webassembly with Emscripten + Docker and use it in Webpack + Typescript + React project","tab":"post"}}},{"node":{"excerpt":"TED…","fields":{"slug":"/2022-02-20-평균의-종말-완전-요약-및-감상평/","readingTime":{"text":"64 min read"}},"frontmatter":{"date":"February 20, 2022","title":"<평균의 종말> 완전 요약 및 감상평 (12 / 100)","tab":"post"}}},{"node":{"excerpt":"12 Rules for Life This post is a very detailed summary and reflection of Jordan Peterson’s book: 12 Rules for Life. Each short summary…","fields":{"slug":"/2022-02-17--detailed-summary-and-reflection-of-12-rules-for-life-a-truly-life-changing-piece/","readingTime":{"text":"75 min read"}},"frontmatter":{"date":"February 17, 2022","title":"12 Rules for Life: a detailed summary and reflection of a truly life-changing book (11 / 100)","tab":"post"}}},{"node":{"excerpt":"This is a personal reflection and review of the book Cosmos. Table of contents Cosmos Cosmos is (and has been) everything Human intellect…","fields":{"slug":"/2022-02-02-100BooksProject-(10):Cosmos/","readingTime":{"text":"11 min read"}},"frontmatter":{"date":"February 02, 2022","title":"100BooksProject: (10): Cosmos","tab":"post"}}},{"node":{"excerpt":"Find the shortest palindrome Inefficient algorithm: bruteforcing Longest Proper Prefix which is Suffix (LPS) The KMP algorithm Finding the…","fields":{"slug":"/2022-01-06--find-the-shortest-palindrome-an-intensive-review-of-kmp(knuth-morris-pratt)-algorithm/","readingTime":{"text":"19 min read"}},"frontmatter":{"date":"January 06, 2022","title":"Find the shortest palindrome: an intensive review of the KMP(Knuth–Morris–Pratt) algorithm","tab":"post"}}},{"node":{"excerpt":"Your Elasticsearch and Kibana instances are open, and that’s a real problem I have been doing some serious research on the public exposure…","fields":{"slug":"/2022-01-02--elasticpwn-how-to-find-and-collect-data-from-exposed-elasticsearch-and-kibana-instances/","readingTime":{"text":"6 min read"}},"frontmatter":{"date":"January 02, 2022","title":"elasticpwn: how to collect and analyse data from exposed Elasticsearch and Kibana instances","tab":"post"}}},{"node":{"excerpt":"I needed to figure out how to set up multiple monitors on my new Kali linux laptop, because it wouldn’t just work automatically like MacOS…","fields":{"slug":"/2021-09-17--set-up-multiple-monitors-on-optimus-laptop-running-kali-linux/","readingTime":{"text":"7 min read"}},"frontmatter":{"date":"September 17, 2021","title":"Set up multiple monitors on Optimus laptop running Kali linux","tab":"post"}}},{"node":{"excerpt":"This is a full guide to locally develop and deploy a backend app with a recently released container image feature for lambda on AWS…","fields":{"slug":"/2021-03-13--complete-end-to-end-guide-for-developing-dockerized-lambda-with-typescript-terraform-and-SAM-cli/","readingTime":{"text":"40 min read"}},"frontmatter":{"date":"March 13, 2021","title":"Complete end-to-end guide for developing dockerized lambda in Typescript, Terraform and SAM CLI","tab":"post"}}},{"node":{"excerpt":"As always, I’ve summarized the book so that anyone can get the main concept in about 20-30 mins. And then I wrote some reflections and…","fields":{"slug":"/2020-10-04-100BooksProject-(9):Atomic-habits/","readingTime":{"text":"30 min read"}},"frontmatter":{"date":"October 04, 2020","title":"100BooksProject: (9): Atomic habits","tab":"post"}}},{"node":{"excerpt":"회사에서 ‘사실상 PM’과 개발자 역할을 같이 수행하게 되면서, 경영에 자꾸 관심이 갔다. 내가 부족하다는 느낌이 많이 들었다. 그래서 무작정 Yes2…","fields":{"slug":"/2020-09-26-100BooksProject-(8):두려움-없는-조직-(The-fearless-organization)/","readingTime":{"text":"45 min read"}},"frontmatter":{"date":"September 26, 2020","title":"100BooksProject: (8): 두려움 없는 조직 (The fearless organization)","tab":"post"}}},{"node":{"excerpt":"Disclaimer: We will focus on itself in this article, rather than third-party libraries like , because it’s out of scope of the article. How…","fields":{"slug":"/2020-09-13--How-to-make-useSelector-not-a-disaster/","readingTime":{"text":"8 min read"}},"frontmatter":{"date":"September 13, 2020","title":"How to make useSelector not a disaster","tab":"post"}}},{"node":{"excerpt":"오랜만에 반디앤루니스에 가서 읽을 만한 책을 고르다가 베스트셀러를 차지하고 있는 돈의 속성을 사 봤다. 매번 그래왔듯이, 20~3…","fields":{"slug":"/2020-08-18--100BooksProject-(7):돈의-속성-(Properties-of-money)/","readingTime":{"text":"47 min read"}},"frontmatter":{"date":"August 18, 2020","title":"100BooksProject: (7): 돈의 속성 (Properties of money)","tab":"post"}}},{"node":{"excerpt":"I wrote a summary of each section and my reflections following it. typo alert: I typed so much that I can’t just review all… if you find a…","fields":{"slug":"/2020-08-17--100BooksProject-(6):How-to-win-friends-and-influence-people/","readingTime":{"text":"40 min read"}},"frontmatter":{"date":"August 17, 2020","title":"100BooksProject: (6): How to Win Friends & Influence People (인간관계론)","tab":"post"}}},{"node":{"excerpt":"Yes, Chrome’s performance tab is overwhelming First time you look into all the charts, you have no idea what means what. What does those…","fields":{"slug":"/2020-08-03--Learn-all-major functionalities-on-Chromes-Performance-tab-and-practice-it-with-a-simple-React-project/","readingTime":{"text":"14 min read"}},"frontmatter":{"date":"August 03, 2020","title":"Learn all major functionalities on Chrome's Performance tab and practice it with a simple React project","tab":"post"}}},{"node":{"excerpt":"초격차 경영에 관련된 책이라 일단은 무작정 사 보게 됐다. 일단 나중에 다시 책 볼 필요 없이 줄거리만 20~30분 안에 읽을 수 있는 정도로 요약했다. 그리고 나서 맨 마지막에 느낀 점과 나에게 적용할 수 있는 점을 적어보았다. 초격차 프롤로그…","fields":{"slug":"/2020-07-29--100BooksProject-(5):초격차(The-Great-Gap)/","readingTime":{"text":"51 min read"}},"frontmatter":{"date":"July 29, 2020","title":"100BooksProject: (5): 초격차 (The Great Gap)","tab":"post"}}},{"node":{"excerpt":"Summary Introduction Craving (열망) Success (성공) 실패 (Failure) Impressions after finishing this book The ways to survive in whichever contexts…","fields":{"slug":"/2020-07-05--100BooksProject-(4):Ego-is-the-enemy-(에고라는-적)/","readingTime":{"text":"19 min read"}},"frontmatter":{"date":"July 05, 2020","title":"100BooksProject: (4): Ego is the enemy (에고라는 적)","tab":"post"}}},{"node":{"excerpt":"I find this problem happening very often. And I never expected it to be so. I thought everyone in the world is able to express his/her…","fields":{"slug":"/2020-06-06--how-to-communicate-better/","readingTime":{"text":"5 min read"}},"frontmatter":{"date":"June 06, 2020","title":"How to communicate better","tab":"post"}}},{"node":{"excerpt":"…","fields":{"slug":"/2020-05-14--NSD-bulbaek-review/","readingTime":{"text":"14 min read"}},"frontmatter":{"date":"May 14, 2020","title":"[개발자도 리뷰 할 수 있다] 낙성대 문기사불백 리뷰","tab":"post"}}},{"node":{"excerpt":"Problem I was struggling at my company trying to write some tests for RxJS operations.\nMost of the network requests were managed by RxJS…","fields":{"slug":"/2020-01-24--Fundamental-yet-extensive-introduction-to-why-and-how-you-might-want-to-use-redux-observable-for-async-actions/","readingTime":{"text":"22 min read"}},"frontmatter":{"date":"January 24, 2020","title":"Extensive introduction to why and how you might want to use and test redux-observable","tab":"post"}}},{"node":{"excerpt":"Before we get started These are the keys usually used in combination with normal keys on Mac: Command (or Cmd) ⌘ Shift ⇧ (I will write as…","fields":{"slug":"/2020-01-08--The-shortcuts-I-like/","readingTime":{"text":"7 min read"}},"frontmatter":{"date":"January 08, 2020","title":"The shortcuts I like","tab":"post"}}},{"node":{"excerpt":"Back in the old days There was no alternative to javascript. It was just javascript. Javascript just happened to be with the web when it got…","fields":{"slug":"/2019-12-07--The-rise-of-low-level-prograamming-(Feat.-WebAssembly)/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"December 07, 2019","title":"The rise of low-level programming (feat. WebAssembly)","tab":"post"}}},{"node":{"excerpt":"The first encounter with the problem I was coding as usual. And I faced an odd encounter with how works. Here goes the code to give an…","fields":{"slug":"/2019-10-12--[...].forEach(sayHello)-does-not-always-say-hello/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"October 12, 2019","title":"[...].forEach(saveFromZombies) does not always save people","tab":"post"}}},{"node":{"excerpt":"I believed React is smart enough to know this… but aye? We had a bunch of large component trees in our company’s web application.\nAnd I saw…","fields":{"slug":"/2019-10-02--Making-stupid-react-smart-in-re-rendering/","readingTime":{"text":"12 min read"}},"frontmatter":{"date":"October 02, 2019","title":"Making stupid react smart in re-rendering","tab":"post"}}},{"node":{"excerpt":"Must must must watch! I got so much great insight from this video in Google IO 2019 detailing latest javascript specs. Improvements from the…","fields":{"slug":"/2019-09-10--Latest-javascript-specs/","readingTime":{"text":"10 min read"}},"frontmatter":{"date":"September 10, 2019","title":"New javascript specifications in 2019 (What's new in Javascript - Google I/O '19)","tab":"post"}}},{"node":{"excerpt":"What am I going to do? So I have my university’s timetable data in excel: I want to read this data using exceljs + node + typescript, and…","fields":{"slug":"/2019-08-17--Using-docker-for-mongodb/","readingTime":{"text":"14 min read"}},"frontmatter":{"date":"August 17, 2019","title":"Using docker and docker-compose to read data from excel to put it into mongodb","tab":"post"}}},{"node":{"excerpt":"1 2 3 4 1 면접이 끝나고 책 하나를 마음 놓고 읽었다. 죽음에 관한.. 책. 폴 칼라니티란 사람은 천재. 스탠포드, 캐임브리지, 예일대에서 문학, 생물학, 의예과 학위를 섭렵한 사람. 근데 36세에 암을 선고받고 살아가게 된다. 선고받은지…","fields":{"slug":"/2019-08-03--100BooksProject-(3):When-the-breath-becomes-air-(숨결이-바람-될-때)/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"August 03, 2019","title":"100BooksProject: (3): When the breath becomes air (숨결이 바람 될 때)","tab":"post"}}},{"node":{"excerpt":"Rationale I was creating an function: Time complexity For now, forget about the condition in the while loop: .\nYou know that you are going…","fields":{"slug":"/2019-05-05--Memoization-in-python-using-a-decorator:-getting-a-prime-number/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"May 05, 2019","title":"Memoization in python using a decorator: getting a prime number","tab":"post"}}},{"node":{"excerpt":"What I’m gonna build I’m gonna build a simple crawler that will send HTTP request to zigbang.com’s api server to receive data on estates…","fields":{"slug":"/2019-03-17--deploying-a-serverless-crawler-with-python-+-lambda+dynamoDB/","readingTime":{"text":"5 min read"}},"frontmatter":{"date":"March 18, 2019","title":"Deploying a serverless crawler with python + lambda","tab":"post"}}},{"node":{"excerpt":"The problem Sometimes you cannot easily change the version of Ubuntu and python if you are running them on cloud. I am using cloud9, as you…","fields":{"slug":"/2019-03-13--Updating-python3-on-an-outdated-ubuntu/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"March 13, 2019","title":"Updating python 3 on an outdated Ubuntu","tab":"post"}}},{"node":{"excerpt":"Encountering the book Sudden encounter over this book just walking by a random bookshelf in the library led me to spend the whole afternoon…","fields":{"slug":"/2019-03-13--100BooksProject-(2):Crossroads-(갈림길)/","readingTime":{"text":"23 min read"}},"frontmatter":{"date":"March 13, 2019","title":"100 Books Project (2): Crossroads (갈림길)","tab":"post"}}},{"node":{"excerpt":"Reflections Again, another big encouragement from a book. Things I got to know: Success is not everything. Things after success are…","fields":{"slug":"/2019-03-09--100BooksProject-(1):Remember-Who-You-Are:-Life-Stories-That-Inspire-the-Heart-and-Mind/","readingTime":{"text":"6 min read"}},"frontmatter":{"date":"March 09, 2019","title":"100 Books Project (1): Reflection and reading notes on Remember Who You Are: Life Stories That Inspire the Heart and Mind","tab":"post"}}},{"node":{"excerpt":"Hacking-notes Rationale As a programmer, I thought it’d be nice to know the basics of hacking for security purposes. For now, we will focus…","fields":{"slug":"/2018-11-02--random-hacking-notes/","readingTime":{"text":"15 min read"}},"frontmatter":{"date":"November 02, 2018","title":"random hacking notes","tab":"post"}}},{"node":{"excerpt":"No time Really, I’ve got no time to learn Vue. I need to get basic concepts in the shortest length of time. Vue Cli 3.0 We are going to use…","fields":{"slug":"/2018-10-19--Vue/","readingTime":{"text":"10 min read"}},"frontmatter":{"date":"October 19, 2018","title":"Vue","tab":"post"}}},{"node":{"excerpt":"Async and await See javascript.info Mozilla Basics Ok. This is not ES6. Its ES8 (ECMAScript 2017) syntax. The async function declaration…","fields":{"slug":"/2018-09-05--Async-and-await/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"September 05, 2018","title":"Async, await, promise","tab":"post"}}},{"node":{"excerpt":"1. Default binding without the strict mode in effect, the global object is eligible for the default binding: however, with the strict mode…","fields":{"slug":"/2018-06-30--This-&-Object-prototypes-(2):-this-All-Makes-Sense-Now!/","readingTime":{"text":"6 min read"}},"frontmatter":{"date":"June 30, 2018","title":"This & Object prototypes (2): this All Makes Sense Now!","tab":"post"}}},{"node":{"excerpt":"Types and objects Types in JS string number boolean null undefined object Built-in Objects String Number Boolean Object Function Array Date…","fields":{"slug":"/2018-06-30--This-&-Object-prototypes-(3):-Objects/","readingTime":{"text":"9 min read"}},"frontmatter":{"date":"June 30, 2018","title":"This & Object prototypes (3): Objects","tab":"post"}}},{"node":{"excerpt":"detecting security incidents event logs by SANS Regular Expressions to look for Keys to Check Domain controller Monitoring Active…","fields":{"slug":"/2018-05-29--Some-hacking-notes/","readingTime":{"text":"9 min read"}},"frontmatter":{"date":"May 29, 2018","title":"Some hacking notes","tab":"post"}}},{"node":{"excerpt":"Sources CSO microsoft gist Corrie Erk Process explorer Submit to VirusTotal to check hash Process Explorer -> Options -> VirusTotal.com…","fields":{"slug":"/2018-05-19--Using-foresic-tools-(1):-process-explorer,-process-monitor,-and-autoruns/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"May 19, 2018","title":"Using foresic tools (1): process explorer, process monitor, and autoruns","tab":"post"}}},{"node":{"excerpt":"Sources Lifewire SSLStore Hash A hashing algorithm is a mathematical function that condenses data to a fixed size. It is easier for the…","fields":{"slug":"/2018-05-12--MD5-and-SHA/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"May 12, 2018","title":"MD5 and SHA","tab":"post"}}},{"node":{"excerpt":"Sources mazebolt microsoft geeksforgeeks ddosguard flowguard wikipedia Three-way handshake: how does it work When is it used TCP three-way…","fields":{"slug":"/2018-05-12--Three-way-handshake-in-TCP-&-ACK-and-SYN-flood-attack/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"May 12, 2018","title":"Three-way handshake in TCP & ACK and SYN flood attack","tab":"post"}}},{"node":{"excerpt":"Base64 Sources lifewire mozilla oracle base64 helper Definition “Base64 is a group of similar binary-to-text encoding schemes that represent…","fields":{"slug":"/2018-05-11--Base64,-Unicode,-ASCII,-URL/","readingTime":{"text":"7 min read"}},"frontmatter":{"date":"May 11, 2018","title":"Base64, Unicode, ASCII, URL","tab":"post"}}},{"node":{"excerpt":"Sources superuser askubuntu Due to the way TCP/IP works, connections can not be closed immediately. Packets may arrive out of order or be…","fields":{"slug":"/2018-05-06--Listening,-Established,-Close_wait-and-Time_wait-in-netstat/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"May 06, 2018","title":"Listening, Established, Close_wait and Time_wait in netstat","tab":"post"}}},{"node":{"excerpt":"Helpful link asecurity Windows (.bat) First, download and add to path: handle pstools autoruns listdlls uptime ntlast And run this batch…","fields":{"slug":"/2018-05-06--Basic-system-checkups-for-windows/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"May 06, 2018","title":"Basic system status checkups for windows","tab":"post"}}},{"node":{"excerpt":"Problem Well, obviously there will be a DEF CON@COMROKFLT about three weeks later. This one will be particular; it is a joint DEF CON…","fields":{"slug":"/2018-05-05--preparing-for-DEF-CON@COMROKFLT-2018/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"May 05, 2018","title":"preparing for DEF CON@COMROKFLT 2018","tab":"post"}}},{"node":{"excerpt":"Sources 1&1 Stackoverflow post CSS-Tricks Google buildwebsite4u Facebook’s open graph protocol gaijin, a metatag generator List of useful…","fields":{"slug":"/2018-04-29--Meta-tags/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"April 29, 2018","title":"Meta tags","tab":"post"}}},{"node":{"excerpt":"Before starting to read this People change. And so do I. This is a story of how I, as a developer and social being, changed over a course of…","fields":{"slug":"/2018-04-20--My-story-(1):-changes-that-occurred-from-high-school-to-the-Navy/","readingTime":{"text":"27 min read"}},"frontmatter":{"date":"April 20, 2018","title":"My story (1): changes that occurred from high school to the Navy","tab":"post"}}},{"node":{"excerpt":"Using is confusing The first confusion Avoiding using Or, using The method allows you to point to the object in the first argument…","fields":{"slug":"/2018-04-09--This-&-Object-prototypes-(1):-this-or-That/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"April 09, 2018","title":"This & Object prototypes (1): this or That?","tab":"post"}}},{"node":{"excerpt":"Dynamic scope “Dynamic scope seems to imply, and for good reason, that there’s a model whereby scope can be determined dynamically at…","fields":{"slug":"/2018-04-08--Scope-and-closure-(6):-Dynamic-scope,-Polyfilling-Block-Scope,-and-Lexical-this/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"April 08, 2018","title":"Scope and closure (6): Dynamic scope, Polyfilling Block Scope, and Lexical-this","tab":"post"}}},{"node":{"excerpt":"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19","fields":{"slug":"/2018-04-08--Presentation-design:-Marketing-project-on-Spotify/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"April 08, 2018","title":"Presentation design: Marketing project on Spotify","tab":"post"}}},{"node":{"excerpt":"GOOP does not have a logo yet. I have to make one. Before doing anything else, I have to know about Jeff’s business clearly. Otherwise, I…","fields":{"slug":"/2018-04-08--GOOP-logs/","readingTime":{"text":"15 min read"}},"frontmatter":{"date":"April 08, 2018","title":"GOOP logs","tab":"post"}}},{"node":{"excerpt":"Getting the hang of it function has a closure over the scope of the function . Nothing really special is going on here. And here are some…","fields":{"slug":"/2018-04-07--Scope-and-closure-(5):-modules/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"April 07, 2018","title":"Scope and closure (5): modules","tab":"post"}}},{"node":{"excerpt":"first\nsecond\nthird\nfourth","fields":{"slug":"/2018-04-07--Error-page-and-no-search-result-page-for-Grafolio-mobile-app/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"April 07, 2018","title":"Error page and no search result page for Grafolio mobile app","tab":"post"}}},{"node":{"excerpt":"You know, you sometimes compress files. And today I needed to send a relatively big folder to my friend so I just compressed it. This folder…","fields":{"slug":"/2018-04-07--How-does-a-file-get-compressed/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"April 07, 2018","title":"How does a file get compressed?","tab":"post"}}},{"node":{"excerpt":"Definition of closure Closure is when a function is able to remember and access its lexical scope even when that function is executing…","fields":{"slug":"/2018-03-31--Scope-and-closure-(4):-closure/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"March 31, 2018","title":"Scope and closure (4): closure","tab":"post"}}},{"node":{"excerpt":"FYI Notes made with You don’t know JS. Important points on hoisting Hoisting is just basic. Just remember hoisting happens per scope and…","fields":{"slug":"/2018-03-24--Scope-and-closure-(3):-hoisting/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"March 24, 2018","title":"Scope and closure (3): hoisting","tab":"post"}}},{"node":{"excerpt":"FYI Notes made with You don’t know JS. Writing good codes Function scope It encourages the idea that all variables belong to the function…","fields":{"slug":"/2018-03-24--Scope-and-closure-(2):-writing-good-codes/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"March 24, 2018","title":"Scope and closure (2): writing good codes","tab":"post"}}},{"node":{"excerpt":"FYI Notes made with You don’t know JS. Simplified steps in compilation Tokenizing: breaking the code into single parts, like to and and…","fields":{"slug":"/2018-03-24--Scope-and-closure-(1):-lexical-scope/","readingTime":{"text":"5 min read"}},"frontmatter":{"date":"March 24, 2018","title":"Scope and closure (1): lexical Scope","tab":"post"}}},{"node":{"excerpt":"How to revert git revert will create a new commit that’s the opposite (or inverse) of the given SHA. If the old commit is “matter”, the new…","fields":{"slug":"/2018-03-21--Using-git/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"March 21, 2018","title":"Using git","tab":"post"}}},{"node":{"excerpt":"Cookies An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to the user’s web browser. The browser may…","fields":{"slug":"/2017-12-31--Difference-between-cookies-and-sessions/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"December 31, 2017","title":"Difference between cookies and sessions","tab":"post"}}},{"node":{"excerpt":"What is VPN and how does it work Virtual Private Network. Microsoft’s Explanation Virtual private networks (VPNs) are point-to-point…","fields":{"slug":"/2017-10-06--VPN/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"October 06, 2017","title":"VPN","tab":"post"}}},{"node":{"excerpt":"How to capture screen The entire screen Partial screen How to make a symlink Create a symlink Create/update a symlink Using curl to get…","fields":{"slug":"/2017-10-05--Misc-useful-operations-on-kali-linux/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"October 05, 2017","title":"Misc useful operations on (kali) linux","tab":"post"}}},{"node":{"excerpt":"Varaiable declaration makes no error. Hoisting Hoisting works for . Scope\nFor , it’s a function scope. and Variable declaration makes an…","fields":{"slug":"/2017-10-01--Difference-among-const-let-and-var/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"October 01, 2017","title":"Difference among const, let, var","tab":"post"}}},{"node":{"excerpt":"Note Below content is just a excerpt from the websites. If you need to see in detail, don’t hesitate to go onto the original website. What…","fields":{"slug":"/2017-09-29--Using-tor/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"September 29, 2017","title":"Using tor","tab":"post"}}},{"node":{"excerpt":"Check bluetooth status And turn it on: This way bluetooth is going to work. will stop bluetooth. Bluetooth is not turned on by default on…","fields":{"slug":"/2017-09-28--Using-bluetooth-on-kali-linux/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 28, 2017","title":"Using bluetooth on kali linux","tab":"post"}}},{"node":{"excerpt":"On This would just work: This also works: To scout the nearby APs, you do: Off For my computer, I do not have to type . It just works. For…","fields":{"slug":"/2017-09-25--Turning-monitor-mode-on-and-off/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 25, 2017","title":"Turning monitor mode on and off","tab":"post"}}},{"node":{"excerpt":"This looks like a pretty awesome boilerplate! Well, I just looked at this repository and it just looks amazing. I cloned the repo to my…","fields":{"slug":"/2017-09-25--Configuring-and-using-vim/","readingTime":{"text":"4 min read"}},"frontmatter":{"date":"September 25, 2017","title":"Configuring and using vim","tab":"post"}}},{"node":{"excerpt":"How to (copied from linuxconfig) Checking environment variables type and it will show you everything. Where are they stored (copied from…","fields":{"slug":"/2017-09-23--Editing-environment-path-and-adding-custom-scripts/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"September 23, 2017","title":"Editing environment path and adding custom scripts","tab":"post"}}},{"node":{"excerpt":"What is macchanger Macchanger is a little program that helps you fake (sometimes called spoof) your mac address. Using ifconfig and…","fields":{"slug":"/2017-09-21--using-macchanger/","readingTime":{"text":"3 min read"}},"frontmatter":{"date":"September 21, 2017","title":"Using macchanger","tab":"post"}}},{"node":{"excerpt":"Many times you want to look for a process to kill it.\nHere’s how: the default pgrep command shows the PIDs of all matching processes. the -l…","fields":{"slug":"/2017-09-21--finding-a-process-with-a-name/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 21, 2017","title":"Finding a process with a name","tab":"post"}}},{"node":{"excerpt":"How to Dead simple. Open up the file /etc/network/interfaces. It will show you something like: and add at the end of the file, this line…","fields":{"slug":"/2017-09-21--changing-mac-address-semi-permanently/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 21, 2017","title":"Changing mac address semi permanently","tab":"post"}}},{"node":{"excerpt":"Scrolling in tmux Using tmux, you sometimes wanna scroll up and down the window just like you used to in the normal bash shell. It’s dead…","fields":{"slug":"/2017-09-21--Using-tmux/","readingTime":{"text":"1 min read"}},"frontmatter":{"date":"September 21, 2017","title":"Using tmux","tab":"post"}}},{"node":{"excerpt":"Guidance First, download utorrent server from the utorrent website. Then, follow this guide in detail:\nhttps://askubuntu.com/questions…","fields":{"slug":"/2017-09-20--installing-torrent-on-debian/","readingTime":{"text":"2 min read"}},"frontmatter":{"date":"September 20, 2017","title":"Installing torrent on debian based linux","tab":"post"}}}]}},"pageContext":{}},"staticQueryHashes":["3128451518","426816048"]} \ No newline at end of file