From 36fb6573eaff3ead885dc1342cb58e85577459f6 Mon Sep 17 00:00:00 2001 From: OSEH-svg Date: Mon, 30 Mar 2026 12:46:45 +0100 Subject: [PATCH] feat(commitment_core): unit-tests-settle-path-expired-commitment-nft-coordination-a --- contracts/commitment_core/src/fee_tests.rs | 131 +++++++---- contracts/commitment_core/src/lib.rs | 20 +- .../commitment_core/src/test_zero_address.rs | 13 +- contracts/commitment_core/src/tests.rs | 210 ++++++++++++++++- .../tests/test_allocate_event.1.json | 217 +++++++++++++++++- .../test_check_violations_not_found.1.json | 2 +- .../tests/test_create_commitment_event.1.json | 59 +++++ .../tests/test_early_exit_event.1.json | 2 +- .../tests/test_get_admin.1.json | 62 ++++- .../tests/test_get_nft_contract.1.json | 62 ++++- .../tests/test_get_owner_commitments.1.json | 62 ++++- .../tests/test_get_total_commitments.1.json | 62 ++++- .../tests/test_initialize.1.json | 62 ++++- .../tests/test_update_value_event.1.json | 59 +++++ 14 files changed, 941 insertions(+), 82 deletions(-) diff --git a/contracts/commitment_core/src/fee_tests.rs b/contracts/commitment_core/src/fee_tests.rs index e47a6420..aa21b7d7 100644 --- a/contracts/commitment_core/src/fee_tests.rs +++ b/contracts/commitment_core/src/fee_tests.rs @@ -11,13 +11,38 @@ use crate::{CommitmentCoreContract, CommitmentCoreContractClient, CommitmentRules}; use soroban_sdk::{ - testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, - token, Address, Env, IntoVal, String, Symbol, + contract, contractimpl, + testutils::{Address as _, Ledger}, + token::{Client as TokenClient, StellarAssetClient}, + Address, Env, String, }; -fn create_token_contract<'a>(e: &Env, admin: &Address) -> (Address, token::Client<'a>) { - let addr = e.register_stellar_asset_contract(admin.clone()); - (addr.clone(), token::Client::new(e, &addr)) +#[contract] +struct FeeTestMockNft; + +#[contractimpl] +impl FeeTestMockNft { + pub fn mint( + _e: Env, + _caller: Address, + _owner: Address, + _commitment_id: String, + _duration_days: u32, + _max_loss_percent: u32, + _commitment_type: String, + _initial_amount: i128, + _asset_address: Address, + _early_exit_penalty: u32, + ) -> u32 { + 1 + } + pub fn settle(_e: Env, _caller: Address, _token_id: u32) {} + pub fn mark_inactive(_e: Env, _caller: Address, _token_id: u32) {} +} + +fn create_token_contract(e: &Env, admin: &Address) -> Address { + let contract = e.register_stellar_asset_contract_v2(admin.clone()); + contract.address() } fn setup_test() -> ( @@ -26,26 +51,25 @@ fn setup_test() -> ( Address, Address, Address, - token::Client<'static>, CommitmentCoreContractClient<'static>, ) { let e = Env::default(); e.mock_all_auths(); let admin = Address::generate(&e); - let nft_contract = Address::generate(&e); + let nft_contract_id = e.register_contract(None, FeeTestMockNft); let user = Address::generate(&e); - let (token_address, token_client) = create_token_contract(&e, &admin); + let token_address = create_token_contract(&e, &admin); - // Mint tokens to user - token_client.mint(&user, &10_000_000); + // Mint tokens to user via StellarAssetClient + StellarAssetClient::new(&e, &token_address).mint(&user, &10_000_000); let contract_id = e.register_contract(None, CommitmentCoreContract); let client = CommitmentCoreContractClient::new(&e, &contract_id); - client.initialize(&admin, &nft_contract); + client.initialize(&admin, &nft_contract_id); - (e, admin, nft_contract, user, token_address, token_client, client) + (e, admin, nft_contract_id, user, token_address, client) } fn default_rules(e: &Env) -> CommitmentRules { @@ -65,7 +89,7 @@ fn default_rules(e: &Env) -> CommitmentRules { #[test] fn test_set_creation_fee_bps() { - let (e, admin, _, _, _, _, client) = setup_test(); + let (e, admin, _, _, _, client) = setup_test(); // Set creation fee to 1% (100 bps) client.set_creation_fee_bps(&admin, &100); @@ -77,7 +101,7 @@ fn test_set_creation_fee_bps() { #[test] #[should_panic(expected = "Invalid fee basis points")] fn test_set_creation_fee_bps_invalid() { - let (e, admin, _, _, _, _, client) = setup_test(); + let (e, admin, _, _, _, client) = setup_test(); // Try to set fee > 10000 bps (100%) client.set_creation_fee_bps(&admin, &10001); @@ -86,7 +110,7 @@ fn test_set_creation_fee_bps_invalid() { #[test] #[should_panic(expected = "Unauthorized")] fn test_set_creation_fee_bps_unauthorized() { - let (e, _, _, user, _, _, client) = setup_test(); + let (e, _, _, user, _, client) = setup_test(); // Non-admin tries to set fee client.set_creation_fee_bps(&user, &100); @@ -94,7 +118,7 @@ fn test_set_creation_fee_bps_unauthorized() { #[test] fn test_create_commitment_with_zero_fee() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); // No fee set (defaults to 0) let amount = 1_000_000i128; @@ -113,7 +137,7 @@ fn test_create_commitment_with_zero_fee() { #[test] fn test_create_commitment_with_creation_fee() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); // Set 1% creation fee (100 bps) client.set_creation_fee_bps(&admin, &100); @@ -139,7 +163,7 @@ fn test_create_commitment_with_creation_fee() { #[test] fn test_create_commitment_with_max_fee() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); // Set 100% creation fee (10000 bps) - extreme case client.set_creation_fee_bps(&admin, &10000); @@ -161,7 +185,7 @@ fn test_create_commitment_with_max_fee() { #[test] fn test_create_commitment_fee_rounds_down() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); // Set 0.15% creation fee (15 bps) client.set_creation_fee_bps(&admin, &15); @@ -181,7 +205,7 @@ fn test_create_commitment_fee_rounds_down() { #[test] fn test_multiple_commitments_accumulate_fees() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); // Set 1% creation fee client.set_creation_fee_bps(&admin, &100); @@ -208,7 +232,8 @@ fn test_multiple_commitments_accumulate_fees() { #[test] fn test_early_exit_penalty_retained_as_fee() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); + let token_client = TokenClient::new(&e, &token_address); let amount = 1_000_000i128; let mut rules = default_rules(&e); @@ -225,13 +250,14 @@ fn test_early_exit_penalty_retained_as_fee() { // Verify penalty was added to collected fees assert_eq!(client.get_collected_fees(&token_address), expected_penalty); - // Verify user received net amount - assert_eq!(token_client.balance(&user), expected_returned); + // Verify user received net amount (Initial 10M - 1M commitment + 900k returned) + assert_eq!(token_client.balance(&user), 9_900_000); } #[test] fn test_early_exit_with_creation_fee_and_penalty() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); + let token_client = TokenClient::new(&e, &token_address); // Set 1% creation fee client.set_creation_fee_bps(&admin, &100); @@ -255,8 +281,8 @@ fn test_early_exit_with_creation_fee_and_penalty() { // Verify both fees were collected assert_eq!(client.get_collected_fees(&token_address), total_fees); - // Verify user received correct amount - assert_eq!(token_client.balance(&user), expected_returned); + // Verify user received correct amount (Initial 10M - 1M commitment + 891k returned) + assert_eq!(token_client.balance(&user), 9_891_000); } // ============================================================================ @@ -265,7 +291,7 @@ fn test_early_exit_with_creation_fee_and_penalty() { #[test] fn test_set_fee_recipient() { - let (e, admin, _, _, _, _, client) = setup_test(); + let (e, admin, _, _, _, client) = setup_test(); let recipient = Address::generate(&e); client.set_fee_recipient(&admin, &recipient); @@ -276,7 +302,7 @@ fn test_set_fee_recipient() { #[test] #[should_panic(expected = "Zero address")] fn test_set_fee_recipient_zero_address() { - let (e, admin, _, _, _, _, client) = setup_test(); + let (e, admin, _, _, _, client) = setup_test(); let zero_str = String::from_str(&e, "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"); let zero_addr = Address::from_string(&zero_str); @@ -287,7 +313,7 @@ fn test_set_fee_recipient_zero_address() { #[test] #[should_panic(expected = "Unauthorized")] fn test_set_fee_recipient_unauthorized() { - let (e, _, _, user, _, _, client) = setup_test(); + let (e, _, _, user, _, client) = setup_test(); let recipient = Address::generate(&e); client.set_fee_recipient(&user, &recipient); @@ -299,7 +325,8 @@ fn test_set_fee_recipient_unauthorized() { #[test] fn test_withdraw_fees() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); + let token_client = TokenClient::new(&e, &token_address); let recipient = Address::generate(&e); client.set_fee_recipient(&admin, &recipient); @@ -324,7 +351,8 @@ fn test_withdraw_fees() { #[test] fn test_withdraw_partial_fees() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); + let token_client = TokenClient::new(&e, &token_address); let recipient = Address::generate(&e); client.set_fee_recipient(&admin, &recipient); @@ -352,7 +380,7 @@ fn test_withdraw_partial_fees() { #[test] #[should_panic(expected = "Fee recipient not set")] fn test_withdraw_fees_no_recipient() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); // Collect fees but don't set recipient client.set_creation_fee_bps(&admin, &100); @@ -366,7 +394,7 @@ fn test_withdraw_fees_no_recipient() { #[test] #[should_panic(expected = "Insufficient collected fees")] fn test_withdraw_fees_insufficient() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); let recipient = Address::generate(&e); client.set_fee_recipient(&admin, &recipient); @@ -383,7 +411,7 @@ fn test_withdraw_fees_insufficient() { #[test] #[should_panic(expected = "Unauthorized")] fn test_withdraw_fees_unauthorized() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); let recipient = Address::generate(&e); client.set_fee_recipient(&admin, &recipient); @@ -400,7 +428,7 @@ fn test_withdraw_fees_unauthorized() { #[test] #[should_panic(expected = "Invalid amount")] fn test_withdraw_fees_zero_amount() { - let (e, admin, _, _, token_address, _, client) = setup_test(); + let (e, admin, _, _, token_address, client) = setup_test(); let recipient = Address::generate(&e); client.set_fee_recipient(&admin, &recipient); @@ -415,7 +443,7 @@ fn test_withdraw_fees_zero_amount() { #[test] fn test_get_creation_fee_bps_default() { - let (e, _, _, _, _, _, client) = setup_test(); + let (_, _, _, _, _, client) = setup_test(); // Default should be 0 assert_eq!(client.get_creation_fee_bps(), 0); @@ -423,7 +451,7 @@ fn test_get_creation_fee_bps_default() { #[test] fn test_get_fee_recipient_default() { - let (e, _, _, _, _, _, client) = setup_test(); + let (_, _, _, _, _, client) = setup_test(); // Default should be None assert_eq!(client.get_fee_recipient(), None); @@ -431,7 +459,7 @@ fn test_get_fee_recipient_default() { #[test] fn test_get_collected_fees_default() { - let (e, _, _, _, token_address, _, client) = setup_test(); + let (_, _, _, _, token_address, client) = setup_test(); // Default should be 0 assert_eq!(client.get_collected_fees(&token_address), 0); @@ -439,14 +467,14 @@ fn test_get_collected_fees_default() { #[test] fn test_get_collected_fees_multiple_assets() { - let (e, admin, _, user, _, _, client) = setup_test(); + let (e, admin, _, user, _, client) = setup_test(); // Create two different tokens - let (token1, token1_client) = create_token_contract(&e, &admin); - let (token2, token2_client) = create_token_contract(&e, &admin); + let token1 = create_token_contract(&e, &admin); + let token2 = create_token_contract(&e, &admin); - token1_client.mint(&user, &10_000_000); - token2_client.mint(&user, &10_000_000); + StellarAssetClient::new(&e, &token1).mint(&user, &10_000_000); + StellarAssetClient::new(&e, &token2).mint(&user, &10_000_000); // Set creation fee client.set_creation_fee_bps(&admin, &100); @@ -468,7 +496,8 @@ fn test_get_collected_fees_multiple_assets() { #[test] fn test_fee_collection_with_settle() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); + let token_client = TokenClient::new(&e, &token_address); // Set creation fee client.set_creation_fee_bps(&admin, &100); @@ -478,24 +507,25 @@ fn test_fee_collection_with_settle() { let net_amount = amount - creation_fee; let mut rules = default_rules(&e); - rules.duration_days = 0; // Expires immediately + rules.duration_days = 1; let commitment_id = client.create_commitment(&user, &amount, &token_address, &rules); // Settle commitment - e.ledger().with_mut(|li| li.timestamp = li.timestamp + 1); + e.ledger().with_mut(|li| li.timestamp += 86400 + 1); client.settle(&commitment_id); + // Verify user received net amount (Initial 10M - 1M commitment + 990k returned) + assert_eq!(token_client.balance(&user), 9_990_000); + // Verify creation fee still collected assert_eq!(client.get_collected_fees(&token_address), creation_fee); - - // Verify user got back net amount - assert_eq!(token_client.balance(&user), net_amount); } #[test] fn test_complete_fee_lifecycle() { - let (e, admin, _, user, token_address, token_client, client) = setup_test(); + let (e, admin, _, user, token_address, client) = setup_test(); + let token_client = TokenClient::new(&e, &token_address); let recipient = Address::generate(&e); @@ -514,7 +544,6 @@ fn test_complete_fee_lifecycle() { // 3. Early exit with penalty client.early_exit(&commitment_id, &user); - let net_amount = amount - creation_fee; let exit_penalty = 99_000i128; // 10% of 990,000 let total_fees = creation_fee + exit_penalty; diff --git a/contracts/commitment_core/src/lib.rs b/contracts/commitment_core/src/lib.rs index 0d9a544c..5125f9bb 100644 --- a/contracts/commitment_core/src/lib.rs +++ b/contracts/commitment_core/src/lib.rs @@ -707,9 +707,23 @@ impl CommitmentCoreContract { /// Settle an expired commitment, release assets to the owner, and mark the NFT settled. /// - /// Cross-contract dependency: invokes `commitment_nft::settle` after the core state and - /// token transfer path have been prepared. This flow is guarded by the reentrancy flag and - /// relies on transaction rollback if the downstream NFT call fails. + /// Settles an expired commitment, transfers assets back to the owner, and notifies the NFT contract. + /// + /// # Arguments + /// * `commitment_id` - Unique identifier of the commitment to settle. + /// + /// # Panics + /// * `CommitmentNotFound` - If the commitment ID doesn't exist. + /// * `NotExpired` - If the current ledger time is less than the commitment's expiration time. + /// * `AlreadySettled` - If the commitment is already in 'settled' status. + /// * `NotActive` - If the commitment is not currently 'active'. + /// * `NotInitialized` - If the contract state is missing dependencies. + /// + /// # Security + /// * Guarded by a reentrancy flag. + /// * Follows the check-effects-interactions pattern: status updated before assets transferred. + /// * Cross-contract dependency: invokes `commitment_nft::settle` after the core state and + /// token transfer path have been prepared. pub fn settle(e: Env, commitment_id: String) { require_no_reentrancy(&e); set_reentrancy_guard(&e, true); diff --git a/contracts/commitment_core/src/test_zero_address.rs b/contracts/commitment_core/src/test_zero_address.rs index 1ecd7991..39ad602b 100644 --- a/contracts/commitment_core/src/test_zero_address.rs +++ b/contracts/commitment_core/src/test_zero_address.rs @@ -2,7 +2,7 @@ extern crate std; use crate::*; -use soroban_sdk::{Address, Env, String}; +use soroban_sdk::{testutils::Address as _, Address, Env, String}; fn generate_zero_address(env: &Env) -> Address { Address::from_string(&String::from_str( @@ -25,12 +25,13 @@ fn test_create_commitment_zero_owner_fails() { let amount: i128 = 100_000_000; let asset_address = Address::generate(&env); - // Corrected field names for the Commitlabs CommitmentRules struct let rules = CommitmentRules { - min_commitment_amount: 0, - max_commitment_amount: i128::MAX, - min_duration: 0, - max_duration: u64::MAX, + duration_days: 30, + max_loss_percent: 10, + commitment_type: String::from_str(&env, "safe"), + early_exit_penalty: 15, + min_fee_threshold: 0, + grace_period_days: 0, }; client.create_commitment(&zero_owner, &amount, &asset_address, &rules); diff --git a/contracts/commitment_core/src/tests.rs b/contracts/commitment_core/src/tests.rs index ff6c11c3..1c2ccde5 100644 --- a/contracts/commitment_core/src/tests.rs +++ b/contracts/commitment_core/src/tests.rs @@ -73,7 +73,11 @@ mod instrumented_nft { 7 } - pub fn settle(_e: Env, _caller: Address, _token_id: u32) {} + pub fn settle(e: Env, caller: Address, token_id: u32) { + e.storage().instance().set(&symbol_short!("set_call"), &true); + e.storage().instance().set(&symbol_short!("set_tid"), &token_id); + e.storage().instance().set(&symbol_short!("set_clr"), &caller); + } pub fn mark_inactive(_e: Env, _caller: Address, _token_id: u32) {} } @@ -583,13 +587,17 @@ fn test_create_commitment_zero_address_fails() { #[should_panic(expected = "Duration would cause expiration timestamp overflow")] fn test_create_commitment_expiration_overflow() { let e = Env::default(); - e.mock_all_auths(); + e.mock_all_auths_allowing_non_root_auth(); let contract_id = e.register_contract(None, CommitmentCoreContract); + let nft_contract = e.register_contract(None, MockNftContract); let admin = Address::generate(&e); - let nft_contract = Address::generate(&e); let owner = Address::generate(&e); - let asset_address = Address::generate(&e); + let token_admin = Address::generate(&e); + + let token_contract = e.register_stellar_asset_contract_v2(token_admin); + let asset_address = token_contract.address(); + StellarAssetClient::new(&e, &asset_address).mint(&owner, &10_000); e.as_contract(&contract_id, || { CommitmentCoreContract::initialize(e.clone(), admin.clone(), nft_contract.clone()); @@ -1642,6 +1650,196 @@ fn test_settle_rejects_when_not_expired() { }); } +#[test] +/// settle succeeds when commitment has reached or passed expiration (Issue #115). +fn test_settle_success_expired() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + + let contract_id = e.register_contract(None, CommitmentCoreContract); + let nft_contract = e.register_contract(None, MockNftContract); + let admin = Address::generate(&e); + let owner = Address::generate(&e); + let token_admin = Address::generate(&e); + let commitment_id = "settle_success"; + + let token_contract = e.register_stellar_asset_contract_v2(token_admin); + let asset_address = token_contract.address(); + let amount = 1000i128; + StellarAssetClient::new(&e, &asset_address).mint(&contract_id, &amount); + + e.as_contract(&contract_id, || { + CommitmentCoreContract::initialize(e.clone(), admin.clone(), nft_contract.clone()); + }); + + let created_at = 1000u64; + let duration_days = 30u32; + let expires_at = created_at + (duration_days as u64) * 86400; + + let mut commitment = create_test_commitment( + &e, + commitment_id, + &owner, + amount, + amount, + 10, + duration_days, + created_at + ); + commitment.asset_address = asset_address.clone(); + store_commitment(&e, &contract_id, &commitment); + + // Update TVL as create_commitment would + e.as_contract(&contract_id, || { + e.storage().instance().set(&DataKey::TotalValueLocked, &amount); + let mut owner_commitments = Vec::new(&e); + owner_commitments.push_back(String::from_str(&e, commitment_id)); + e.storage().instance().set(&DataKey::OwnerCommitments(owner.clone()), &owner_commitments); + }); + + e.ledger().with_mut(|l| { + l.timestamp = expires_at; + }); + + e.as_contract(&contract_id, || { + CommitmentCoreContract::settle(e.clone(), String::from_str(&e, commitment_id)); + }); + + let settled = e.as_contract(&contract_id, || { + CommitmentCoreContract::get_commitment(e.clone(), String::from_str(&e, commitment_id)) + }); + + assert_eq!(settled.status, String::from_str(&e, "settled")); + + let tvl = e.as_contract(&contract_id, || { + CommitmentCoreContract::get_total_value_locked(e.clone()) + }); + assert_eq!(tvl, 0); + + let owner_commitments = e.as_contract(&contract_id, || { + CommitmentCoreContract::get_owner_commitments(e.clone(), owner.clone()) + }); + assert_eq!(owner_commitments.len(), 0); +} + +#[test] +/// settle must coordinate with the NFT contract (Issue #115). +fn test_settle_nft_coordination() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + + let contract_id = e.register_contract(None, CommitmentCoreContract); + let nft_contract = e.register_contract(None, instrumented_nft::InstrumentedNftContract); + let admin = Address::generate(&e); + let owner = Address::generate(&e); + let token_admin = Address::generate(&e); + let commitment_id = "settle_nft_coord"; + + let token_contract = e.register_stellar_asset_contract_v2(token_admin); + let asset_address = token_contract.address(); + let amount = 1000i128; + StellarAssetClient::new(&e, &asset_address).mint(&contract_id, &amount); + + e.as_contract(&contract_id, || { + CommitmentCoreContract::initialize(e.clone(), admin.clone(), nft_contract.clone()); + }); + + let created_at = 1000u64; + let duration_days = 30u32; + let expires_at = created_at + (duration_days as u64) * 86400; + let nft_token_id = 123u32; + + let mut commitment = create_test_commitment( + &e, + commitment_id, + &owner, + amount, + amount, + 10, + duration_days, + created_at + ); + commitment.nft_token_id = nft_token_id; + commitment.asset_address = asset_address.clone(); + store_commitment(&e, &contract_id, &commitment); + + e.ledger().with_mut(|l| { + l.timestamp = expires_at; + }); + + e.as_contract(&contract_id, || { + CommitmentCoreContract::settle(e.clone(), String::from_str(&e, commitment_id)); + }); + + // Check if InstrumentedNftContract::settle was called correctly + let (is_called, called_tid, called_clr) = e.as_contract(&nft_contract, || { + let is_called: bool = e.storage().instance().get(&symbol_short!("set_call")).unwrap_or(false); + let called_tid: u32 = e.storage().instance().get(&symbol_short!("set_tid")).unwrap_or(0); + let called_clr: Address = e.storage().instance().get(&symbol_short!("set_clr")).unwrap(); + (is_called, called_tid, called_clr) + }); + + assert!(is_called); + assert_eq!(called_tid, nft_token_id); + assert_eq!(called_clr, contract_id); +} + +#[test] +/// settle must transfer assets back to the owner (Issue #115). +fn test_settle_asset_transfers() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + + let contract_id = e.register_contract(None, CommitmentCoreContract); + let nft_contract = e.register_contract(None, MockNftContract); + let admin = Address::generate(&e); + let owner = Address::generate(&e); + let token_admin = Address::generate(&e); + let amount = 1_000i128; + + let token_contract = e.register_stellar_asset_contract_v2(token_admin); + let asset_address = token_contract.address(); + let token_admin_client = StellarAssetClient::new(&e, &asset_address); + let token_client = TokenClient::new(&e, &asset_address); + + e.as_contract(&contract_id, || { + CommitmentCoreContract::initialize(e.clone(), admin.clone(), nft_contract.clone()); + }); + + // Mint tokens to the core contract to simulate custody + token_admin_client.mint(&contract_id, &amount); + + let created_at = 1000u64; + let duration_days = 30u32; + let expires_at = created_at + (duration_days as u64) * 86400; + let commitment_id = "settle_assets"; + + let mut commitment = create_test_commitment( + &e, + commitment_id, + &owner, + amount, + amount, + 10, + duration_days, + created_at + ); + commitment.asset_address = asset_address.clone(); + store_commitment(&e, &contract_id, &commitment); + + e.ledger().with_mut(|l| { + l.timestamp = expires_at; + }); + + e.as_contract(&contract_id, || { + CommitmentCoreContract::settle(e.clone(), String::from_str(&e, commitment_id)); + }); + + // Verify balances + assert_eq!(token_client.balance(&owner), amount); + assert_eq!(token_client.balance(&contract_id), 0); +} + /// early_exit by owner succeeds; by non-owner (e.g. admin) fails (Issue #116). #[test] #[should_panic(expected = "Unauthorized: caller not allowed")] @@ -1756,6 +1954,7 @@ fn test_allocate_when_settled_fails() { e.as_contract(&contract_id, || { CommitmentCoreContract::allocate( e.clone(), + admin.clone(), String::from_str(&e, commitment_id), target_pool.clone(), 100, @@ -1788,6 +1987,7 @@ fn test_allocate_when_violated_fails() { e.as_contract(&contract_id, || { CommitmentCoreContract::allocate( e.clone(), + admin.clone(), String::from_str(&e, commitment_id), target_pool.clone(), 100, @@ -1820,6 +2020,7 @@ fn test_allocate_when_early_exit_fails() { e.as_contract(&contract_id, || { CommitmentCoreContract::allocate( e.clone(), + admin.clone(), String::from_str(&e, commitment_id), target_pool.clone(), 100, @@ -1858,6 +2059,7 @@ fn test_allocate_when_active_succeeds() { store_commitment(&e, &contract_id, &commitment); client.allocate( + &admin, &String::from_str(&e, commitment_id), &target_pool, &allocation_amount, diff --git a/contracts/commitment_core/test_snapshots/tests/test_allocate_event.1.json b/contracts/commitment_core/test_snapshots/tests/test_allocate_event.1.json index 674a5e83..aa253808 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_allocate_event.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_allocate_event.1.json @@ -1,9 +1,10 @@ { "generators": { - "address": 2, + "address": 4, "nonce": 0 }, "auth": [ + [], [] ], "ledger": { @@ -19,7 +20,7 @@ [ { "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": "ledger_key_contract_instance", "durability": "persistent" } @@ -30,7 +31,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": "ledger_key_contract_instance", "durability": "persistent", "val": { @@ -38,7 +39,111 @@ "executable": { "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, - "storage": null + "storage": [ + { + "key": { + "symbol": "EMG_MODE" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "paused" + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AllCommitmentIds" + } + ] + }, + "val": { + "vec": [] + } + }, + { + "key": { + "vec": [ + { + "symbol": "AuthorizedUpdaters" + } + ] + }, + "val": { + "vec": [] + } + }, + { + "key": { + "vec": [ + { + "symbol": "NftContract" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "ReentrancyGuard" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalCommitments" + } + ] + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalValueLocked" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + } + ] } } } @@ -84,7 +189,88 @@ "symbol": "fn_call" }, { - "bytes": "0000000000000000000000000000000000000000000000000000000000000002" + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "symbol": "initialize" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "EmgMode" + } + ], + "data": { + "vec": [ + { + "symbol": "EMG_OFF" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_return" + }, + { + "symbol": "initialize" + } + ], + "data": "void" + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": null, + "type_": "diagnostic", + "body": { + "v0": { + "topics": [ + { + "symbol": "fn_call" + }, + { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" }, { "symbol": "allocate" @@ -92,11 +278,14 @@ ], "data": { "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, { "string": "test_id" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { "i128": { @@ -114,7 +303,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", "type_": "contract", "body": { "v0": { @@ -147,7 +336,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", "type_": "diagnostic", "body": { "v0": { @@ -161,11 +350,14 @@ { "string": "caught panic 'Commitment not found' from contract function 'Symbol(allocate)'" }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, { "string": "test_id" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { "i128": { @@ -183,7 +375,7 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000002", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000003", "type_": "diagnostic", "body": { "v0": { @@ -232,11 +424,14 @@ }, { "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, { "string": "test_id" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { "i128": { diff --git a/contracts/commitment_core/test_snapshots/tests/test_check_violations_not_found.1.json b/contracts/commitment_core/test_snapshots/tests/test_check_violations_not_found.1.json index ef6f0953..fb487a26 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_check_violations_not_found.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_check_violations_not_found.1.json @@ -88,7 +88,7 @@ "data": { "vec": [ { - "string": "check_violations" + "string": "chk" }, { "string": "Unknown error" diff --git a/contracts/commitment_core/test_snapshots/tests/test_create_commitment_event.1.json b/contracts/commitment_core/test_snapshots/tests/test_create_commitment_event.1.json index 0f62af19..6303a562 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_create_commitment_event.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_create_commitment_event.1.json @@ -39,6 +39,14 @@ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "storage": [ + { + "key": { + "symbol": "EMG_MODE" + }, + "val": { + "bool": false + } + }, { "key": { "symbol": "paused" @@ -71,6 +79,18 @@ "vec": [] } }, + { + "key": { + "vec": [ + { + "symbol": "AuthorizedUpdaters" + } + ] + }, + "val": { + "vec": [] + } + }, { "key": { "vec": [ @@ -83,6 +103,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" } }, + { + "key": { + "vec": [ + { + "symbol": "ReentrancyGuard" + } + ] + }, + "val": { + "bool": false + } + }, { "key": { "vec": [ @@ -177,6 +209,33 @@ }, "failed_call": false }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "EmgMode" + } + ], + "data": { + "vec": [ + { + "symbol": "EMG_OFF" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0", diff --git a/contracts/commitment_core/test_snapshots/tests/test_early_exit_event.1.json b/contracts/commitment_core/test_snapshots/tests/test_early_exit_event.1.json index 1e223b1d..0161fef4 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_early_exit_event.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_early_exit_event.1.json @@ -123,7 +123,7 @@ "data": { "vec": [ { - "string": "early_exit" + "string": "exit" }, { "string": "Unknown error" diff --git a/contracts/commitment_core/test_snapshots/tests/test_get_admin.1.json b/contracts/commitment_core/test_snapshots/tests/test_get_admin.1.json index 708c869f..3c5ff991 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_get_admin.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_get_admin.1.json @@ -40,6 +40,14 @@ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "storage": [ + { + "key": { + "symbol": "EMG_MODE" + }, + "val": { + "bool": false + } + }, { "key": { "symbol": "paused" @@ -72,6 +80,18 @@ "vec": [] } }, + { + "key": { + "vec": [ + { + "symbol": "AuthorizedUpdaters" + } + ] + }, + "val": { + "vec": [] + } + }, { "key": { "vec": [ @@ -84,6 +104,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "vec": [ + { + "symbol": "ReentrancyGuard" + } + ] + }, + "val": { + "bool": false + } + }, { "key": { "vec": [ @@ -144,5 +176,33 @@ ] ] }, - "events": [] + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "EmgMode" + } + ], + "data": { + "vec": [ + { + "symbol": "EMG_OFF" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + } + ] } \ No newline at end of file diff --git a/contracts/commitment_core/test_snapshots/tests/test_get_nft_contract.1.json b/contracts/commitment_core/test_snapshots/tests/test_get_nft_contract.1.json index 708c869f..3c5ff991 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_get_nft_contract.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_get_nft_contract.1.json @@ -40,6 +40,14 @@ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "storage": [ + { + "key": { + "symbol": "EMG_MODE" + }, + "val": { + "bool": false + } + }, { "key": { "symbol": "paused" @@ -72,6 +80,18 @@ "vec": [] } }, + { + "key": { + "vec": [ + { + "symbol": "AuthorizedUpdaters" + } + ] + }, + "val": { + "vec": [] + } + }, { "key": { "vec": [ @@ -84,6 +104,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "vec": [ + { + "symbol": "ReentrancyGuard" + } + ] + }, + "val": { + "bool": false + } + }, { "key": { "vec": [ @@ -144,5 +176,33 @@ ] ] }, - "events": [] + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "EmgMode" + } + ], + "data": { + "vec": [ + { + "symbol": "EMG_OFF" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + } + ] } \ No newline at end of file diff --git a/contracts/commitment_core/test_snapshots/tests/test_get_owner_commitments.1.json b/contracts/commitment_core/test_snapshots/tests/test_get_owner_commitments.1.json index 6f778c52..e902d51a 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_get_owner_commitments.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_get_owner_commitments.1.json @@ -40,6 +40,14 @@ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "storage": [ + { + "key": { + "symbol": "EMG_MODE" + }, + "val": { + "bool": false + } + }, { "key": { "symbol": "paused" @@ -72,6 +80,18 @@ "vec": [] } }, + { + "key": { + "vec": [ + { + "symbol": "AuthorizedUpdaters" + } + ] + }, + "val": { + "vec": [] + } + }, { "key": { "vec": [ @@ -84,6 +104,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "vec": [ + { + "symbol": "ReentrancyGuard" + } + ] + }, + "val": { + "bool": false + } + }, { "key": { "vec": [ @@ -144,5 +176,33 @@ ] ] }, - "events": [] + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "EmgMode" + } + ], + "data": { + "vec": [ + { + "symbol": "EMG_OFF" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + } + ] } \ No newline at end of file diff --git a/contracts/commitment_core/test_snapshots/tests/test_get_total_commitments.1.json b/contracts/commitment_core/test_snapshots/tests/test_get_total_commitments.1.json index 708c869f..3c5ff991 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_get_total_commitments.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_get_total_commitments.1.json @@ -40,6 +40,14 @@ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "storage": [ + { + "key": { + "symbol": "EMG_MODE" + }, + "val": { + "bool": false + } + }, { "key": { "symbol": "paused" @@ -72,6 +80,18 @@ "vec": [] } }, + { + "key": { + "vec": [ + { + "symbol": "AuthorizedUpdaters" + } + ] + }, + "val": { + "vec": [] + } + }, { "key": { "vec": [ @@ -84,6 +104,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "vec": [ + { + "symbol": "ReentrancyGuard" + } + ] + }, + "val": { + "bool": false + } + }, { "key": { "vec": [ @@ -144,5 +176,33 @@ ] ] }, - "events": [] + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "EmgMode" + } + ], + "data": { + "vec": [ + { + "symbol": "EMG_OFF" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + } + ] } \ No newline at end of file diff --git a/contracts/commitment_core/test_snapshots/tests/test_initialize.1.json b/contracts/commitment_core/test_snapshots/tests/test_initialize.1.json index 908631a0..acd2a443 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_initialize.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_initialize.1.json @@ -39,6 +39,14 @@ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "storage": [ + { + "key": { + "symbol": "EMG_MODE" + }, + "val": { + "bool": false + } + }, { "key": { "symbol": "paused" @@ -71,6 +79,18 @@ "vec": [] } }, + { + "key": { + "vec": [ + { + "symbol": "AuthorizedUpdaters" + } + ] + }, + "val": { + "vec": [] + } + }, { "key": { "vec": [ @@ -83,6 +103,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "vec": [ + { + "symbol": "ReentrancyGuard" + } + ] + }, + "val": { + "bool": false + } + }, { "key": { "vec": [ @@ -143,5 +175,33 @@ ] ] }, - "events": [] + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "EmgMode" + } + ], + "data": { + "vec": [ + { + "symbol": "EMG_OFF" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + } + ] } \ No newline at end of file diff --git a/contracts/commitment_core/test_snapshots/tests/test_update_value_event.1.json b/contracts/commitment_core/test_snapshots/tests/test_update_value_event.1.json index bf5218bb..7cd09c25 100644 --- a/contracts/commitment_core/test_snapshots/tests/test_update_value_event.1.json +++ b/contracts/commitment_core/test_snapshots/tests/test_update_value_event.1.json @@ -42,6 +42,14 @@ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "storage": [ + { + "key": { + "symbol": "EMG_MODE" + }, + "val": { + "bool": false + } + }, { "key": { "symbol": "paused" @@ -74,6 +82,18 @@ "vec": [] } }, + { + "key": { + "vec": [ + { + "symbol": "AuthorizedUpdaters" + } + ] + }, + "val": { + "vec": [] + } + }, { "key": { "vec": [ @@ -240,6 +260,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "vec": [ + { + "symbol": "ReentrancyGuard" + } + ] + }, + "val": { + "bool": false + } + }, { "key": { "vec": [ @@ -301,6 +333,33 @@ ] }, "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "EmgMode" + } + ], + "data": { + "vec": [ + { + "symbol": "EMG_OFF" + }, + { + "u64": 0 + } + ] + } + } + } + }, + "failed_call": false + }, { "event": { "ext": "v0",