Web3 / DeFi Protocol
Blockchain Protocol — Smart Contract Execution Engine
The market order processing function converted a user-controlled uint256 input through an intermediate int64 cast before computing the final transfer amount. For values above 2^63−1 the cast silently truncated to a small positive or negative value, allowing an attacker to submit a market order with an astronomically large nominal value that the engine accepted, processed at the truncated amount, and credited to the attacker's balance — an effective fund extraction with near-zero cost.
Featured Finding — Report Excerpt
Proof of Concept
# Root cause — order execution (simplified pseudocode)
def execute_market_order(amount_u256: uint256, price: int64) -> int64:
# Unsafe downcast: values > 2^63-1 wrap to negative or small positive
amount_i64 = int64(amount_u256) # ← truncation here
transfer_value = amount_i64 * price # computed on truncated value
credit_account(transfer_value) # wrong amount credited
return transfer_value
# PoC: craft an amount that truncates to a small positive integer
# 2^63 + 1 → int64 cast → -9223372036854775807 (negative, rejected)
# 2^64 + 100 → int64 cast → 100 (small positive — accepted)
# On-chain transaction replay (redacted)
TriggerSmartContract {
owner_address: <attacker>
contract_address: <market_contract>
function_selector: "marketOrder(uint256,int64,bool)"
parameter: [
amount = 0x10000000000000064, # 2^64 + 100 → truncates to 100
price = 0x000000000000270F, # 9999
is_buy = true
]
}
# Engine receives: amount=100, price=9999
# Attacker's nominal commitment: 2^64+100 (rejected by token balance check)
# Attacker's actual debit: 100 * 9999 = 999,900 units (tiny)
# Attacker's credited position: 100 units at market rate
# Net: attacker pays ~0 and receives credit equivalent to 100 unitsConfirmed Impact
An attacker can submit a market order with a uint256 value engineered to truncate to any small positive integer after the int64 cast. The engine debits the truncated amount (negligible cost) while crediting the attacker with a position derived from that same truncated amount — but the nominal order size triggers no pre-check failure because the uint256 value itself is plausible. Across repeated calls with crafted amounts, an attacker can drain counterparty reserves at near-zero personal cost. Negative control confirmed: amounts that truncate to negative integers are correctly rejected by the sign check; only wrap-around-to-positive values bypass the guard.
Remediation
Remove all int64 intermediate casts from the order execution path. Perform all arithmetic in the native uint256/int256 domain and add explicit overflow/range checks before any downcast. Add a unit test for every amount boundary at 2^31, 2^32, 2^63, 2^64, and 2^256−1 in the order execution and crediting functions.
Evidence Summary
Runtime test on a local protocol fork confirmed the truncation path: a uint256 amount of 2^64+100 was accepted, debited at 100 units, and credited 100 units of position — while the nominal 2^64+100 value passed the token balance check with no error. Control transactions with non-wrapping amounts processed correctly. The truncation gate is the sole bypass vector; amounts that land on negative int64 representations are caught by a downstream sign check and rejected.