<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nguyen Nguyen</title>
    <description>The latest articles on DEV Community by Nguyen Nguyen (@nguyen-nguyen-vn).</description>
    <link>https://dev.to/nguyen-nguyen-vn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4033016%2F37bbc952-8d8b-4a39-8f56-ae6dcb86f4f9.jpg</url>
      <title>DEV Community: Nguyen Nguyen</title>
      <link>https://dev.to/nguyen-nguyen-vn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nguyen-nguyen-vn"/>
    <language>en</language>
    <item>
      <title>Designing a High-Performance Cryptocurrency Exchange</title>
      <dc:creator>Nguyen Nguyen</dc:creator>
      <pubDate>Fri, 17 Jul 2026 22:46:43 +0000</pubDate>
      <link>https://dev.to/nguyen-nguyen-vn/designing-a-high-performance-cryptocurrency-exchange-2ga2</link>
      <guid>https://dev.to/nguyen-nguyen-vn/designing-a-high-performance-cryptocurrency-exchange-2ga2</guid>
      <description>&lt;p&gt;How a modern exchange processes millions of orders with low latency while keeping the architecture scalable.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Building a cryptocurrency exchange is very different from building a typical web application.&lt;/p&gt;

&lt;p&gt;Users expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orders to execute within milliseconds.&lt;/li&gt;
&lt;li&gt;Zero duplicated orders.&lt;/li&gt;
&lt;li&gt;Strict ordering of events.&lt;/li&gt;
&lt;li&gt;High throughput during market volatility.&lt;/li&gt;
&lt;li&gt;Real-time updates for millions of connected users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike traditional CRUD systems, the matching engine becomes the heart of the platform. Everything else exists to support it.&lt;/p&gt;

&lt;p&gt;This article walks through the architecture below and explains why each component exists and which technologies are commonly used in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;The architecture follows one important principle:&lt;/p&gt;

&lt;p&gt;Only the Matching Engine is stateful. Everything else should be stateless whenever possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faf0sm6d8ccm8pek55lm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faf0sm6d8ccm8pek55lm7.png" alt="Trading System" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  a. Load Balancer
&lt;/h3&gt;

&lt;p&gt;The load balancer is the public entry point of the exchange.&lt;/p&gt;

&lt;p&gt;Responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distribute requests across multiple API servers&lt;/li&gt;
&lt;li&gt;Handle SSL termination&lt;/li&gt;
&lt;li&gt;Protect against server failures&lt;/li&gt;
&lt;li&gt;Support horizontal scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NGINX&lt;/li&gt;
&lt;li&gt;HAProxy&lt;/li&gt;
&lt;li&gt;AWS ALB&lt;/li&gt;
&lt;li&gt;Envoy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Trading traffic is unpredictable.&lt;/p&gt;

&lt;p&gt;During major market events, request volume can increase by hundreds of times within seconds.&lt;/p&gt;

&lt;p&gt;Instead of scaling one giant API server, we simply add more REST API instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  b. REST API Layer
&lt;/h3&gt;

&lt;p&gt;The REST API layer handles everything related to user interaction.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Place Order&lt;/li&gt;
&lt;li&gt;Cancel Order&lt;/li&gt;
&lt;li&gt;Query Balances&lt;/li&gt;
&lt;li&gt;Open Orders&lt;/li&gt;
&lt;li&gt;Trade History&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layer should remain completely stateless.&lt;/p&gt;

&lt;p&gt;Typical stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;Rust&lt;/li&gt;
&lt;li&gt;Node.js (NestJS)&lt;/li&gt;
&lt;li&gt;Spring Boot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT verification&lt;/li&gt;
&lt;li&gt;API validation&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Risk checks&lt;/li&gt;
&lt;li&gt;Forward orders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice something important:&lt;/p&gt;

&lt;p&gt;The REST API never performs order matching.&lt;/p&gt;

&lt;p&gt;Its only job is validating requests and forwarding them.&lt;/p&gt;

&lt;p&gt;Because it is stateless, we can run hundreds of API servers behind a load balancer.&lt;/p&gt;

&lt;h3&gt;
  
  
  c. Communication with the Matching Layer
&lt;/h3&gt;

&lt;p&gt;After validation, orders are forwarded to the matching layer.&lt;/p&gt;

