From 0a46b751ccdabe7acd5b34725efbe1957b48bd17 Mon Sep 17 00:00:00 2001 From: K1NGD4VID Date: Sat, 20 Jun 2026 13:31:36 +0100 Subject: [PATCH 1/2] refactor(remittance_nft): consolidate repayment floor validations and remove dead code --- remittance_nft/src/lib.rs | 40 +- remittance_nft/src/test.rs | 111 ++++ ..._update_score_floor_above_effective.1.json | 506 ++++++++++++++++++ ...est_update_score_floor_boundary_100.1.json | 436 +++++++++++++++ ...test_update_score_floor_boundary_99.1.json | 295 ++++++++++ ..._floor_configured_min_150_repay_120.1.json | 365 +++++++++++++ ..._update_score_floor_exact_effective.1.json | 506 ++++++++++++++++++ 7 files changed, 2242 insertions(+), 17 deletions(-) create mode 100644 remittance_nft/test_snapshots/test/test_update_score_floor_above_effective.1.json create mode 100644 remittance_nft/test_snapshots/test/test_update_score_floor_boundary_100.1.json create mode 100644 remittance_nft/test_snapshots/test/test_update_score_floor_boundary_99.1.json create mode 100644 remittance_nft/test_snapshots/test/test_update_score_floor_configured_min_150_repay_120.1.json create mode 100644 remittance_nft/test_snapshots/test/test_update_score_floor_exact_effective.1.json diff --git a/remittance_nft/src/lib.rs b/remittance_nft/src/lib.rs index 409abdd..20d646b 100644 --- a/remittance_nft/src/lib.rs +++ b/remittance_nft/src/lib.rs @@ -13,7 +13,6 @@ pub enum NftError { NftAlreadyExists = 4, BurnedRequiresApproval = 5, NftNotFound = 6, - InvalidRepaymentAmount = 7, CollateralAlreadySeized = 8, SelfTransfer = 9, DestinationOccupied = 10, @@ -83,7 +82,7 @@ impl RemittanceNFT { /// Dust repayments below this threshold award 0 score points due to integer /// division (`repayment_amount / 100 == 0`) but still write storage and emit /// events, enabling spam attacks. This floor rejects such calls early with - /// InvalidRepaymentAmount (error 7). + /// BelowMinimum (error 17). pub const MIN_SCORE_UPDATE_REPAYMENT: i128 = 100; fn admin_key() -> soroban_sdk::Symbol { @@ -572,26 +571,31 @@ impl RemittanceNFT { .unwrap_or(0) } - /// Update the score for a user's NFT based on a repayment amount. + /// Updates the reputation score of a user's NFT based on their repayment amount. + /// + /// # Validation + /// Consolidates repayment validation into a single floor check. The repayment amount + /// must be at least the effective floor, which is the maximum of the configured + /// minimum repayment amount (`min_repayment`) and the fixed update threshold + /// (`MIN_SCORE_UPDATE_REPAYMENT`, which is 100). + /// + /// # Errors + /// Returns `NftError::BelowMinimum` if the repayment amount is less than the + /// effective floor. pub fn update_score( env: Env, user: Address, repayment_amount: i128, minter: Option
, ) -> Result<(), NftError> { - if repayment_amount <= 0 { - return Err(NftError::InvalidRepaymentAmount); - } - let min_repayment = Self::min_repayment_amount(&env); - if repayment_amount < min_repayment { - return Err(NftError::BelowMinimum); - } + let effective_floor = min_repayment.max(Self::MIN_SCORE_UPDATE_REPAYMENT); - // Reject dust repayments that award zero score points (repayment_amount / 100 == 0) - // but still incur storage writes and event emissions, enabling low-cost spam. - if repayment_amount < Self::MIN_SCORE_UPDATE_REPAYMENT { - return Err(NftError::InvalidRepaymentAmount); + // Single consolidated floor check: the repayment amount must be at least the effective floor. + // This keeps `BelowMinimum` as the surviving error variant to describe consolidated floor check semantics, + // and also rejects non-positive repayments since the effective floor is always >= 100. + if repayment_amount < effective_floor { + return Err(NftError::BelowMinimum); } Self::require_admin_or_authorized_minter(&env, minter)?; @@ -601,9 +605,11 @@ impl RemittanceNFT { // Simple logic: 1 point per 100 units of repayment. let points_i128 = repayment_amount / 100; - if points_i128 == 0 { - return Ok(()); - } + + // Note: The previous check `if points_i128 == 0 { return Ok(()); }` is mathematically + // unreachable. Because the floor check guarantees that `repayment_amount >= effective_floor` + // where `effective_floor` is at least 100, `points_i128` (calculated as `repayment_amount / 100`) + // is guaranteed to be at least 1 (since 100 / 100 = 1). Thus, this check is safe to remove. let points = if points_i128 > (Self::MAX_SCORE as i128) { Self::MAX_SCORE } else { diff --git a/remittance_nft/src/test.rs b/remittance_nft/src/test.rs index 89878de..b04084a 100644 --- a/remittance_nft/src/test.rs +++ b/remittance_nft/src/test.rs @@ -1791,3 +1791,114 @@ fn test_score_history_max_50_entries() { .unwrap(); assert_eq!(last_entry.ledger, 60); } + +#[test] +fn test_update_score_floor_boundary_99() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + + let contract_id = env.register(RemittanceNFT, ()); + let client = RemittanceNFTClient::new(&env, &contract_id); + + client.initialize(&admin); + let history_hash = create_test_hash(&env, 1); + client.mint(&user, &500, &history_hash, &create_test_uri(&env), &None); + + // Default min_repayment is 0, but the fixed floor is 100. + // 99 is below the fixed floor. + let result = client.try_update_score(&user, &99, &None); + assert_eq!(result, Err(Ok(NftError::BelowMinimum))); +} + +#[test] +fn test_update_score_floor_boundary_100() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + + let contract_id = env.register(RemittanceNFT, ()); + let client = RemittanceNFTClient::new(&env, &contract_id); + + client.initialize(&admin); + let history_hash = create_test_hash(&env, 1); + client.mint(&user, &500, &history_hash, &create_test_uri(&env), &None); + + // Default min_repayment is 0. 100 is exactly at the fixed floor. + // Should succeed and add 1 point (100 / 100). + client.update_score(&user, &100, &None); + assert_eq!(client.get_score(&user), 501); +} + +#[test] +fn test_update_score_floor_configured_min_150_repay_120() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + + let contract_id = env.register(RemittanceNFT, ()); + let client = RemittanceNFTClient::new(&env, &contract_id); + + client.initialize(&admin); + client.set_min_repayment_amount(&150); + + let history_hash = create_test_hash(&env, 1); + client.mint(&user, &500, &history_hash, &create_test_uri(&env), &None); + + // 120 is above 100 but below the configured min_repayment of 150. + let result = client.try_update_score(&user, &120, &None); + assert_eq!(result, Err(Ok(NftError::BelowMinimum))); +} + +#[test] +fn test_update_score_floor_exact_effective() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + + let contract_id = env.register(RemittanceNFT, ()); + let client = RemittanceNFTClient::new(&env, &contract_id); + + client.initialize(&admin); + client.set_min_repayment_amount(&150); // min_repayment = 150, which is > 100. Effective floor is 150. + + let history_hash = create_test_hash(&env, 1); + client.mint(&user, &500, &history_hash, &create_test_uri(&env), &None); + + // 150 is exactly at the effective floor (150). + // Should succeed and add 1 point (150 / 100 = 1). + client.update_score(&user, &150, &None); + assert_eq!(client.get_score(&user), 501); +} + +#[test] +fn test_update_score_floor_above_effective() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + + let contract_id = env.register(RemittanceNFT, ()); + let client = RemittanceNFTClient::new(&env, &contract_id); + + client.initialize(&admin); + client.set_min_repayment_amount(&150); // Effective floor is 150. + + let history_hash = create_test_hash(&env, 1); + client.mint(&user, &500, &history_hash, &create_test_uri(&env), &None); + + // 151 is one unit above the effective floor (150). + // Should succeed and add 1 point (151 / 100 = 1). + client.update_score(&user, &151, &None); + assert_eq!(client.get_score(&user), 501); +} + diff --git a/remittance_nft/test_snapshots/test/test_update_score_floor_above_effective.1.json b/remittance_nft/test_snapshots/test/test_update_score_floor_above_effective.1.json new file mode 100644 index 0000000..fda1fb4 --- /dev/null +++ b/remittance_nft/test_snapshots/test/test_update_score_floor_above_effective.1.json @@ -0,0 +1,506 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "set_min_repayment_amount", + "args": [ + { + "i128": { + "hi": 0, + "lo": 150 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 500 + }, + { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + }, + { + "string": "ipfs://QmTest123" + }, + "void" + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "update_score", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 151 + } + }, + "void" + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "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": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "history_hash" + }, + "val": { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "metadata_uri" + }, + "val": { + "string": "ipfs://QmTest123" + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "u32": 501 + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "ScoreHistory" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "ScoreHistory" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "ledger" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "new_score" + }, + "val": { + "u32": 501 + } + }, + { + "key": { + "symbol": "old_score" + }, + "val": { + "u32": 500 + } + }, + { + "key": { + "symbol": "reason" + }, + "val": { + "symbol": "REPAY" + } + } + ] + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ADMIN" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "BURNTHR" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "vec": [ + { + "symbol": "MinRepaymentAmount" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 150 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Version" + } + ] + }, + "val": { + "u32": 2 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/remittance_nft/test_snapshots/test/test_update_score_floor_boundary_100.1.json b/remittance_nft/test_snapshots/test/test_update_score_floor_boundary_100.1.json new file mode 100644 index 0000000..dcb716e --- /dev/null +++ b/remittance_nft/test_snapshots/test/test_update_score_floor_boundary_100.1.json @@ -0,0 +1,436 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 500 + }, + { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + }, + { + "string": "ipfs://QmTest123" + }, + "void" + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "update_score", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + }, + "void" + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "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": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "history_hash" + }, + "val": { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "metadata_uri" + }, + "val": { + "string": "ipfs://QmTest123" + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "u32": 501 + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "ScoreHistory" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "ScoreHistory" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "ledger" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "new_score" + }, + "val": { + "u32": 501 + } + }, + { + "key": { + "symbol": "old_score" + }, + "val": { + "u32": 500 + } + }, + { + "key": { + "symbol": "reason" + }, + "val": { + "symbol": "REPAY" + } + } + ] + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ADMIN" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "BURNTHR" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Version" + } + ] + }, + "val": { + "u32": 2 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/remittance_nft/test_snapshots/test/test_update_score_floor_boundary_99.1.json b/remittance_nft/test_snapshots/test/test_update_score_floor_boundary_99.1.json new file mode 100644 index 0000000..2367fbd --- /dev/null +++ b/remittance_nft/test_snapshots/test/test_update_score_floor_boundary_99.1.json @@ -0,0 +1,295 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 500 + }, + { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + }, + { + "string": "ipfs://QmTest123" + }, + "void" + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "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": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "history_hash" + }, + "val": { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "metadata_uri" + }, + "val": { + "string": "ipfs://QmTest123" + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "u32": 500 + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ADMIN" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "BURNTHR" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Version" + } + ] + }, + "val": { + "u32": 2 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/remittance_nft/test_snapshots/test/test_update_score_floor_configured_min_150_repay_120.1.json b/remittance_nft/test_snapshots/test/test_update_score_floor_configured_min_150_repay_120.1.json new file mode 100644 index 0000000..327b53d --- /dev/null +++ b/remittance_nft/test_snapshots/test/test_update_score_floor_configured_min_150_repay_120.1.json @@ -0,0 +1,365 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "set_min_repayment_amount", + "args": [ + { + "i128": { + "hi": 0, + "lo": 150 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 500 + }, + { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + }, + { + "string": "ipfs://QmTest123" + }, + "void" + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "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": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "history_hash" + }, + "val": { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "metadata_uri" + }, + "val": { + "string": "ipfs://QmTest123" + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "u32": 500 + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ADMIN" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "BURNTHR" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "vec": [ + { + "symbol": "MinRepaymentAmount" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 150 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Version" + } + ] + }, + "val": { + "u32": 2 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/remittance_nft/test_snapshots/test/test_update_score_floor_exact_effective.1.json b/remittance_nft/test_snapshots/test/test_update_score_floor_exact_effective.1.json new file mode 100644 index 0000000..5978cd8 --- /dev/null +++ b/remittance_nft/test_snapshots/test/test_update_score_floor_exact_effective.1.json @@ -0,0 +1,506 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "set_min_repayment_amount", + "args": [ + { + "i128": { + "hi": 0, + "lo": 150 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 500 + }, + { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + }, + { + "string": "ipfs://QmTest123" + }, + "void" + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "update_score", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 150 + } + }, + "void" + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "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": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "AuthorizedMinter" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Metadata" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "history_hash" + }, + "val": { + "bytes": "0100000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "metadata_uri" + }, + "val": { + "string": "ipfs://QmTest123" + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "u32": 501 + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "ScoreHistory" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "ScoreHistory" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "ledger" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "new_score" + }, + "val": { + "u32": 501 + } + }, + { + "key": { + "symbol": "old_score" + }, + "val": { + "u32": 500 + } + }, + { + "key": { + "symbol": "reason" + }, + "val": { + "symbol": "REPAY" + } + } + ] + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ADMIN" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "BURNTHR" + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "vec": [ + { + "symbol": "MinRepaymentAmount" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 150 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "Version" + } + ] + }, + "val": { + "u32": 2 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file From 6b4209e131729ab8d68a4e20f85bae161d08c526 Mon Sep 17 00:00:00 2001 From: K1NGD4VID Date: Sun, 21 Jun 2026 20:26:41 +0100 Subject: [PATCH 2/2] Update fuzz target for LP-share model and remove default fuzz_target_1.rs --- fuzz/Cargo.toml | 7 - fuzz/fuzz_targets/fuzz_target_1.rs | 7 - fuzz/fuzz_targets/lending_pool_fuzz.rs | 264 +++- lending_pool/src/test.rs | 72 + .../test/test_fuzz_sanity_invariants.1.json | 1209 +++++++++++++++++ 5 files changed, 1495 insertions(+), 64 deletions(-) delete mode 100644 fuzz/fuzz_targets/fuzz_target_1.rs create mode 100644 lending_pool/test_snapshots/test/test_fuzz_sanity_invariants.1.json diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 77542ed..31555d3 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -24,13 +24,6 @@ path = "../remittance_nft" [dependencies.multisig_governance] path = "../multisig_governance" -[[bin]] -name = "fuzz_target_1" -path = "fuzz_targets/fuzz_target_1.rs" -test = false -doc = false -bench = false - [[bin]] name = "lending_pool_fuzz" path = "fuzz_targets/lending_pool_fuzz.rs" diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs deleted file mode 100644 index 43a88c1..0000000 --- a/fuzz/fuzz_targets/fuzz_target_1.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -use libfuzzer_sys::fuzz_target; - -fuzz_target!(|data: &[u8]| { - // fuzzed code goes here -}); diff --git a/fuzz/fuzz_targets/lending_pool_fuzz.rs b/fuzz/fuzz_targets/lending_pool_fuzz.rs index 3c9b255..b9f9bb1 100644 --- a/fuzz/fuzz_targets/lending_pool_fuzz.rs +++ b/fuzz/fuzz_targets/lending_pool_fuzz.rs @@ -18,7 +18,7 @@ macro_rules! rcall { }; } -#[derive(Arbitrary, Debug)] +#[derive(Arbitrary, Debug, Clone)] enum FuzzAction { Deposit { user_id: u8, amount: i128 }, Withdraw { user_id: u8, amount: i128 }, @@ -26,7 +26,7 @@ enum FuzzAction { MultipleOperations { operations: Vec }, } -#[derive(Arbitrary, Debug)] +#[derive(Arbitrary, Debug, Clone)] struct Operation { user_id: u8, amount: i128, @@ -43,7 +43,36 @@ fn setup_token_contract<'a>( (contract_id.address(), stellar_asset_client, token_client) } -fuzz_target!(|data: FuzzAction| { +fn assert_no_value_creation(shares: i128, pool_balance: i128, total_shares: i128, redeemable: i128) { + if total_shares == 0 { + assert_eq!(redeemable, 0); + return; + } + if let (Some(lhs), Some(rhs)) = (redeemable.checked_mul(total_shares), shares.checked_mul(pool_balance)) { + assert!(lhs <= rhs, "Value creation from rounding detected"); + } else { + let q = shares / total_shares; + let r = shares % total_shares; + if let Some(term1) = q.checked_mul(pool_balance) { + if let Some(term2_num) = r.checked_mul(pool_balance) { + let term2 = term2_num / total_shares; + if let Some(max_redeemable) = term1.checked_add(term2) { + assert!(redeemable <= max_redeemable, "Value creation from rounding detected"); + } + } + } + } +} + +macro_rules! safe_call { + ($expr:expr) => { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| $expr)).unwrap_or_else(|e| { + panic!("Unexpected contract panic: {:?}", e); + }) + }; +} + +pub fn run_fuzz_logic(data: FuzzAction) { let env = Env::default(); env.mock_all_auths(); @@ -58,6 +87,9 @@ fuzz_target!(|data: FuzzAction| { // 3. Initialize LendingPool with Admin (Contract client wrapper has 1 arg) let pool_admin = Address::generate(&env); pool_client.initialize(&pool_admin); + + // Disable withdrawal cooldown so we can test deposits and withdrawals in the same sequence + pool_client.set_withdrawal_cooldown(&0); match data { FuzzAction::Deposit { user_id: _, amount } => { @@ -71,13 +103,30 @@ fuzz_target!(|data: FuzzAction| { // Mint tokens to user stellar_asset_client.mint(&user, &amount); - let result = rcall!(&env, pool_client, "deposit", (user, token_id, amount)); + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + rcall!(&env, pool_client, "deposit", (user, token_id, amount)) + })); + let result = match result { + Ok(res) => res, + Err(err) => { + panic!("Contract deposit panicked unexpectedly: {:?}", err); + } + }; if result.is_ok() { - // Verify invariant: deposit should increase user balance - let balance = pool_client.get_deposit(&user, &token_id); + // Verify invariants: + // 1. Shares minted on deposit must be strictly positive for any positive deposit amount + let shares = safe_call!(pool_client.get_shares(&user, &token_id)); + assert!(shares > 0, "Shares minted on deposit must be strictly positive"); + + // 2. get_deposit's redeemable asset value must never be negative + let balance = safe_call!(pool_client.get_deposit(&user, &token_id)); assert!(balance >= 0, "Balance should never be negative"); - assert_eq!(balance, amount, "Balance should match deposited amount"); + + // 3. Redeemable value must never exceed what is mathematically possible (no value creation from rounding) + let cur_total_shares = safe_call!(pool_client.get_total_shares(&token_id)); + let pool_balance = token_client.balance(&pool_id); + assert_no_value_creation(shares, pool_balance, cur_total_shares, balance); // Verify pool token balance assert_eq!( @@ -88,51 +137,75 @@ fuzz_target!(|data: FuzzAction| { } } - FuzzAction::Withdraw { user_id: _, amount } => { + FuzzAction::Withdraw { user_id: _, amount: shares_to_withdraw } => { let user = Address::generate(&env); // Skip invalid amounts - if amount <= 0 { + if shares_to_withdraw <= 0 { return; } - // First deposit some amount to allow withdrawal - let deposit_amount = match amount.checked_mul(2) { - Some(v) => v, - None => return, - }; + // First deposit some assets to mint shares to allow withdrawal. + // Since it's a fresh pool, depositing `deposit_amount` of assets will mint `deposit_amount` shares. + // The minimum initial deposit is 1,000 assets (which mints 1,000 shares). + // So we need deposit_amount >= 1,000 and deposit_amount >= shares_to_withdraw. + let deposit_amount = std::cmp::max(shares_to_withdraw, 1_000); stellar_asset_client.mint(&user, &deposit_amount); - pool_client.deposit(&user, &token_id, &deposit_amount); - let balance_before = pool_client.get_deposit(&user, &token_id); - let result = rcall!(&env, pool_client, "withdraw", (user, amount)); + let dep_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + pool_client.deposit(&user, &token_id, &deposit_amount) + })); + if dep_result.is_err() { + return; + } + + let shares_before = safe_call!(pool_client.get_shares(&user, &token_id)); + + // withdraw's parameter is a share count, not an asset amount + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + rcall!(&env, pool_client, "withdraw", (user, token_id, shares_to_withdraw)) + })); + let result = match result { + Ok(res) => res, + Err(err) => { + panic!("Contract withdraw panicked unexpectedly: {:?}", err); + } + }; if result.is_ok() { - let balance_after = pool_client.get_deposit(&user, &token_id); + let shares_after = safe_call!(pool_client.get_shares(&user, &token_id)); + let balance_after = safe_call!(pool_client.get_deposit(&user, &token_id)); - // Verify invariant: balance should decrease by withdrawal amount + // Verify invariants: + // 1. shares decreased by shares_to_withdraw assert_eq!( - balance_before - amount, - balance_after, - "Balance should decrease by withdrawal amount" + shares_before - shares_to_withdraw, + shares_after, + "Shares should decrease by withdrawal share count" ); + + // 2. get_deposit's redeemable asset value must never be negative assert!(balance_after >= 0, "Balance should never be negative"); - // Verify pool token balance - assert_eq!( - token_client.balance(&pool_id), - deposit_amount - amount, - "Pool token balance mismatch after withdrawal" - ); + // 3. Redeemable value must never exceed what is mathematically possible (no value creation from rounding) + let cur_total_shares = safe_call!(pool_client.get_total_shares(&token_id)); + let pool_balance = token_client.balance(&pool_id); + assert_no_value_creation(shares_after, pool_balance, cur_total_shares, balance_after); } } FuzzAction::GetDeposit { user_id: _ } => { let user = Address::generate(&env); - let balance = pool_client.get_deposit(&user, &token_id); + let balance = safe_call!(pool_client.get_deposit(&user, &token_id)); // Verify invariant: balance should never be negative assert!(balance >= 0, "Balance should never be negative"); + + // Verify no value creation from rounding + let shares = safe_call!(pool_client.get_shares(&user, &token_id)); + let cur_total_shares = safe_call!(pool_client.get_total_shares(&token_id)); + let pool_balance = token_client.balance(&pool_id); + assert_no_value_creation(shares, pool_balance, cur_total_shares, balance); } FuzzAction::MultipleOperations { operations } => { @@ -151,30 +224,83 @@ fuzz_target!(|data: FuzzAction| { } stellar_asset_client.mint(&user_addr, &op.amount); - let result = rcall!( - &env, - pool_client, - "deposit", - (user_addr, token_id, op.amount) - ); + + let shares_before = safe_call!(pool_client.get_shares(&user_addr, &token_id)); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + rcall!( + &env, + pool_client, + "deposit", + (user_addr, token_id, op.amount) + ) + })); + let result = match result { + Ok(res) => res, + Err(err) => { + panic!("Contract deposit panicked unexpectedly: {:?}", err); + } + }; + if result.is_ok() { total_expected_deposits += op.amount; + + // Verify invariants: + // 1. Shares minted on deposit must be strictly positive + let shares_after = safe_call!(pool_client.get_shares(&user_addr, &token_id)); + assert!(shares_after > shares_before, "Shares minted on deposit must be strictly positive"); + + // 2. get_deposit's redeemable asset value must never be negative + let balance = safe_call!(pool_client.get_deposit(&user_addr, &token_id)); + assert!(balance >= 0, "Balance should never be negative"); + + // 3. Redeemable value must never exceed what is mathematically possible (no value creation from rounding) + let cur_total_shares = safe_call!(pool_client.get_total_shares(&token_id)); + let pool_balance = token_client.balance(&pool_id); + assert_no_value_creation(shares_after, pool_balance, cur_total_shares, balance); } } else { - if op.amount <= 0 { + let shares_to_withdraw = op.amount; + if shares_to_withdraw <= 0 { continue; } - // Attempt withdrawal - let result = rcall!(&env, pool_client, "withdraw", (user_addr, op.amount)); - if result.is_ok() { - total_expected_deposits -= op.amount; - } else { - // If it fails, balance should be verified or we just continue - let balance = pool_client.get_deposit(&user_addr, &token_id); - if balance < op.amount { - // Expected failure + let shares_before = safe_call!(pool_client.get_shares(&user_addr, &token_id)); + let pool_balance_before = token_client.balance(&pool_id); + + // withdraw's parameter is a share count, not an asset amount + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + rcall!(&env, pool_client, "withdraw", (user_addr, token_id, shares_to_withdraw)) + })); + let result = match result { + Ok(res) => res, + Err(err) => { + panic!("Contract withdraw panicked unexpectedly: {:?}", err); } + }; + + if result.is_ok() { + let pool_balance_after = token_client.balance(&pool_id); + let assets_withdrawn = pool_balance_before - pool_balance_after; + total_expected_deposits -= assets_withdrawn; + + let shares_after = safe_call!(pool_client.get_shares(&user_addr, &token_id)); + let balance_after = safe_call!(pool_client.get_deposit(&user_addr, &token_id)); + + // Verify invariants: + // 1. shares decreased by shares_to_withdraw + assert_eq!( + shares_before - shares_to_withdraw, + shares_after, + "Shares should decrease by withdrawal share count" + ); + + // 2. get_deposit's redeemable asset value must never be negative + assert!(balance_after >= 0, "Balance should never be negative"); + + // 3. Redeemable value must never exceed what is mathematically possible (no value creation from rounding) + let cur_total_shares = safe_call!(pool_client.get_total_shares(&token_id)); + assert_no_value_creation(shares_after, pool_balance_after, cur_total_shares, balance_after); } } } @@ -186,13 +312,51 @@ fuzz_target!(|data: FuzzAction| { "Total deposits should match pool token balance" ); - // Verify all individual balances are non-negative + // Verify all individual balances are non-negative and satisfy the no value creation invariant for (_, user_addr) in users { - assert!( - pool_client.get_deposit(&user_addr, &token_id) >= 0, - "Individual balance should never be negative" - ); + let shares = safe_call!(pool_client.get_shares(&user_addr, &token_id)); + let balance = safe_call!(pool_client.get_deposit(&user_addr, &token_id)); + assert!(balance >= 0, "Individual balance should never be negative"); + + let cur_total_shares = safe_call!(pool_client.get_total_shares(&token_id)); + let pool_balance = token_client.balance(&pool_id); + assert_no_value_creation(shares, pool_balance, cur_total_shares, balance); } } } +} + +fuzz_target!(|data: FuzzAction| { + run_fuzz_logic(data); }); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fuzz_sanity() { + let test_cases = vec![ + FuzzAction::Deposit { user_id: 1, amount: 500 }, // < MINIMUM_INITIAL_DEPOSIT (should fail) + FuzzAction::Deposit { user_id: 1, amount: 2000 }, // OK + FuzzAction::Deposit { user_id: 2, amount: 1500 }, // OK + FuzzAction::Withdraw { user_id: 1, amount: 500 }, // OK (withdraws 500 shares) + FuzzAction::Withdraw { user_id: 2, amount: 3000 }, // exceeds shares (should fail) + FuzzAction::GetDeposit { user_id: 1 }, + FuzzAction::GetDeposit { user_id: 3 }, // new user (0 shares) + FuzzAction::MultipleOperations { + operations: vec![ + Operation { user_id: 1, amount: 1500, is_deposit: true }, + Operation { user_id: 2, amount: 2500, is_deposit: true }, + Operation { user_id: 1, amount: 500, is_deposit: false }, + Operation { user_id: 2, amount: 1000, is_deposit: false }, + Operation { user_id: 3, amount: 100, is_deposit: false }, // should fail + ] + } + ]; + + for case in test_cases { + run_fuzz_logic(case); + } + } +} diff --git a/lending_pool/src/test.rs b/lending_pool/src/test.rs index c3b588f..a88bd83 100644 --- a/lending_pool/src/test.rs +++ b/lending_pool/src/test.rs @@ -1504,3 +1504,75 @@ fn test_donation_attack_does_not_zero_out_subsequent_depositor() { "victim should recover their share of the pool" ); } + +#[test] +fn test_fuzz_sanity_invariants() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let token_admin = Address::generate(&env); + let (token_id, stellar_asset_client, token_client) = create_token_contract(&env, &token_admin); + + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + pool_client.set_withdrawal_cooldown(&0); + + // Invariant helper: + let assert_no_value_creation = |shares: i128, pool_balance: i128, total_shares: i128, redeemable: i128| { + if total_shares == 0 { + assert_eq!(redeemable, 0); + return; + } + if let (Some(lhs), Some(rhs)) = (redeemable.checked_mul(total_shares), shares.checked_mul(pool_balance)) { + assert!(lhs <= rhs, "Value creation from rounding detected"); + } else { + let q = shares / total_shares; + let r = shares % total_shares; + if let Some(term1) = q.checked_mul(pool_balance) { + if let Some(term2_num) = r.checked_mul(pool_balance) { + let term2 = term2_num / total_shares; + if let Some(max_redeemable) = term1.checked_add(term2) { + assert!(redeemable <= max_redeemable, "Value creation from rounding detected"); + } + } + } + } + }; + + // Action 1: Deposit (positive amount) + let user1 = Address::generate(&env); + let amount1 = 2000; + stellar_asset_client.mint(&user1, &amount1); + pool_client.deposit(&user1, &token_id, &amount1); + + let shares1 = pool_client.get_shares(&user1, &token_id); + assert!(shares1 > 0, "Shares minted on deposit must be strictly positive"); + let balance1 = pool_client.get_deposit(&user1, &token_id); + assert!(balance1 >= 0, "Balance should never be negative"); + assert_no_value_creation(shares1, token_client.balance(&pool_id), pool_client.get_total_shares(&token_id), balance1); + + // Action 2: Deposit from another user + let user2 = Address::generate(&env); + let amount2 = 1500; + stellar_asset_client.mint(&user2, &amount2); + pool_client.deposit(&user2, &token_id, &amount2); + + let shares2 = pool_client.get_shares(&user2, &token_id); + assert!(shares2 > 0, "Shares minted on deposit must be strictly positive"); + let balance2 = pool_client.get_deposit(&user2, &token_id); + assert!(balance2 >= 0, "Balance should never be negative"); + assert_no_value_creation(shares2, token_client.balance(&pool_id), pool_client.get_total_shares(&token_id), balance2); + + // Action 3: Withdraw (shares count) + let shares_to_withdraw = 500; + let shares_before = pool_client.get_shares(&user1, &token_id); + pool_client.withdraw(&user1, &token_id, &shares_to_withdraw); + let shares_after = pool_client.get_shares(&user1, &token_id); + assert_eq!(shares_before - shares_to_withdraw, shares_after); + let balance_after = pool_client.get_deposit(&user1, &token_id); + assert!(balance_after >= 0, "Balance should never be negative"); + assert_no_value_creation(shares_after, token_client.balance(&pool_id), pool_client.get_total_shares(&token_id), balance_after); +} + diff --git a/lending_pool/test_snapshots/test/test_fuzz_sanity_invariants.1.json b/lending_pool/test_snapshots/test/test_fuzz_sanity_invariants.1.json new file mode 100644 index 0000000..91b37b0 --- /dev/null +++ b/lending_pool/test_snapshots/test/test_fuzz_sanity_invariants.1.json @@ -0,0 +1,1209 @@ +{ + "generators": { + "address": 6, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "set_withdrawal_cooldown", + "args": [ + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": { + "hi": 0, + "lo": 2000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "deposit", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + }, + { + "i128": { + "hi": 0, + "lo": 2000 + } + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 2000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "i128": { + "hi": 0, + "lo": 1500 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "deposit", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + }, + { + "i128": { + "hi": 0, + "lo": 1500 + } + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": { + "hi": 0, + "lo": 1500 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "withdraw", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + }, + { + "i128": { + "hi": 0, + "lo": 500 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "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": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "DepositTimestamp" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "DepositTimestamp" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 0 + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "DepositTimestamp" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "DepositTimestamp" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "durability": "persistent", + "val": { + "u32": 0 + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Shares" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Shares" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 1500 + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Shares" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Shares" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 1500 + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "DepositorCount" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "val": { + "u32": 2 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalDeposits" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 3000 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalShares" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 3000 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Version" + } + ] + }, + "val": { + "u32": 3 + } + }, + { + "key": { + "vec": [ + { + "symbol": "WithdrawalCooldown" + } + ] + }, + "val": { + "u32": 0 + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 3000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 500 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 518400 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file