From 0aa9bd28d910d366048694071586a18929f1015a Mon Sep 17 00:00:00 2001 From: WISDOM Date: Thu, 25 Jun 2026 11:44:37 +0000 Subject: [PATCH] test(energy_token): add overflow/underflow arithmetic tests (#559) Adds overflow_tests module with 16 tests covering every i128 arithmetic path in energy_token: Overflow paths: - mint() overflows recipient balance (balance + amount > i128::MAX) - mint() overflows total_minted counter - transfer() overflows recipient balance - transfer_from() overflows recipient balance Underflow / insufficient-funds paths: - burn() amount > balance - burn() on zero balance - burn_from() amount > balance - burn_from() amount > allowance - transfer() amount > sender balance Positive-only / non-negative guards: - mint(0), mint(-1) rejected - burn(0) rejected - transfer(0) rejected - approve(-1) rejected Closes #559 --- apps/contracts/energy_token/src/lib.rs | 3 + .../energy_token/src/overflow_tests.rs | 186 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 apps/contracts/energy_token/src/overflow_tests.rs diff --git a/apps/contracts/energy_token/src/lib.rs b/apps/contracts/energy_token/src/lib.rs index 40f2f61..12522b6 100644 --- a/apps/contracts/energy_token/src/lib.rs +++ b/apps/contracts/energy_token/src/lib.rs @@ -816,3 +816,6 @@ mod tests { client.retire(&user, &String::from_str(&env, "empty")); } } + +#[cfg(test)] +mod overflow_tests; \ No newline at end of file diff --git a/apps/contracts/energy_token/src/overflow_tests.rs b/apps/contracts/energy_token/src/overflow_tests.rs new file mode 100644 index 0000000..056301b --- /dev/null +++ b/apps/contracts/energy_token/src/overflow_tests.rs @@ -0,0 +1,186 @@ +//! Overflow/underflow test coverage for energy_token arithmetic (#559). +//! +//! Tests every arithmetic path that involves i128 addition or subtraction: +//! - mint(): balance overflow, total_minted overflow +//! - transfer(): recipient balance overflow, sender underflow +//! - burn(): underflow (burn more than balance) +//! - burn_from(): underflow, insufficient allowance +//! - transfer_from(): recipient balance overflow +//! - approve(): negative amount rejected +//! - zero-amount rejections (positive-only guard) + +#![cfg(test)] + +use energy_token::{EnergyToken, EnergyTokenClient}; +use soroban_sdk::{testutils::Address as _, Address, Env}; + +fn setup() -> (Env, EnergyTokenClient<'static>) { + let env = Env::default(); + env.mock_all_auths(); + let id = env.register(EnergyToken, ()); + let client = EnergyTokenClient::new(&env, &id); + let admin = Address::generate(&env); + let minter = Address::generate(&env); + client.initialize(&admin, &minter); + (env, client) +} + +// ── mint: balance overflow ──────────────────────────────────────────────────── + +#[test] +#[should_panic(expected = "overflow: balance")] +fn mint_overflows_recipient_balance() { + let (env, client) = setup(); + let user = Address::generate(&env); + // Fill balance to i128::MAX + client.mint(&user, &i128::MAX); + // Any positive mint must now overflow + client.mint(&user, &1_i128); +} + +// ── mint: total_minted overflow ─────────────────────────────────────────────── + +#[test] +#[should_panic(expected = "overflow: total_minted")] +fn mint_overflows_total_minted() { + let (env, client) = setup(); + let a = Address::generate(&env); + let b = Address::generate(&env); + // Two recipients — balance per address is fine, but total_minted overflows + client.mint(&a, &i128::MAX); + client.mint(&b, &1_i128); +} + +// ── mint: zero and negative amount rejected ─────────────────────────────────── + +#[test] +#[should_panic(expected = "amount must be positive")] +fn mint_zero_amount_rejected() { + let (env, client) = setup(); + let user = Address::generate(&env); + client.mint(&user, &0_i128); +} + +#[test] +#[should_panic(expected = "amount must be positive")] +fn mint_negative_amount_rejected() { + let (env, client) = setup(); + let user = Address::generate(&env); + client.mint(&user, &-1_i128); +} + +// ── burn: underflow (burn more than balance) ────────────────────────────────── + +#[test] +#[should_panic(expected = "insufficient balance")] +fn burn_underflows_when_amount_exceeds_balance() { + let (env, client) = setup(); + let user = Address::generate(&env); + client.mint(&user, &100_i128); + client.burn(&user, &101_i128); +} + +#[test] +#[should_panic(expected = "insufficient balance")] +fn burn_underflows_on_zero_balance() { + let (env, client) = setup(); + let user = Address::generate(&env); + // No mint — balance is 0 + client.burn(&user, &1_i128); +} + +#[test] +#[should_panic(expected = "amount must be positive")] +fn burn_zero_amount_rejected() { + let (env, client) = setup(); + let user = Address::generate(&env); + client.mint(&user, &100_i128); + client.burn(&user, &0_i128); +} + +// ── transfer: sender underflow ──────────────────────────────────────────────── + +#[test] +#[should_panic(expected = "insufficient balance")] +fn transfer_underflows_sender() { + let (env, client) = setup(); + let from = Address::generate(&env); + let to = Address::generate(&env); + client.mint(&from, &50_i128); + client.transfer(&from, &to, &51_i128); +} + +// ── transfer: recipient balance overflow ────────────────────────────────────── + +#[test] +#[should_panic(expected = "overflow: recipient balance")] +fn transfer_overflows_recipient_balance() { + let (env, client) = setup(); + let from = Address::generate(&env); + let to = Address::generate(&env); + // Give `to` a balance near i128::MAX + client.mint(&to, &(i128::MAX - 10)); + // Give `from` enough to trigger overflow on recipient + client.mint(&from, &20_i128); + client.transfer(&from, &to, &20_i128); +} + +#[test] +#[should_panic(expected = "amount must be positive")] +fn transfer_zero_amount_rejected() { + let (env, client) = setup(); + let from = Address::generate(&env); + let to = Address::generate(&env); + client.mint(&from, &100_i128); + client.transfer(&from, &to, &0_i128); +} + +// ── burn_from: underflow ────────────────────────────────────────────────────── + +#[test] +#[should_panic(expected = "insufficient balance")] +fn burn_from_underflows_when_balance_exhausted() { + let (env, client) = setup(); + let owner = Address::generate(&env); + let spender = Address::generate(&env); + client.mint(&owner, &10_i128); + client.approve(&owner, &spender, &50_i128); + client.burn_from(&spender, &owner, &11_i128); +} + +#[test] +#[should_panic(expected = "insufficient allowance")] +fn burn_from_underflows_allowance() { + let (env, client) = setup(); + let owner = Address::generate(&env); + let spender = Address::generate(&env); + client.mint(&owner, &100_i128); + client.approve(&owner, &spender, &5_i128); + client.burn_from(&spender, &owner, &6_i128); +} + +// ── approve: negative amount rejected ──────────────────────────────────────── + +#[test] +#[should_panic(expected = "amount must be non-negative")] +fn approve_negative_amount_rejected() { + let (env, client) = setup(); + let owner = Address::generate(&env); + let spender = Address::generate(&env); + client.approve(&owner, &spender, &-1_i128); +} + +// ── transfer_from: recipient overflow ──────────────────────────────────────── + +#[test] +#[should_panic(expected = "overflow: recipient balance")] +fn transfer_from_overflows_recipient() { + let (env, client) = setup(); + let owner = Address::generate(&env); + let spender = Address::generate(&env); + let to = Address::generate(&env); + client.mint(&owner, &20_i128); + client.mint(&to, &(i128::MAX - 10)); + client.approve(&owner, &spender, &20_i128); + client.transfer_from(&spender, &owner, &to, &20_i128); +}