&lt;p&gt;Several communication protocols are commonly used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1 — gRPC&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Pros&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to develop&lt;/li&gt;
&lt;li&gt;Strong typing&lt;/li&gt;
&lt;li&gt;Protobuf serialization&lt;/li&gt;
&lt;li&gt;Excellent developer experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Cons&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher latency&lt;/li&gt;
&lt;li&gt;HTTP/2 overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal microservices&lt;/li&gt;
&lt;li&gt;Moderate latency systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option 2 — Raw TCP Binary Protocol&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many exchanges implement their own binary protocol.&lt;/p&gt;

&lt;p&gt;Instead of JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"BTC-USDT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;105000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They send something similar to:&lt;/p&gt;

&lt;p&gt;[OrderID][Price][Quantity][Side]&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely small packets&lt;/li&gt;
&lt;li&gt;Minimal CPU usage&lt;/li&gt;
&lt;li&gt;Almost zero serialization overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach is common in high-frequency trading systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3 — Aeron&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many modern exchanges and financial systems use Aeron.&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very low latency&lt;/li&gt;
&lt;li&gt;Lock-free design&lt;/li&gt;
&lt;li&gt;Efficient memory usage&lt;/li&gt;
&lt;li&gt;Reliable messaging&lt;/li&gt;
&lt;li&gt;Millions of messages per second&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Companies using Aeron include financial trading platforms and low-latency infrastructures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9zzbe6m5qf0g514yqcii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9zzbe6m5qf0g514yqcii.png" alt="Communication options" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Message Broker (like Kafka) or HTTP API take millisecond for delivering orders, not a good choice in low latency system like Trading System&lt;/p&gt;

&lt;h3&gt;
  
  
  d. Order Router
&lt;/h3&gt;

&lt;p&gt;The most important key: &lt;/p&gt;

&lt;p&gt;Multiple matching engines but only every trading pair owns exactly one matching engine.&lt;/p&gt;

&lt;p&gt;Reason: Because order matching requires strict sequencing.&lt;br&gt;
A single writer guarantees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deterministic execution&lt;/li&gt;
&lt;li&gt;price-time priority&lt;/li&gt;
&lt;li&gt;no race conditions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  e. Event Bus Consumers
&lt;/h3&gt;

&lt;p&gt;Multiple services subscribe to exchange events.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trade History (store orders to Database)&lt;/li&gt;
&lt;li&gt;WebSocket (notifications)&lt;/li&gt;
&lt;li&gt;Settle Balance&lt;/li&gt;
&lt;li&gt;Risk/Fraud Check&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Event-Driven Architecture?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce latency, matching engine no need to wait anything from these tasks&lt;/li&gt;
&lt;li&gt;Loose coupling&lt;/li&gt;
&lt;li&gt;Horizontal scalability&lt;/li&gt;
&lt;li&gt;Fault isolation&lt;/li&gt;
&lt;li&gt;Easy to add new services&lt;/li&gt;
&lt;li&gt;Replay historical events&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2. Matching Engine
&lt;/h2&gt;

&lt;p&gt;The Matching Engine is the most latency-sensitive component in the entire exchange.&lt;/p&gt;

&lt;p&gt;Unlike a typical backend service that spends most of its time waiting for databases or external APIs, the Matching Engine keeps everything in memory and focuses on executing orders with deterministic, microsecond-level latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Everything Lives in Memory ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Accessing memory takes tens of nanoseconds.&lt;/p&gt;

&lt;p&gt;Reading from PostgreSQL may take hundreds of microseconds.&lt;/p&gt;

&lt;p&gt;Reading from disk may take milliseconds.&lt;/p&gt;

&lt;p&gt;Since every incoming order must be processed immediately, the order book cannot live inside a database.&lt;/p&gt;

&lt;p&gt;Instead, the database is only used for persistence after matching completes.&lt;/p&gt;
&lt;h3&gt;
  
  
  a. Core Data Structures
&lt;/h3&gt;

&lt;p&gt;The matching engine typically maintains two order books: Bid and Ask&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price Tree&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A balanced tree stores all price levels.&lt;/p&gt;

&lt;p&gt;Common implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Red-Black Tree&lt;/li&gt;
&lt;li&gt;AVL Tree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why not HashMap?&lt;/p&gt;

