Type: Bug / Security
Priority: High
Difficulty: Easy
Labels: bug, security, smart-contract, escrow
File: contracts/escrow/src/lib.rs → submit_result
In EscrowContract::submit_result, the payout for a non-draw result is calculated as:
let payout_amount: i128 = match winner {
Winner::Draw => m.stake_amount,
_ => m.stake_amount * 2, // ← unchecked multiplication
};Soroban contracts compile to WASM with overflow-checks = false in release mode, meaning an unchecked i128 multiplication will wrap silently rather than trap. If stake_amount is near i128::MAX / 2 (a value that passes the > 0 guard in create_match), the multiplication overflows to a negative number, causing token::transfer to receive a negative amount and either panic or send the wrong value.
- Deploy the escrow contract on Soroban testnet.
- Create a match with
stake_amount = i128::MAX / 2 + 1. - Both players call
deposit. - Oracle calls
submit_resultwithWinner::Player1. payout_amount = (i128::MAX / 2 + 1) * 2overflows to a negative value.client.transfer(…, &payout_amount)is called with a negative amount.
submit_result returns Error::Overflow (code 8) when stake_amount * 2 would exceed i128::MAX. No token transfer occurs and match state remains Active so the situation can be recovered.
In release WASM builds (where overflow-checks are disabled), the multiplication wraps silently. The contract either panics inside the token transfer (surfacing a confusing host error) or sends an incorrect amount.
i128::MAX=170_141_183_460_469_231_731_687_303_715_884_105_727i128::MAX / 2=85_070_591_730_234_615_865_843_651_857_942_052_863- Any
stake_amount > i128::MAX / 2will overflow when doubled - The existing overflow guard in
create_matchonly protects theMatchCountcounter, not the payout calculation i128::checked_mulreturnsNoneon overflow with zero runtime cost in release mode
- Replace
m.stake_amount * 2withm.stake_amount.checked_mul(2).ok_or(Error::Overflow)? - Add a corresponding unit test in
contracts/escrow/src/tests.rs:#[test] fn test_submit_result_overflow_stake_rejected() { // stake_amount = i128::MAX / 2 + 1 should fail with Error::Overflow }
- Verify the test fails before the fix and passes after
- Add a comment above the calculation explaining the overflow risk
// In submit_result, replace:
let payout_amount: i128 = match winner {
Winner::Draw => m.stake_amount,
_ => m.stake_amount * 2,
};
// With:
let payout_amount: i128 = match winner {
Winner::Draw => m.stake_amount,
_ => m.stake_amount.checked_mul(2).ok_or(Error::Overflow)?,
};No other changes are required. The Error::Overflow variant (code 8) already exists in contracts/escrow/src/errors.rs.
contracts/escrow/src/lib.rs:258— current multiplicationcontracts/escrow/src/errors.rs:38—Error::Overflow = 8- Rust reference: integer overflow behaviour
- Soroban WASM compilation flags