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.

Where it lands in Jayverse

Key expressions

Words and phrases from this page worth keeping, with the Korean meaning and the sentence they come from.

Expression뜻 · 쓰이는 자리
dust leak(먼지처럼 아주 작은 금액이 새어나가는) 더스트 유출 · 반올림 방향을 잘못 잡으면 생기는 누수 · "a dust leak where repeated trades siphon off the remainder"
siphon off(조금씩) 빼돌리다, 빨아가다 · 반복 거래로 잔여분을 조금씩 빼가는 것 · "repeated trades siphon off the remainder"
in ~'s favor~에 유리한 방향으로 · 반올림을 항상 프로토콜 쪽에 유리하게 하는 규칙 · "always round in the protocol's (pool/contract's) favor"
truncation error절삭 오차 · 나눗셈으로 스케일을 되돌릴 때 생기는 오차 · "each such division introduces truncation error"
no matter what order순서가 어떻든 상관없이 · 연산 순서와 무관하게 불변식이 지켜져야 함 · "whether the invariant holds no matter what order the operations run"
monotonicity단조성(항상 증가하거나 항상 감소하는 성질) · 근사 오차가 이 성질을 깨는지 확인해야 함 · "breaks the cost function's monotonicity or convexity"
drift(기준 없이 각자) 제멋대로 어긋나다 · 반올림 규칙을 안 정해두면 모듈마다 다르게 틀어짐 · "different modules will drift in different directions"
LMSR로그 마켓 스코어링 룰(Logarithmic Market Scoring Rule, LMSR) · exp/ln 근사가 필요한 예측시장 비용함수, 이 카드의 오차 분석 대상. "LMSR's exp/ln Approximation Error Bound"
CLOB중앙집중형 지정가 주문서(Central Limit Order Book, CLOB) · 매칭·정산 경로에서 반올림 규칙이 함께 적용되는 대상. "its CLOB matching/settlement path sit right at the center"
mulDivmulDiv(고정소수점 곱셈 후 나눗셈) · 스케일을 되돌리는 표준 고정소수점 연산, 반올림 방향 정책이 적용되는 지점. "Implement fixed-point mulDiv in both round-down and round-up versions"

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 체결·정산 경로는 정확히 이 문제의 중심이고, 반올림 규약을 한 곳에 문서화해 두지 않으면 모듈마다 방향이 어긋난다.

Jayverse에서의 위치

핵심 표현

이 페이지의 영어 본문에서 배울 만한 단어와 표현, 뜻과 나온 자리.

Expression뜻 · 쓰이는 자리
dust leak(먼지처럼 아주 작은 금액이 새어나가는) 더스트 유출 · 반올림 방향을 잘못 잡으면 생기는 누수 · "a dust leak where repeated trades siphon off the remainder"
siphon off(조금씩) 빼돌리다, 빨아가다 · 반복 거래로 잔여분을 조금씩 빼가는 것 · "repeated trades siphon off the remainder"
in ~'s favor~에 유리한 방향으로 · 반올림을 항상 프로토콜 쪽에 유리하게 하는 규칙 · "always round in the protocol's (pool/contract's) favor"
truncation error절삭 오차 · 나눗셈으로 스케일을 되돌릴 때 생기는 오차 · "each such division introduces truncation error"
no matter what order순서가 어떻든 상관없이 · 연산 순서와 무관하게 불변식이 지켜져야 함 · "whether the invariant holds no matter what order the operations run"
monotonicity단조성(항상 증가하거나 항상 감소하는 성질) · 근사 오차가 이 성질을 깨는지 확인해야 함 · "breaks the cost function's monotonicity or convexity"
drift(기준 없이 각자) 제멋대로 어긋나다 · 반올림 규칙을 안 정해두면 모듈마다 다르게 틀어짐 · "different modules will drift in different directions"
LMSR로그 마켓 스코어링 룰(Logarithmic Market Scoring Rule, LMSR) · exp/ln 근사가 필요한 예측시장 비용함수, 이 카드의 오차 분석 대상. "LMSR's exp/ln Approximation Error Bound"
CLOB중앙집중형 지정가 주문서(Central Limit Order Book, CLOB) · 매칭·정산 경로에서 반올림 규칙이 함께 적용되는 대상. "its CLOB matching/settlement path sit right at the center"
mulDivmulDiv(고정소수점 곱셈 후 나눗셈) · 스케일을 되돌리는 표준 고정소수점 연산, 반올림 방향 정책이 적용되는 지점. "Implement fixed-point mulDiv in both round-down and round-up versions"

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

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