&lt;p&gt;Because we constantly need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highest Bid&lt;/li&gt;
&lt;li&gt;Lowest Ask&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HashMap cannot answer those efficiently.&lt;/p&gt;

&lt;p&gt;Tree complexity:&lt;/p&gt;

&lt;p&gt;Operation           Complexity&lt;br&gt;
Insert Price Level  O(log n)&lt;br&gt;
Remove Price Level  O(log n)&lt;br&gt;
Find Best Bid           O(1)&lt;br&gt;
Find Best Ask           O(1)&lt;/p&gt;

&lt;p&gt;Most implementations keep pointers to:&lt;/p&gt;

&lt;p&gt;`highestBid&lt;/p&gt;

&lt;p&gt;lowestAsk`&lt;/p&gt;

&lt;p&gt;Therefore finding the best price becomes constant time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price Level&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each node inside the tree represents one price.&lt;br&gt;
Orders at the same price are not sorted again.&lt;/p&gt;

&lt;p&gt;They simply follow FIFO order following Price-Time Priority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order Object&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Side&lt;/span&gt; &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Quantity&lt;/span&gt; &lt;span class="n"&gt;remainingQty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;PriceLevel&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each order keeps a pointer back to its own price level.&lt;/p&gt;

&lt;p&gt;This makes cancellation extremely efficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order Index (HashMap)&lt;/strong&gt;&lt;br&gt;
Why?&lt;/p&gt;

&lt;p&gt;Imagine a cancel request arrives.&lt;/p&gt;

&lt;p&gt;Without a HashMap, we search price level from the tree, O(n)&lt;br&gt;
With HashMap, O(1)&lt;/p&gt;
&lt;h3&gt;
  
  
  b. Matching Algorithm
&lt;/h3&gt;

&lt;p&gt;Suppose the order book looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Bid

105 (5 BTC)

104 (3 BTC)

Ask

106 (4 BTC)

107 (2 BTC)

A new buy order arrives:

BUY

106

3 BTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine compares:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Buy Price &amp;gt;= Best Ask ?&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;So matching begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partial Fill&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
BUY

10 BTC @106

Best ask

4 BTC @106

The result:

BUY

Remaining

6 BTC

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first ask disappears.&lt;/p&gt;

&lt;p&gt;The remaining quantity continues matching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple Price Levels&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
BUY

10 BTC

Book

106

4 BTC

107

3 BTC

108

5 BTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
106

Filled

↓

107

Filled

↓

108
Partially Filled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine walks through price levels until:&lt;/p&gt;

&lt;p&gt;order completely filled&lt;br&gt;
no more executable prices&lt;/p&gt;
&lt;h3&gt;
  
  
  c. Inserting a New Order
&lt;/h3&gt;

&lt;p&gt;If the order cannot match:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BUY 105&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;while&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Best Ask =106&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Insert

↓

Price Tree

↓

Price Level

↓

FIFO Queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Complexity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Tree Insert

O(log n)

Linked List Append

