Workspace IndexMath › Day 7

Big-O and Gas TODO

Math · Day 7 / 52 · July — Discrete Math & Logic (Day 3-10)

Concept

Big-O is an asymptotic upper bound describing how resource usage grows, within a constant factor, as input size grows — by definition it ignores constant multipliers and lower-order terms. Gas is the cost unit assigned to each operation in the EVM, and because transactions and blocks have gas limits, it means "the price tag of finite computation." The decisive difference between the two concepts is that gas is a concrete price list that includes the constants, so operations with large constants — like storage writes or hashing — can dominate total cost even in the small-n regime. Conversely, an O(n) loop over an entire array becomes a DoS vector if an attacker can grow n, since it eventually hits the block gas limit and the function becomes permanently unexecutable. So on-chain cost analysis has to look at both asymptotic order and per-operation constants, and always ask which inputs are under attacker control.

In on-chain code, slow code doesn't just run slowly — it can simply fail to execute at all and lock up funds. An array loop that can grow without bound is a real, documented vulnerability class.

Code & Formula

# Big-O & 가스 — "점근 차수"와 "연산별 상수"는 다르다는 걸 O(n) vs O(1) 조회 비교로 보여준다.
# 참여자 목록을 루프로 순회(O(n))하는 정산과, 매핑으로 바로 조회(O(1))하는 정산을 비교.

import time

GAS_PER_ITER = 3       # 루프 한 바퀴 도는 데 드는 가스(단순화한 상수)
GAS_PER_LOOKUP = 5      # 매핑(dict) 조회 1회 가스

def settle_by_scan(balances: list, target_id: int) -> int:
    # O(n): 참여자 수만큼 전부 훑어야 target 을 찾는다 — 공격자가 n 을 키우면 가스가 비례 증가.
    gas = 0
    for pid, bal in balances:
        gas += GAS_PER_ITER
        if pid == target_id:
            return gas
    return gas

def settle_by_map(balance_map: dict, target_id: int) -> int:
    # O(1): n 과 무관하게 상수 가스.
    _ = balance_map[target_id]
    return GAS_PER_LOOKUP

for n in (10, 100, 1_000, 10_000, 100_000):
    balances = [(i, 100) for i in range(n)]
    balance_map = dict(balances)
    target = n - 1   # 최악의 경우: 리스트 맨 끝

    gas_scan = settle_by_scan(balances, target)
    gas_map = settle_by_map(balance_map, target)
    print(f"n={n:>7}  scan(O(n)) gas={gas_scan:>7}  map(O(1)) gas={gas_map}")

BLOCK_GAS_LIMIT = 30_000_000
n_at_limit = BLOCK_GAS_LIMIT // GAS_PER_ITER
print(f"\n블록 가스 한도 {BLOCK_GAS_LIMIT:,} 기준, scan 방식은 참여자 수가 약 {n_at_limit:,} 명을")
print("넘으면 트랜잭션 하나로 정산이 아예 불가능해진다 — O(1) map 방식은 n 과 무관하게 안전.")

Exercise

Implement the same logic once as an O(n) array scan and once as an O(1) mapping lookup, measure gas as element count grows, and find roughly where n hits the block gas limit.

Practical Connection

If Verex settles by looping over a list of market participants or open orders, the settlement transaction starts failing as participants grow, so it needs to move to a constant-cost design like pull-based claims.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/math-50-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

Big-O & 가스 TODO

Math · Day 7 / 52 · 7월 — 이산수학·논리 (Day 3–10)

"가스=유한 계산의 가격" 직관

개념

Big-O는 입력 크기가 커질 때 자원 사용량이 어떤 함수의 상수배 이내로 증가하는지를 나타내는 점근적 상한이며, 정의상 상수 배수와 낮은 차수 항을 무시한다. 가스는 EVM에서 각 연산에 매겨진 비용 단위이고, 트랜잭션과 블록에 가스 한도가 있어 유한한 계산량만 허용된다는 뜻에서 '유한 계산의 가격표'다. 두 개념의 결정적 차이는 가스가 상수까지 포함한 구체적 가격표라는 점이며, 그래서 스토리지 쓰기나 해시처럼 상수가 큰 연산이 n이 작은 구간에서도 전체 비용을 지배한다. 반대로 배열 전체를 도는 O(n) 루프는 n을 공격자가 키울 수 있으면 블록 가스 한도에 걸려 함수가 영구히 실행 불가능해지는 DoS 벡터가 된다. 따라서 온체인 비용 분석은 '점근 차수'와 '연산별 상수'를 함께 봐야 하고, 어느 입력이 공격자 통제 하에 있는지를 반드시 함께 따져야 한다.

온체인 코드에서는 느린 코드가 그냥 느린 게 아니라 아예 실행되지 않고 자금을 묶어 버릴 수 있다. 무한정 길어질 수 있는 배열 순회는 실제 취약점 유형이다.

코드 · 수식

# Big-O & 가스 — "점근 차수"와 "연산별 상수"는 다르다는 걸 O(n) vs O(1) 조회 비교로 보여준다.
# 참여자 목록을 루프로 순회(O(n))하는 정산과, 매핑으로 바로 조회(O(1))하는 정산을 비교.

import time

GAS_PER_ITER = 3       # 루프 한 바퀴 도는 데 드는 가스(단순화한 상수)
GAS_PER_LOOKUP = 5      # 매핑(dict) 조회 1회 가스

def settle_by_scan(balances: list, target_id: int) -> int:
    # O(n): 참여자 수만큼 전부 훑어야 target 을 찾는다 — 공격자가 n 을 키우면 가스가 비례 증가.
    gas = 0
    for pid, bal in balances:
        gas += GAS_PER_ITER
        if pid == target_id:
            return gas
    return gas

def settle_by_map(balance_map: dict, target_id: int) -> int:
    # O(1): n 과 무관하게 상수 가스.
    _ = balance_map[target_id]
    return GAS_PER_LOOKUP

for n in (10, 100, 1_000, 10_000, 100_000):
    balances = [(i, 100) for i in range(n)]
    balance_map = dict(balances)
    target = n - 1   # 최악의 경우: 리스트 맨 끝

    gas_scan = settle_by_scan(balances, target)
    gas_map = settle_by_map(balance_map, target)
    print(f"n={n:>7}  scan(O(n)) gas={gas_scan:>7}  map(O(1)) gas={gas_map}")

BLOCK_GAS_LIMIT = 30_000_000
n_at_limit = BLOCK_GAS_LIMIT // GAS_PER_ITER
print(f"\n블록 가스 한도 {BLOCK_GAS_LIMIT:,} 기준, scan 방식은 참여자 수가 약 {n_at_limit:,} 명을")
print("넘으면 트랜잭션 하나로 정산이 아예 불가능해진다 — O(1) map 방식은 n 과 무관하게 안전.")

연습

같은 로직을 배열 순회 O(n)과 매핑 조회 O(1)로 두 벌 구현해 원소 수를 늘려 가며 가스를 측정하고, 블록 가스 한도에 걸리는 n이 어디쯤인지 찾아보라.

실무 · Verex 연결

Verex에서 시장 참여자 목록이나 미결제 주문을 루프로 순회해 정산하는 구조라면 참여자가 늘수록 정산 트랜잭션이 실패하게 되므로, pull 방식 청구 같은 상수 비용 설계로 바꿔야 한다.

공부한 날 원본 커리큘럼(docs/knowledge/math-50-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 6. 비둘기집 원리8. 관계와 동치류 →