Fixed-Point Arithmetic and Rounding Policy — The Rounding Direction That Preserves Invariants (Preventing Dust Leaks), and LMSR's exp/ln Approximation Error Bound TODO
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")
docs/code/algorithms/algorithms-89.py
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/.