Workspace IndexAlgorithms › Day 89

Fixed-Point Arithmetic and Rounding Policy — The Rounding Direction That Preserves Invariants (Preventing Dust Leaks), and LMSR's exp/ln Approximation Error Bound TODO

Algorithms · Day 89 / 100 · F. Cryptography & ZK (Day 82-96)

Concept

The EVM has no floating point, so ratios and prices are handled as fixed-point numbers — integers with an implicit scale factor (e.g., 1e18). Multiplication doubles the scale, so it has to be divided back down, and each such division introduces truncation error whose direction either preserves or breaks the system's invariants. The rule is to always round in the protocol's (pool/contract's) favor: round down what a user receives, round up what a user pays, to prevent a dust leak where repeated trades siphon off the remainder. Cost functions that need exp and ln, like LMSR's, have to be implemented as integer approximations, and you need to work out both the error bound of that approximation and whether the error breaks the cost function's monotonicity or convexity. The thing that ultimately needs verifying isn't the precision of any single operation — it's whether the invariant holds no matter what order the operations run in.

Getting a single rounding direction backwards turns a mathematically tiny error into a free, infinitely repeatable withdrawal path.

Code & Formula

# 고정소수점 산술과 반올림 정책 — 정수 스케일(1e18)로 나눗셈 절사 오차를 다루고,
# "프로토콜에 유리한 방향"으로 반올림해 dust leak(잔여분 누적 착취)을 막는 것을 시연.

SCALE = 10**18  # 고정소수점 스케일 (EVM의 흔한 관례)

def mul_div_floor(a, b, denom):
    return (a * b) // denom            # 사용자가 "받는" 양 — 내림 (프로토콜에 유리)

def mul_div_ceil(a, b, denom):
    return -((-(a * b)) // denom)      # 사용자가 "내는" 양 — 올림 (프로토콜에 유리)

price = 3 * SCALE // 7  # 나누어떨어지지 않는 가격 (절사 오차가 필연적으로 생김)

def swap_user_receives(amount_in):
    # 사용자가 amount_in 을 내고 price 만큼의 비율로 얼마를 받는지: 내림 처리
    return mul_div_floor(amount_in, SCALE, price)

def swap_user_pays(amount_out_wanted):
    # 사용자가 amount_out_wanted 를 원할 때 얼마를 내야 하는지: 올림 처리
    return mul_div_ceil(amount_out_wanted * price, 1, SCALE)

amount_in = 1_000_000  # 아주 작은 입력 (절사 오차가 상대적으로 크게 드러나도록)
received_correct = swap_user_receives(amount_in)   # 내림 (안전)
received_wrong = (amount_in * SCALE) // price if True else None
paid_correct = swap_user_pays(received_correct)     # 올림 (안전)

print("price (scaled):", price, "=> approx", price / SCALE)
print("user receives (floor, protocol-favoring):", received_correct)
print("re-quoted amount user must pay (ceil, protocol-favoring):", paid_correct)
print("paid >= amount_in (no value leaked to user via rounding):", paid_correct >= amount_in)

# 반대로 "받는 양"을 올림 처리하면 반복 거래로 프로토콜에서 조금씩 잔여분을 긁어갈 수 있다
def swap_user_receives_UNSAFE(amount_in):
    return mul_div_ceil(amount_in, SCALE, price)

unsafe_received = swap_user_receives_UNSAFE(amount_in)
print()
print("UNSAFE variant (rounds in user's favor) receives:", unsafe_received)
print("unsafe > safe by:", unsafe_received - received_correct, "=> repeated trades could drain dust")

Exercise

Implement fixed-point mulDiv in both round-down and round-up versions, then simulate thousands of rapid buy-then-sell cycles and check whether the pool balance grows or shrinks.

Practical Connection

Verex's LMSR cost function and its CLOB matching/settlement path sit right at the center of this problem, and without documenting the rounding convention in one place, different modules will drift in different directions.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-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/.


한국어

고정소수점 산술과 반올림 정책 TODO

Algorithms · Day 89 / 100 · F. 암호학·ZK (Day 82–96)

불변식을 지키는 반올림 방향(dust leak 방지), LMSR의 exp/ln 근사 오차 상한

개념

EVM에는 부동소수점이 없으므로 비율과 가격은 고정소수점, 즉 정수에 암묵적 스케일(예: 1e18)을 곱한 표현으로 다룬다. 곱셈은 스케일이 두 배가 되므로 나누어 되돌려야 하고, 이 나눗셈마다 절사 오차가 생기며 그 방향이 시스템의 불변식을 지키거나 깨뜨린다. 원칙은 항상 프로토콜(풀·컨트랙트)에 유리한 방향으로 반올림하는 것으로, 사용자가 받는 양은 내림, 사용자가 내는 양은 올림으로 처리해 반복 거래로 잔여분을 긁어가는 dust leak을 막는다. LMSR처럼 exp와 ln이 필요한 비용함수는 정수 근사로 구현해야 하고, 근사 오차의 상한과 그 오차가 비용함수의 단조성·볼록성을 깨지 않는지를 함께 따져야 한다. 결론적으로 검증해야 할 것은 개별 연산의 정밀도가 아니라 "어떤 순서로 연산해도 불변식이 유지되는가"이다.

반올림 방향 하나를 반대로 잡으면 수학적으로는 미미한 오차가 무한 반복 가능한 무료 인출 경로가 된다.

코드 · 수식

# 고정소수점 산술과 반올림 정책 — 정수 스케일(1e18)로 나눗셈 절사 오차를 다루고,
# "프로토콜에 유리한 방향"으로 반올림해 dust leak(잔여분 누적 착취)을 막는 것을 시연.

SCALE = 10**18  # 고정소수점 스케일 (EVM의 흔한 관례)

def mul_div_floor(a, b, denom):
    return (a * b) // denom            # 사용자가 "받는" 양 — 내림 (프로토콜에 유리)

def mul_div_ceil(a, b, denom):
    return -((-(a * b)) // denom)      # 사용자가 "내는" 양 — 올림 (프로토콜에 유리)

price = 3 * SCALE // 7  # 나누어떨어지지 않는 가격 (절사 오차가 필연적으로 생김)

def swap_user_receives(amount_in):
    # 사용자가 amount_in 을 내고 price 만큼의 비율로 얼마를 받는지: 내림 처리
    return mul_div_floor(amount_in, SCALE, price)

def swap_user_pays(amount_out_wanted):
    # 사용자가 amount_out_wanted 를 원할 때 얼마를 내야 하는지: 올림 처리
    return mul_div_ceil(amount_out_wanted * price, 1, SCALE)

amount_in = 1_000_000  # 아주 작은 입력 (절사 오차가 상대적으로 크게 드러나도록)
received_correct = swap_user_receives(amount_in)   # 내림 (안전)
received_wrong = (amount_in * SCALE) // price if True else None
paid_correct = swap_user_pays(received_correct)     # 올림 (안전)

print("price (scaled):", price, "=> approx", price / SCALE)
print("user receives (floor, protocol-favoring):", received_correct)
print("re-quoted amount user must pay (ceil, protocol-favoring):", paid_correct)
print("paid >= amount_in (no value leaked to user via rounding):", paid_correct >= amount_in)

# 반대로 "받는 양"을 올림 처리하면 반복 거래로 프로토콜에서 조금씩 잔여분을 긁어갈 수 있다
def swap_user_receives_UNSAFE(amount_in):
    return mul_div_ceil(amount_in, SCALE, price)

unsafe_received = swap_user_receives_UNSAFE(amount_in)
print()
print("UNSAFE variant (rounds in user's favor) receives:", unsafe_received)
print("unsafe > safe by:", unsafe_received - received_correct, "=> repeated trades could drain dust")

연습

고정소수점 mulDiv를 내림·올림 두 버전으로 구현하고, 매수 후 즉시 매도를 수천 번 반복하는 시뮬레이션으로 풀 잔고가 늘어나는지 줄어드는지 확인해 보기.

실무 · Verex 연결

Verex의 LMSR 비용함수와 CLOB 체결·정산 경로는 정확히 이 문제의 중심이고, 반올림 규약을 한 곳에 문서화해 두지 않으면 모듈마다 방향이 어긋난다.

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

← 88. 다중정밀 산술(bignum)90. 유한체·다항식 산술과 NTT 구현 관점 →