diff --git a/campaign/src/lib.rs b/campaign/src/lib.rs index a83ffc7..e32a1e7 100644 --- a/campaign/src/lib.rs +++ b/campaign/src/lib.rs @@ -431,6 +431,7 @@ impl CampaignContract { // Calculate pro-rata refund: (donor_amount * refund_numerator) / refund_denominator // PR #21: anti-dust floor via calculate_refund_amount helper. let refund_amount = calculate_refund_amount( + &env, donor_asset_amount, refund_numerator, refund_denominator, @@ -809,20 +810,23 @@ mod test { } } -fn calculate_refund_amount( +pub(crate) fn calculate_refund_amount( + env: &Env, donor_asset_amount: i128, refund_numerator: i128, refund_denominator: i128, ) -> i128 { - debug_assert!(refund_denominator > 0, "refund_denominator must be nonzero"); + if refund_denominator <= 0 { + panic_with_error(env, Error::Overflow); + } let numerator = donor_asset_amount .checked_mul(refund_numerator) - .expect("overflow in refund numerator"); + .unwrap_or_else(|| panic_with_error(env, Error::Overflow)); let refund = numerator / refund_denominator; - // Anti-dust floor: if the donor is entitled to something nonzero but + // PR #21: anti-dust floor — if the donor is entitled to something nonzero but // floor division rounded it all the way down to 0, bump to 1 unit // rather than letting them lose their entire refund to rounding. if refund == 0 && numerator > 0 { diff --git a/campaign/src/test/claim_refund_tests.rs b/campaign/src/test/claim_refund_tests.rs index a708583..4604f3f 100644 --- a/campaign/src/test/claim_refund_tests.rs +++ b/campaign/src/test/claim_refund_tests.rs @@ -14,7 +14,7 @@ use soroban_sdk::{log, vec, Address, Env, Vec}; use super::with_contract; use crate::storage::{set_campaign, set_donor, set_milestone}; use crate::types::{ - AssetInfo, CampaignData, CampaignStatus, DonorRecord, MilestoneData, MilestoneStatus, + AssetInfo, CampaignData, CampaignStatus, DataKey, DonorRecord, MilestoneData, MilestoneStatus, StellarAsset, }; use crate::{CampaignContract, CampaignContractClient}; @@ -100,6 +100,137 @@ fn create_test_donor(env: &Env, donor: &Address, total_donated: i128, refund_cla set_donor(env, donor, &donor_record); } +// ─── calculate_refund_amount typed-error tests (issue #33) ─────────────────── + +/// Zero refund denominator must panic with typed `Error::Overflow`, not a WASM trap. +#[test] +#[should_panic(expected = "Error(Contract, #17)")] +fn test_calculate_refund_amount_zero_denominator() { + let env = make_env(); + with_contract(&env, || { + crate::calculate_refund_amount(&env, 100, 50, 0); + }); +} + +/// Negative refund denominator must also panic with typed `Error::Overflow`. +#[test] +#[should_panic(expected = "Error(Contract, #17)")] +fn test_calculate_refund_amount_negative_denominator() { + let env = make_env(); + with_contract(&env, || { + crate::calculate_refund_amount(&env, 100, 50, -1); + }); +} + +/// Integer overflow in refund numerator must panic with typed `Error::Overflow`. +#[test] +#[should_panic(expected = "Error(Contract, #17)")] +fn test_calculate_refund_amount_overflow() { + let env = make_env(); + with_contract(&env, || { + crate::calculate_refund_amount(&env, i128::MAX, 2, 1); + }); +} + +/// PR #21 anti-dust floor: tiny pro-rata share rounds up to 1 unit. +#[test] +fn test_calculate_refund_amount_anti_dust_floor() { + let env = make_env(); + with_contract(&env, || { + // (1 * 1) / 1000 = 0 in floor division, but numerator > 0 → bump to 1 + let refund = crate::calculate_refund_amount(&env, 1, 1, 1000); + assert_eq!(refund, 1); + }); +} + +/// `claim_refund` must surface typed `Error::Overflow` when refund math overflows. +#[test] +#[should_panic(expected = "Error(Contract, #17)")] +fn test_claim_refund_refund_amount_overflow() { + let env = make_env(); + env.ledger().set_timestamp(BASE); + env.mock_all_auths(); + with_contract(&env, || { + let token_issuer = Address::generate(&env); + let end_time = env.ledger().timestamp() + 1000; + let campaign = CampaignData { + creator: Address::generate(&env), + goal_amount: 1000, + raised_amount: 2, + end_time, + status: CampaignStatus::Cancelled, + accepted_assets: { + let mut assets = soroban_sdk::Vec::new(&env); + assets.push_back(StellarAsset { + asset_code: soroban_sdk::String::from_str(&env, "TST"), + issuer: Some(token_issuer.clone()), + }); + assets + }, + milestone_count: 1, + min_donation_amount: 0, + created_at_ledger: 0, + created_at_time: 0, + concluded_at_ledger: None, + }; + set_campaign(&env, &campaign); + create_test_milestone(&env, 0, 1000, MilestoneStatus::Locked); + + let donor = Address::generate(&env); + create_test_donor(&env, &donor, 100, false); + env.storage().persistent().set( + &DataKey::DonorAssetDonation(donor.clone(), token_issuer.clone()), + &i128::MAX, + ); + + CampaignContract::claim_refund(env.clone(), donor); + }); +} + +/// `claim_refund` must surface typed `Error::Overflow` when raised_amount is zero. +#[test] +#[should_panic(expected = "Error(Contract, #17)")] +fn test_claim_refund_zero_denominator() { + let env = make_env(); + env.ledger().set_timestamp(BASE); + env.mock_all_auths(); + with_contract(&env, || { + let token_issuer = Address::generate(&env); + let end_time = env.ledger().timestamp() + 1000; + let campaign = CampaignData { + creator: Address::generate(&env), + goal_amount: 1000, + raised_amount: 0, + end_time, + status: CampaignStatus::Cancelled, + accepted_assets: { + let mut assets = soroban_sdk::Vec::new(&env); + assets.push_back(StellarAsset { + asset_code: soroban_sdk::String::from_str(&env, "TST"), + issuer: Some(token_issuer.clone()), + }); + assets + }, + milestone_count: 1, + min_donation_amount: 0, + created_at_ledger: 0, + created_at_time: 0, + concluded_at_ledger: None, + }; + set_campaign(&env, &campaign); + create_test_milestone(&env, 0, 1000, MilestoneStatus::Locked); + + let donor = Address::generate(&env); + create_test_donor(&env, &donor, 100, false); + env.storage().persistent().set( + &DataKey::DonorAssetDonation(donor.clone(), token_issuer.clone()), + &100i128, + ); + + CampaignContract::claim_refund(env.clone(), donor); + }); +} + // ─── Error path tests ──────────────────────────────────────────────────────── /// Claiming a refund when no campaign has been initialized should panic. diff --git a/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_anti_dust_floor.1.json b/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_anti_dust_floor.1.json new file mode 100644 index 0000000..3a921a4 --- /dev/null +++ b/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_anti_dust_floor.1.json @@ -0,0 +1,61 @@ +{ + "generators": { + "address": 1, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_negative_denominator.1.json b/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_negative_denominator.1.json new file mode 100644 index 0000000..f4cbd15 --- /dev/null +++ b/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_negative_denominator.1.json @@ -0,0 +1,60 @@ +{ + "generators": { + "address": 1, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_overflow.1.json b/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_overflow.1.json new file mode 100644 index 0000000..f4cbd15 --- /dev/null +++ b/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_overflow.1.json @@ -0,0 +1,60 @@ +{ + "generators": { + "address": 1, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_zero_denominator.1.json b/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_zero_denominator.1.json new file mode 100644 index 0000000..f4cbd15 --- /dev/null +++ b/campaign/test_snapshots/test/claim_refund_tests/test_calculate_refund_amount_zero_denominator.1.json @@ -0,0 +1,60 @@ +{ + "generators": { + "address": 1, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/campaign/test_snapshots/test/claim_refund_tests/test_claim_refund_refund_amount_overflow.1.json b/campaign/test_snapshots/test/claim_refund_tests/test_claim_refund_refund_amount_overflow.1.json new file mode 100644 index 0000000..0ec80d1 --- /dev/null +++ b/campaign/test_snapshots/test/claim_refund_tests/test_claim_refund_refund_amount_overflow.1.json @@ -0,0 +1,454 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 31536000, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "CampaignData" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "accepted_assets" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "TST" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + ] + } + }, + { + "key": { + "symbol": "concluded_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "created_at_ledger" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "created_at_time" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "end_time" + }, + "val": { + "u64": "31537000" + } + }, + { + "key": { + "symbol": "goal_amount" + }, + "val": { + "i128": "1000" + } + }, + { + "key": { + "symbol": "milestone_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "min_donation_amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "raised_amount" + }, + "val": { + "i128": "2" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Cancelled" + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 1036800 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "DonorAssetDonation" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": "170141183460469231731687303715884105727" + } + } + }, + "ext": "v0" + }, + "live_until": 1036800 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "DonorData" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "asset" + }, + "val": { + "vec": [ + { + "symbol": "Native" + } + ] + } + }, + { + "key": { + "symbol": "donation_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "donor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "last_donation_ledger" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "last_donation_time" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "refund_claimed" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "total_donated" + }, + "val": { + "i128": "100" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 1036800 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "MilestoneData" + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "description_hash" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "index" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "release_tx" + }, + "val": "void" + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "released_at" + }, + "val": "void" + }, + { + "key": { + "symbol": "released_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "released_to" + }, + "val": "void" + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Locked" + } + ] + } + }, + { + "key": { + "symbol": "target_amount" + }, + "val": { + "i128": "1000" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 1036800 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "ReentrancyLock" + } + ] + }, + "durability": "temporary", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + "live_until": 15 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/campaign/test_snapshots/test/claim_refund_tests/test_claim_refund_zero_denominator.1.json b/campaign/test_snapshots/test/claim_refund_tests/test_claim_refund_zero_denominator.1.json new file mode 100644 index 0000000..b5d3218 --- /dev/null +++ b/campaign/test_snapshots/test/claim_refund_tests/test_claim_refund_zero_denominator.1.json @@ -0,0 +1,454 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 31536000, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "CampaignData" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "accepted_assets" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "TST" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + ] + } + }, + { + "key": { + "symbol": "concluded_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "created_at_ledger" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "created_at_time" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "end_time" + }, + "val": { + "u64": "31537000" + } + }, + { + "key": { + "symbol": "goal_amount" + }, + "val": { + "i128": "1000" + } + }, + { + "key": { + "symbol": "milestone_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "min_donation_amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "raised_amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Cancelled" + } + ] + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 1036800 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "DonorAssetDonation" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": "100" + } + } + }, + "ext": "v0" + }, + "live_until": 1036800 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "DonorData" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "asset" + }, + "val": { + "vec": [ + { + "symbol": "Native" + } + ] + } + }, + { + "key": { + "symbol": "donation_count" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "donor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "last_donation_ledger" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "last_donation_time" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "refund_claimed" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "total_donated" + }, + "val": { + "i128": "100" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 1036800 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "MilestoneData" + }, + { + "u32": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "description_hash" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "index" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "release_tx" + }, + "val": "void" + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "released_at" + }, + "val": "void" + }, + { + "key": { + "symbol": "released_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "released_to" + }, + "val": "void" + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Locked" + } + ] + } + }, + { + "key": { + "symbol": "target_amount" + }, + "val": { + "i128": "1000" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 1036800 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "ReentrancyLock" + } + ] + }, + "durability": "temporary", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + "live_until": 15 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file