From b0b0fabc77f11ae3b3866100fc447f9a4a3943b8 Mon Sep 17 00:00:00 2001 From: xtep103 <302179003+xtep103@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:28:05 +0100 Subject: [PATCH] fix(tholos): verify balance deltas for incoming token transfers (#164) --- contracts/tholos/src/lib.rs | 76 ++- contracts/tholos/src/test.rs | 186 +++++ ...n_dispute_resolves_without_deadlock.1.json | 633 ++++++++++++++++++ ..._finalize_resolves_without_deadlock.1.json | 514 ++++++++++++++ ...est_zero_received_transfer_rejected.1.json | 292 ++++++++ 5 files changed, 1671 insertions(+), 30 deletions(-) create mode 100644 contracts/tholos/test_snapshots/test/test_fee_on_transfer_token_dispute_resolves_without_deadlock.1.json create mode 100644 contracts/tholos/test_snapshots/test/test_fee_on_transfer_token_finalize_resolves_without_deadlock.1.json create mode 100644 contracts/tholos/test_snapshots/test/test_zero_received_transfer_rejected.1.json diff --git a/contracts/tholos/src/lib.rs b/contracts/tholos/src/lib.rs index 7844d73..9ac232a 100644 --- a/contracts/tholos/src/lib.rs +++ b/contracts/tholos/src/lib.rs @@ -642,7 +642,7 @@ impl Tholos { // transfer can't be allocated the same not-yet-incremented id. let id: u64 = Self::get(&env, &DataKey::NextId)?; env.storage().instance().set(&DataKey::NextId, &(id + 1)); - let assertion = Assertion { + let mut assertion = Assertion { asserter: asserter.clone(), final_outcome: None, outcome, @@ -659,11 +659,19 @@ impl Tholos { Self::set_assertion(&env, id, &assertion); let token_id: Address = Self::get(&env, &DataKey::Token)?; - token::Client::new(&env, &token_id).transfer( - &asserter, - env.current_contract_address(), - &bond_amount, - ); + let token_client = token::Client::new(&env, &token_id); + let contract_address = env.current_contract_address(); + let balance_before = token_client.balance(&contract_address); + token_client.transfer(&asserter, &contract_address, &bond_amount); + let balance_after = token_client.balance(&contract_address); + let received = balance_after.saturating_sub(balance_before); + if received <= 0 { + return Err(Error::InvalidBondAmount); + } + if received != bond_amount { + assertion.bond = received; + Self::set_assertion(&env, id, &assertion); + } Asserted { id, @@ -714,11 +722,15 @@ impl Tholos { Self::set_assertion(&env, id, &assertion); let token_id: Address = Self::get(&env, &DataKey::Token)?; - token::Client::new(&env, &token_id).transfer( - &disputer, - env.current_contract_address(), - &assertion.bond, - ); + let token_client = token::Client::new(&env, &token_id); + let contract_address = env.current_contract_address(); + let balance_before = token_client.balance(&contract_address); + token_client.transfer(&disputer, &contract_address, &assertion.bond); + let balance_after = token_client.balance(&contract_address); + let received = balance_after.saturating_sub(balance_before); + if received <= 0 { + return Err(Error::InvalidBondAmount); + } Disputed { id, disputer }.publish(&env); @@ -759,13 +771,6 @@ impl Tholos { return Err(Error::ChallengeWindowOpen); } - let reward_bps: u32 = Self::get(&env, &DataKey::FinalizeRewardBps)?; - let reward = if reward_bps > 0 { - assertion.bond * (reward_bps as i128) / 10_000 - } else { - 0 - }; - // State is written before the external token transfers below so that // a reentrant call from a non-standard token sees this assertion as // already resolved, rather than still `Pending`. @@ -774,8 +779,17 @@ impl Tholos { assertion.finalizer = Some(caller.clone()); Self::set_assertion(&env, id, &assertion); + let reward_bps: u32 = Self::get(&env, &DataKey::FinalizeRewardBps)?; let token_id: Address = Self::get(&env, &DataKey::Token)?; let token_client = token::Client::new(&env, &token_id); + let contract_balance = token_client.balance(&env.current_contract_address()); + let total_payout = assertion.bond.min(contract_balance); + + let reward = if reward_bps > 0 { + total_payout * (reward_bps as i128) / 10_000 + } else { + 0 + }; if reward > 0 { // Pay the caller their reward first, then pay the asserter the @@ -784,12 +798,14 @@ impl Tholos { token_client.transfer(&env.current_contract_address(), &caller, &reward); } - let asserter_payout = assertion.bond - reward; - token_client.transfer( - &env.current_contract_address(), - &assertion.asserter, - &asserter_payout, - ); + let asserter_payout = total_payout - reward; + if asserter_payout > 0 { + token_client.transfer( + &env.current_contract_address(), + &assertion.asserter, + &asserter_payout, + ); + } Finalized { id, @@ -850,7 +866,6 @@ impl Tholos { return Ok(None); }; - let payout = assertion.bond * 2; let winner = if winner_is_asserter { assertion.asserter.clone() } else { @@ -874,11 +889,12 @@ impl Tholos { Self::set_assertion(&env, id, &assertion); let token_id: Address = Self::get(&env, &DataKey::Token)?; - token::Client::new(&env, &token_id).transfer( - &env.current_contract_address(), - &winner, - &payout, - ); + let token_client = token::Client::new(&env, &token_id); + let contract_balance = token_client.balance(&env.current_contract_address()); + let payout = (assertion.bond.saturating_mul(2)).min(contract_balance); + if payout > 0 { + token_client.transfer(&env.current_contract_address(), &winner, &payout); + } Resolved { id, outcome: final_outcome, diff --git a/contracts/tholos/src/test.rs b/contracts/tholos/src/test.rs index aeb0e41..df4f9d7 100644 --- a/contracts/tholos/src/test.rs +++ b/contracts/tholos/src/test.rs @@ -1821,6 +1821,192 @@ fn test_finalize_is_not_reentrant() { assert_eq!(evil_token.balance(&asserter), 1_000); } +// --------------------------------------------------------------------------- +// Fee-on-transfer token tests +// --------------------------------------------------------------------------- + +mod fee_token { + use super::*; + use soroban_sdk::Map; + + #[contracttype] + pub enum DataKey { + Balances, + FeeBps, + } + + #[contract] + pub struct FeeToken; + + #[contractimpl] + impl FeeToken { + pub fn set_fee_bps(env: Env, bps: u32) { + env.storage().instance().set(&DataKey::FeeBps, &bps); + } + + pub fn credit(env: Env, addr: Address, amount: i128) { + let mut balances = Self::balances(&env); + let current = balances.get(addr.clone()).unwrap_or(0); + balances.set(addr, current + amount); + env.storage().instance().set(&DataKey::Balances, &balances); + } + + pub fn balance(env: Env, addr: Address) -> i128 { + Self::balances(&env).get(addr).unwrap_or(0) + } + + pub fn transfer(env: Env, from: Address, to: Address, amount: i128) { + let fee_bps: u32 = env + .storage() + .instance() + .get(&DataKey::FeeBps) + .unwrap_or(1_000); + + let fee = amount * (fee_bps as i128) / 10_000; + let received = amount - fee; + + let mut balances = Self::balances(&env); + let from_bal = balances.get(from.clone()).unwrap_or(0); + assert!(from_bal >= amount, "insufficient balance"); + let to_bal = balances.get(to.clone()).unwrap_or(0); + + balances.set(from, from_bal - amount); + balances.set(to, to_bal + received); + env.storage().instance().set(&DataKey::Balances, &balances); + } + + fn balances(env: &Env) -> Map { + env.storage() + .instance() + .get(&DataKey::Balances) + .unwrap_or(Map::new(env)) + } + } +} + +fn fee_fixture( + env: &Env, + fee_bps: u32, +) -> ( + fee_token::FeeTokenClient<'static>, + TholosClient<'static>, + Address, + Vec
, +) { + use fee_token::FeeToken; + + let fee_token_id = env.register(FeeToken, ()); + let fee_token = fee_token::FeeTokenClient::new(env, &fee_token_id); + fee_token.set_fee_bps(&fee_bps); + + let resolvers = Vec::from_array( + env, + [ + Address::generate(env), + Address::generate(env), + Address::generate(env), + ], + ); + let contract_id = env.register(Tholos, ()); + let client = TholosClient::new(env, &contract_id); + + let admin = Address::generate(env); + client.initialize( + &admin, + &fee_token_id, + &DEFAULT_BOND, + &DEFAULT_WINDOW, + &resolvers, + &0u32, + ); + + (fee_token, client, contract_id, resolvers) +} + +#[test] +fn test_fee_on_transfer_token_dispute_resolves_without_deadlock() { + let env = Env::default(); + env.mock_all_auths(); + let (fee_token, client, contract_id, resolvers) = fee_fixture(&env, 1_000); // 10% fee + + let asserter = Address::generate(&env); + let disputer = Address::generate(&env); + fee_token.credit(&asserter, &1_000); + fee_token.credit(&disputer, &1_000); + + // assert_outcome requests DEFAULT_BOND (100). + // With 10% fee, 90 arrives in the contract. + let id = client.assert_outcome(&asserter, &true); + let assertion = client.get_assertion_state(&id); + assert_eq!(assertion.bond, 90); + assert_eq!(fee_token.balance(&contract_id), 90); + assert_eq!(fee_token.balance(&asserter), 900); + + // dispute requests assertion.bond (90). + // With 10% fee, 81 arrives in the contract. + // Total in contract is now 90 + 81 = 171. + client.dispute(&disputer, &id); + assert_eq!(fee_token.balance(&contract_id), 171); + assert_eq!(fee_token.balance(&disputer), 910); + + // Resolvers vote to reach strict majority (2 out of 3). + client.resolve(&resolvers.get(0).unwrap(), &id, &false); + let outcome = client.resolve(&resolvers.get(1).unwrap(), &id, &false); + assert_eq!(outcome, Some(false)); + + // Payout was capped at available balance (171), not nominal 90 * 2 = 180, + // so it did not deadlock or panic. + // Disputer (winner) receives 171 - 10% fee = 171 - 17 = 154 tokens. + assert_eq!(fee_token.balance(&contract_id), 0); + assert_eq!(fee_token.balance(&disputer), 910 + 154); + + let state = client.get_assertion_state(&id); + assert_eq!(state.status, Status::Resolved); + assert_eq!(state.final_outcome, Some(false)); +} + +#[test] +fn test_fee_on_transfer_token_finalize_resolves_without_deadlock() { + let env = Env::default(); + env.mock_all_auths(); + let (fee_token, client, contract_id, _resolvers) = fee_fixture(&env, 1_000); // 10% fee + + let asserter = Address::generate(&env); + let caller = Address::generate(&env); + fee_token.credit(&asserter, &1_000); + + let id = client.assert_outcome(&asserter, &true); + let assertion = client.get_assertion_state(&id); + assert_eq!(assertion.bond, 90); + assert_eq!(fee_token.balance(&contract_id), 90); + + env.ledger().with_mut(|l| l.timestamp += DEFAULT_WINDOW + 1); + + let outcome = client.finalize(&caller, &id); + assert!(outcome); + + assert_eq!(fee_token.balance(&contract_id), 0); + // Asserter receives 90 - 10% fee = 81 + assert_eq!(fee_token.balance(&asserter), 900 + 81); + + let state = client.get_assertion_state(&id); + assert_eq!(state.status, Status::Resolved); +} + +#[test] +fn test_zero_received_transfer_rejected() { + let env = Env::default(); + env.mock_all_auths(); + // 100% fee token: delivers 0 tokens on transfer + let (fee_token, client, _contract_id, _resolvers) = fee_fixture(&env, 10_000); + + let asserter = Address::generate(&env); + fee_token.credit(&asserter, &1_000); + + let result = client.try_assert_outcome(&asserter, &true); + assert_eq!(result, Err(Ok(Error::InvalidBondAmount))); +} + // --------------------------------------------------------------------------- // Property-based tests for resolver vote counting and majority logic // --------------------------------------------------------------------------- diff --git a/contracts/tholos/test_snapshots/test/test_fee_on_transfer_token_dispute_resolves_without_deadlock.1.json b/contracts/tholos/test_snapshots/test/test_fee_on_transfer_token_dispute_resolves_without_deadlock.1.json new file mode 100644 index 0000000..6d3504c --- /dev/null +++ b/contracts/tholos/test_snapshots/test/test_fee_on_transfer_token_dispute_resolves_without_deadlock.1.json @@ -0,0 +1,633 @@ +{ + "generators": { + "address": 8, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "100" + }, + { + "u64": "3600" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "assert_outcome", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "dispute", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "u64": "0" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "resolve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u64": "0" + }, + { + "bool": false + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "resolve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": "0" + }, + { + "bool": false + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Balances" + } + ] + }, + "val": { + "map": [ + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + "val": { + "i128": "900" + } + }, + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + "val": { + "i128": "1064" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "FeeBps" + } + ] + }, + "val": { + "u32": 1000 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": "2032731177588607455" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "vec": [ + { + "symbol": "Assertion" + }, + { + "u64": "0" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "asserter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + }, + { + "key": { + "symbol": "bond" + }, + "val": { + "i128": "90" + } + }, + { + "key": { + "symbol": "disputer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + }, + { + "key": { + "symbol": "final_outcome" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "finalizer" + }, + "val": "void" + }, + { + "key": { + "symbol": "opened_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "outcome" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "resolvers" + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Resolved" + } + ] + } + }, + { + "key": { + "symbol": "voted" + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + { + "key": { + "symbol": "votes_against_outcome" + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "symbol": "votes_for_outcome" + }, + "val": { + "u32": 0 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "BondAmount" + } + ] + }, + "val": { + "i128": "100" + } + }, + { + "key": { + "vec": [ + { + "symbol": "ChallengeWindow" + } + ] + }, + "val": { + "u64": "3600" + } + }, + { + "key": { + "vec": [ + { + "symbol": "FinalizeRewardBps" + } + ] + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextId" + } + ] + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Resolvers" + } + ] + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Token" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 518400 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/tholos/test_snapshots/test/test_fee_on_transfer_token_finalize_resolves_without_deadlock.1.json b/contracts/tholos/test_snapshots/test/test_fee_on_transfer_token_finalize_resolves_without_deadlock.1.json new file mode 100644 index 0000000..2152764 --- /dev/null +++ b/contracts/tholos/test_snapshots/test/test_fee_on_transfer_token_finalize_resolves_without_deadlock.1.json @@ -0,0 +1,514 @@ +{ + "generators": { + "address": 8, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "100" + }, + { + "u64": "3600" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "assert_outcome", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "finalize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "u64": "0" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 3601, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Balances" + } + ] + }, + "val": { + "map": [ + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + "val": { + "i128": "981" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "FeeBps" + } + ] + }, + "val": { + "u32": 1000 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "vec": [ + { + "symbol": "Assertion" + }, + { + "u64": "0" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "asserter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + }, + { + "key": { + "symbol": "bond" + }, + "val": { + "i128": "90" + } + }, + { + "key": { + "symbol": "disputer" + }, + "val": "void" + }, + { + "key": { + "symbol": "final_outcome" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "finalizer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + }, + { + "key": { + "symbol": "opened_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "outcome" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "resolvers" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Resolved" + } + ] + } + }, + { + "key": { + "symbol": "voted" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "votes_against_outcome" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "votes_for_outcome" + }, + "val": { + "u32": 0 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "BondAmount" + } + ] + }, + "val": { + "i128": "100" + } + }, + { + "key": { + "vec": [ + { + "symbol": "ChallengeWindow" + } + ] + }, + "val": { + "u64": "3600" + } + }, + { + "key": { + "vec": [ + { + "symbol": "FinalizeRewardBps" + } + ] + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextId" + } + ] + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Resolvers" + } + ] + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Token" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 518400 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/tholos/test_snapshots/test/test_zero_received_transfer_rejected.1.json b/contracts/tholos/test_snapshots/test/test_zero_received_transfer_rejected.1.json new file mode 100644 index 0000000..8dea76a --- /dev/null +++ b/contracts/tholos/test_snapshots/test/test_zero_received_transfer_rejected.1.json @@ -0,0 +1,292 @@ +{ + "generators": { + "address": 7, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "100" + }, + { + "u64": "3600" + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Balances" + } + ] + }, + "val": { + "map": [ + { + "key": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + "val": { + "i128": "1000" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "FeeBps" + } + ] + }, + "val": { + "u32": 10000 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "BondAmount" + } + ] + }, + "val": { + "i128": "100" + } + }, + { + "key": { + "vec": [ + { + "symbol": "ChallengeWindow" + } + ] + }, + "val": { + "u64": "3600" + } + }, + { + "key": { + "vec": [ + { + "symbol": "FinalizeRewardBps" + } + ] + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextId" + } + ] + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Resolvers" + } + ] + }, + "val": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Token" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 518400 + } + ] + }, + "events": [] +} \ No newline at end of file