O(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  d. Cancelling an Order
&lt;/h3&gt;

&lt;p&gt;Cancellation is surprisingly one of the most frequent operations.&lt;/p&gt;

&lt;p&gt;Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Cancel Request

↓

HashMap Lookup

↓

Order Pointer

↓

Remove From Linked List

↓

Remove From Tree If Empty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pseudo-code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;
&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="n"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;orderMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;erase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Complexity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
HashMap lookup:

O(1)

Linked list removal:

O(1)

Tree removal:

O(log n)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  e. Why Linked List Instead of Array?
&lt;/h3&gt;

&lt;p&gt;Imagine 50,000 orders exist at one price.&lt;/p&gt;

&lt;p&gt;Removing the first order:&lt;/p&gt;

&lt;p&gt;Array&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Shift 49,999 elements&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Linked List&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Reconnect two pointers&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Constant time.&lt;/p&gt;

&lt;h3&gt;
  
  
  f. Why &lt;strong&gt;Red-Black Tree&lt;/strong&gt; Instead of Heap?
&lt;/h3&gt;

&lt;p&gt;Many engineers first think about using a heap because we only care about the best bid and best ask.&lt;/p&gt;

&lt;p&gt;However, heaps are poorly suited for exchange order books.&lt;/p&gt;

&lt;p&gt;A heap excels at retrieving the minimum or maximum element, but it performs poorly when you need to:&lt;/p&gt;

&lt;p&gt;Remove an arbitrary price level.&lt;br&gt;
Iterate through consecutive price levels during large market orders.&lt;br&gt;
Delete an empty price level after the last order is canceled or filled.&lt;/p&gt;

&lt;p&gt;A balanced tree provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ordered traversal.&lt;/li&gt;
&lt;li&gt;Efficient insertion and deletion.&lt;/li&gt;
&lt;li&gt;Fast access to neighboring price levels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it a much better fit for a continuously changing limit order book.&lt;/p&gt;

&lt;h3&gt;
  
  
  g. Key Design Principles
&lt;/h3&gt;

&lt;p&gt;A production-grade matching engine achieves high performance by combining several simple but carefully chosen data structures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Balanced Trees (Red-Black Tree / AVL Tree): Maintain all price levels in sorted order and provide instant access to the best bid and ask.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FIFO Doubly Linked Lists: Preserve price-time priority and allow constant-time insertion and removal of orders at the same price.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HashMap:* Enables O(1) lookup for order cancellation and modification without scanning the order book.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In-Memory Processing: Keeps the critical execution path free from database latency, while persistence is handled asynchronously through an event log or message broker.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  h. Time Complexity Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Place Order&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Match Best Price&lt;/td&gt;
&lt;td&gt;O(1) per price level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancel Order&lt;/td&gt;
&lt;td&gt;O(1) + O(log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find Order&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find Best Bid&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find Best Ask&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Append Order at Same Price&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove Order from FIFO&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Practical Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  a. Handling many trading pairs
&lt;/h3&gt;

&lt;p&gt;A scalable exchange does not create one operating system process for every trading pair. Instead, it uses market partitioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each trading pair is owned by exactly one matching engine at any given time.&lt;/li&gt;
&lt;li&gt;Each engine can manage many independent order books.&lt;/li&gt;
&lt;li&gt;Hot markets may receive dedicated engines, while hundreds of low-volume markets share a single engine.&lt;/li&gt;
&lt;li&gt;The Order Router maintains a routing table (symbol → engine) so incoming orders are always forwarded to the correct owner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design strikes a balance between low latency, efficient resource utilization, and horizontal scalability, and is widely adopted by modern cryptocurrency and traditional financial exchanges.&lt;/p&gt;

&lt;h3&gt;
  
  
  b. Restoring OrderBook from crash
&lt;/h3&gt;

&lt;p&gt;A production-grade matching engine relies on several complementary techniques to recover safely after failures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In-memory&lt;/strong&gt; order books provide the lowest possible latency during normal operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Append-only event logs&lt;/strong&gt; record every state-changing operation in execution order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Periodic snapshots&lt;/strong&gt; dramatically reduce restart time by avoiding a full replay from genesis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-Ahead Logging&lt;/strong&gt; (WAL) ensures no accepted order is lost even if the process crashes unexpectedly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence numbers&lt;/strong&gt; guarantee deterministic replay and prevent inconsistent order books.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active-standby&lt;/strong&gt; replication minimizes downtime by keeping a synchronized backup engine ready to take over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these techniques allow an exchange to recover quickly while preserving the exact market state, ensuring that every open order, trade, and cancellation remains consistent even after unexpected failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  c. Preventing Double Spending: Account Locking
&lt;/h3&gt;

&lt;p&gt;A common architectural principle in modern exchanges is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reserved Balance and Available Balance&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account Service&lt;/strong&gt; owns all account balances and is the only component allowed to modify them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk Engine&lt;/strong&gt; validates whether an order is financially acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matching Engine&lt;/strong&gt; never checks balances; it only matches valid orders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settlement Service&lt;/strong&gt; updates final balances after trades are executed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By assigning a single writer to every account, exchanges eliminate race conditions without relying on expensive database locks or complex synchronization primitives. The result is a system that remains both correct and highly scalable under heavy trading workloads. &lt;/p&gt;

&lt;h2&gt;
  
  
  About me
&lt;/h2&gt;

&lt;p&gt;Nguyen Nguyen - Senior Blockchain Engineer&lt;br&gt;
Website: &lt;a href="https://portfolio-nguyen-nguyen-vn.vercel.app/" rel="noopener noreferrer"&gt;https://portfolio-nguyen-nguyen-vn.vercel.app/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>trading</category>
      <category>exchange</category>
    </item>
    <item>
      <title>Ethereum Smart Contract Storage</title>
      <dc:creator>Nguyen Nguyen</dc:creator>
      <pubDate>Fri, 17 Jul 2026 03:35:08 +0000</pubDate>
      <link>https://dev.to/nguyen-nguyen-vn/ethereum-smart-contract-storage-310l</link>
      <guid>https://dev.to/nguyen-nguyen-vn/ethereum-smart-contract-storage-310l</guid>
      <description>&lt;p&gt;Ethereum smart contracts use an uncommon storage model that often confuses new developers. In this post, I’ll describe that storage model and explain how the Solidity programming language makes use of it.&lt;/p&gt;

&lt;p&gt;Each smart contract running in the Ethereum Virtual Machine (EVM) maintains state in its own permanent storage. This storage can be thought of as a very large array, initially full of zeros. Each value in the array is 32-bytes wide, and there are 2256 such values. A smart contract can read from or write to a value at any location. That’s the extent of the storage interface.&lt;/p&gt;

&lt;p&gt;I encourage you to stick with the “astronomically large array” mental model, but be aware that this is not how storage is implemented on the physical computers that make up the Ethereum network. Storage is extremely sparsely populated, and there’s no need to store the zeros. A key/value store mapping 32-byte keys to 32-byte values will do the job nicely. An absent key is simply defined as mapping to the value zero.&lt;/p&gt;

&lt;p&gt;Because zeros don’t take up any space, storage can be reclaimed by setting a value to zero. This is incentivized in smart contracts with a gas refund when you change a value to zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Locating Fixed-Sized Values
&lt;/h2&gt;

&lt;p&gt;In this storage model, where do things actually go? For known variables with fixed sizes, it makes sense to just give them reserved locations in storage. The Solidity programming language does just that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract StorageTest {
    uint256 a;
    uint256[2] b;
    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frieaxrngj7oxlamq6d6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frieaxrngj7oxlamq6d6d.png" alt=" " width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;a is stored at slot 0. (Solidity’s term for a location within storage is a “slot.”)&lt;br&gt;
b is stored at slots 1, and 2 (one for each element of the array).&lt;br&gt;
c starts at slot 3 and consumes two slots, because the Entry struct stores two 32-byte values.&lt;/p&gt;

&lt;p&gt;These slots are determined at compile time, strictly based on the order in which the variables appear in the contract code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Locating Dynamically-Sized Values
&lt;/h2&gt;

&lt;p&gt;Using reserved slots works well for fixed-size state variables, but it doesn’t work for dynamically-sized arrays and mappings because there’s no way of knowing how many slots to reserve.&lt;/p&gt;

&lt;p&gt;If you’re thinking of computer RAM or hard drive as an analogy, you might expect that there’s an “allocation” step to find free space to use and then a “release” step to put that space back into the pool of available storage.&lt;/p&gt;

&lt;p&gt;This is unnecessary due to the astronomical scale of smart contract storage. There are 2^256 locations to choose from in storage, which is approximately the number of atoms in the known, observable universe. You could choose storage locations at random without ever experiencing a collision. The locations you chose would be so far apart that you could store as much data as you wanted at each location without running into the next one.&lt;/p&gt;

&lt;p&gt;Of course, choosing locations at random wouldn’t be very helpful, because you would have no way to find the data again. Solidity instead uses a hash function to uniformly and repeatably compute locations for dynamically-sized values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamically-Sized Arrays&lt;/strong&gt;&lt;br&gt;
A dynamically-sized array needs a place to store its size as well as its elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract StorageTest {
    uint256 a;     // slot 0
    uint256[2] b;  // slots 1-2

    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;       // slots 3-4
    Entry[] d;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, the dynamically-sized array d is at slot 5, but the only thing that’s stored there is the size of d. The values in the array are stored consecutively starting at the hash of the slot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk1zdhiqpzvlc7z5nm0b3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk1zdhiqpzvlc7z5nm0b3.png" alt=" " width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following Solidity function computes the location of an element of a dynamically-sized array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
function arrLocation(uint256 slot, uint256 index, uint256 elementSize)
    public
    pure
    returns (uint256)
{
    return uint256(keccak256(slot)) + (index * elementSize);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mappings
&lt;/h2&gt;

&lt;p&gt;A mapping requires an efficient way to find the location corresponding to a given key. Hashing the key is a good start, but care must be taken to make sure different mappings generate different locations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
contract StorageTest {
    uint256 a;     // slot 0
    uint256[2] b;  // slots 1-2

    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;       // slots 3-4
    Entry[] d;     // slot 5 for length, keccak256(5)+ for data

    mapping(uint256 =&amp;gt; uint256) e;
    mapping(uint256 =&amp;gt; uint256) f;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, the “location” for e is slot 6, and the location for f is slot 7, but nothing is actually stored at those locations. (There’s no length to be stored, and individual values need to be located elsewhere.)&lt;/p&gt;

&lt;p&gt;To find the location of a specific value within a mapping, the key and the mapping’s slot are hashed together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2xj4ytuxeadif35t4dea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2xj4ytuxeadif35t4dea.png" alt=" " width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following Solidity function computes the location of a value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
function mapLocation(uint256 slot, uint256 key) public pure returns (uint256) {
    return uint256(keccak256(key, slot));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that when keccak256 is called with multiple parameters, the parameters are concatenated together before hashing. Because the slot and key are both inputs to the hash function, there aren’t collisions between different mappings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combinations of Complex Types
&lt;/h2&gt;

&lt;p&gt;Dynamically-sized arrays and mappings can be nested within each other recursively. When that happens, the location of a value is found by recursively applying the calculations defined above. This sounds more complex than it is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
contract StorageTest {
    uint256 a;     // slot 0
    uint256[2] b;  // slots 1-2

    struct Entry {
        uint256 id;
        uint256 value;
    }
    Entry c;       // slots 3-4
    Entry[] d;     // slot 5 for length, keccak256(5)+ for data

    mapping(uint256 =&amp;gt; uint256) e;    // slot 6, data at h(k . 6)
    mapping(uint256 =&amp;gt; uint256) f;    // slot 7, data at h(k . 7)

    mapping(uint256 =&amp;gt; uint256[]) g;  // slot 8
    mapping(uint256 =&amp;gt; uint256)[] h;  // slot 9
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To find items within these complex types, we can use the functions defined above. To find g[123][0]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
// first find arr = g[123]
arrLoc = mapLocation(8, 123);  // g is at slot 8

// then find arr[0]
itemLoc = arrLocation(arrLoc, 0, 1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To find h[2][456]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// first find map = h[2]
mapLoc = arrLocation(9, 2, 1);  // h is at slot 9

// then find map[456]
itemLoc = mapLocation(mapLoc, 456);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Each smart contract has storage in the form of an array of 2256 32-byte values, all initialized to zero.&lt;/li&gt;
&lt;li&gt;Zeros are not explicitly stored, so setting a value to zero reclaims that storage.&lt;/li&gt;
&lt;li&gt;Solidity locates fixed-size values at reserved locations called slots, starting at slot 0.&lt;/li&gt;
&lt;li&gt;Solidity exploits the sparseness of storage and the uniform distribution of hash outputs to safely locate dynamically-sized values.&lt;/li&gt;
&lt;li&gt;The following table shows how storage locations are computed for different types. The “slot” refers to the next available slot when the state variable is encountered at compile time, and a dot indicates binary concatenation:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsgrf8whb7qtogwckfff9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsgrf8whb7qtogwckfff9.png" alt=" " width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.soliditylang.org/en/v0.4.20/miscellaneous.html#layout-of-state-variables-in-storage" rel="noopener noreferrer"&gt;https://docs.soliditylang.org/en/v0.4.20/miscellaneous.html#layout-of-state-variables-in-storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-2-storage-layout-bc5349cb11b7" rel="noopener noreferrer"&gt;https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-2-storage-layout-bc5349cb11b7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@hayeah/diving-into-the-ethereum-vm-the-hidden-costs-of-arrays-28e119f04a9b" rel="noopener noreferrer"&gt;https://medium.com/@hayeah/diving-into-the-ethereum-vm-the-hidden-costs-of-arrays-28e119f04a9b&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ethereum</category>
      <category>blockchain</category>
      <category>smartcontract</category>
    </item>
  </channel>
</rss>
