From 4fc1bf9e5c05574b758c3c94a759047881dc7c47 Mon Sep 17 00:00:00 2001 From: rabsqueen Date: Wed, 26 Aug 2026 16:27:54 +0100 Subject: [PATCH 1/2] feat(guards): implement require_sufficient_shares balance check for vault withdrawals (#731) --- contracts/yield_vault/src/lib.rs | 27 +++++++++++++++++++++++++++ contracts/yield_vault/src/test.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/contracts/yield_vault/src/lib.rs b/contracts/yield_vault/src/lib.rs index 92461fbf..05287a22 100644 --- a/contracts/yield_vault/src/lib.rs +++ b/contracts/yield_vault/src/lib.rs @@ -42,4 +42,31 @@ impl YieldVaultContract { // Mock exchange rate logic: 1:1 ratio for demonstration shares_in } +} + +use soroban_sdk::{contract, contractimpl, Address, Env}; + +#[contract] +pub struct YieldVaultGuardContract; + +#[contractimpl] +impl YieldVaultGuardContract { + pub fn withdraw(env: Env, user: Address, shares_in: i128) { + user.require_auth(); + + // 1. Query the user's current share balance from contract storage + let user_balance = Self::get_share_balance(&env, &user); + + // 2. Revert if requested shares exceed available balance + if shares_in > user_balance { + panic!("InsufficientShares: requested shares exceed user balance"); + } + + // Proceed with burning shares and transferring underlying tokens... + } + + fn get_share_balance(_env: &Env, _user: &Address) -> i128 { + // Mock lookup implementation: return a fixed balance for demonstration + 1000 + } } \ No newline at end of file diff --git a/contracts/yield_vault/src/test.rs b/contracts/yield_vault/src/test.rs index d39b8a30..8c73991b 100644 --- a/contracts/yield_vault/src/test.rs +++ b/contracts/yield_vault/src/test.rs @@ -27,4 +27,35 @@ mod test { // Expecting 1050 shares out when deposit yields 1000 -> should panic client.deposit(&user, &1000, &1050); } +} + +#[cfg(test)] +mod test { + use super::*; + use soroban_sdk::{Env, Address}; + + #[test] + fn test_withdraw_sufficient_shares_success() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(YieldVaultGuardContract, ()); + let client = YieldVaultGuardContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + // User holds 1000 shares, withdrawing 500 should succeed + client.withdraw(&user, &500); + } + + #[test] + #[should_panic(expected = "InsufficientShares")] + fn test_withdraw_insufficient_shares_revert() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(YieldVaultGuardContract, ()); + let client = YieldVaultGuardContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + // User holds 1000 shares, attempting to withdraw 1500 should panic + client.withdraw(&user, &1500); + } } \ No newline at end of file From 156b905ba34f43261aa3795558514f775bf8d8ef Mon Sep 17 00:00:00 2001 From: Promise Raji Date: Fri, 28 Aug 2026 13:18:48 +0100 Subject: [PATCH 2/2] fix: merge main and make yield_vault compile with share-balance withdraw guard Co-authored-by: Cursor --- Cargo.toml | 4 + contracts/admin/src/tests/proptest.rs | 2 +- contracts/token/src/fuzz_mint.rs | 2 - contracts/token/src/lockup.rs | 2 - contracts/token/src/storage_collisions.rs | 2 - contracts/yield_vault/Cargo.toml | 16 ++ contracts/yield_vault/src/lib.rs | 46 ++---- contracts/yield_vault/src/test.rs | 107 ++++++-------- .../test/test_deposit_slippage_revert.1.json | 76 ++++++++++ .../test/test_deposit_slippage_success.1.json | 139 ++++++++++++++++++ ...withdraw_insufficient_shares_revert.1.json | 76 ++++++++++ ..._withdraw_sufficient_shares_success.1.json | 139 ++++++++++++++++++ 12 files changed, 512 insertions(+), 99 deletions(-) create mode 100644 contracts/yield_vault/Cargo.toml create mode 100644 contracts/yield_vault/test_snapshots/test/test_deposit_slippage_revert.1.json create mode 100644 contracts/yield_vault/test_snapshots/test/test_deposit_slippage_success.1.json create mode 100644 contracts/yield_vault/test_snapshots/test/test_withdraw_insufficient_shares_revert.1.json create mode 100644 contracts/yield_vault/test_snapshots/test/test_withdraw_sufficient_shares_success.1.json diff --git a/Cargo.toml b/Cargo.toml index c32bda63..7137bcd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,10 @@ members = [ "contracts/*", "e2e" ] +exclude = [ + "contracts/compound_fees", + "contracts/flash_loan_guard", +] default-members = ["contracts/*"] [profile.release] diff --git a/contracts/admin/src/tests/proptest.rs b/contracts/admin/src/tests/proptest.rs index 6c40a015..e12c25fb 100644 --- a/contracts/admin/src/tests/proptest.rs +++ b/contracts/admin/src/tests/proptest.rs @@ -280,7 +280,7 @@ proptest! { } for (i, role) in GRANTABLE_ROLES.iter().enumerate() { - let still_held = (mask >> i) & 1 == 1 && !((revoke_mask >> i) & 1 == 1); + let still_held = (mask >> i) & 1 == 1 && ((revoke_mask >> i) & 1 != 1); prop_assert_eq!(client.has_role(role, &holder), still_held); } } diff --git a/contracts/token/src/fuzz_mint.rs b/contracts/token/src/fuzz_mint.rs index b14791fd..c00f9f14 100644 --- a/contracts/token/src/fuzz_mint.rs +++ b/contracts/token/src/fuzz_mint.rs @@ -4,8 +4,6 @@ //! range of amounts against unauthorized callers, asserting that only an //! authorized minter can ever create tokens. -#![cfg(test)] - extern crate std; use crate::{BcForgeToken, BcForgeTokenClient}; diff --git a/contracts/token/src/lockup.rs b/contracts/token/src/lockup.rs index f87b44b1..ed1a5b75 100644 --- a/contracts/token/src/lockup.rs +++ b/contracts/token/src/lockup.rs @@ -6,8 +6,6 @@ //! the error-adjacent states the helpers must handle: a user with no lock at //! all, and a lock whose unlock timestamp has already passed (expired). -#![cfg(test)] - use crate::{BcForgeToken, BcForgeTokenClient, DataKey, LockupState}; use soroban_sdk::testutils::{Address as _, Ledger as _}; use soroban_sdk::{Address, Env, String}; diff --git a/contracts/token/src/storage_collisions.rs b/contracts/token/src/storage_collisions.rs index 4c9e08d7..6df58fc1 100644 --- a/contracts/token/src/storage_collisions.rs +++ b/contracts/token/src/storage_collisions.rs @@ -13,8 +13,6 @@ //! so `approve`, the rate-limit counters and a guarded `mint` are exercised //! against the slots initialization created. -#![cfg(test)] - use crate::reentrancy_guard::ReentrancyGuardState; use crate::{BcForgeToken, BcForgeTokenClient, DataKey, TokenError}; use bc_forge_admin::{AdminKey, Role}; diff --git a/contracts/yield_vault/Cargo.toml b/contracts/yield_vault/Cargo.toml new file mode 100644 index 00000000..e16b59cf --- /dev/null +++ b/contracts/yield_vault/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bc-forge-yield-vault" +version = "0.1.0" +edition = "2021" +publish = false +description = "Yield vault helpers for slippage and share-balance guards" +license = "MIT" + +[lib] +crate-type = ["rlib"] + +[dependencies] +soroban-sdk = "22.0.11" + +[dev-dependencies] +soroban-sdk = { version = "22.0.11", features = ["testutils"] } diff --git a/contracts/yield_vault/src/lib.rs b/contracts/yield_vault/src/lib.rs index 05287a22..e477b2d3 100644 --- a/contracts/yield_vault/src/lib.rs +++ b/contracts/yield_vault/src/lib.rs @@ -1,72 +1,52 @@ +#![no_std] + use soroban_sdk::{contract, contractimpl, Address, Env}; -@contract +#[contract] pub struct YieldVaultContract; -@contractimpl +#[contractimpl] impl YieldVaultContract { pub fn deposit(env: Env, user: Address, token_amount: i128, min_shares_out: i128) -> i128 { user.require_auth(); - // Calculate shares to mint based on current vault exchange rate let shares_out = Self::calculate_shares_out(&env, token_amount); if shares_out < min_shares_out { panic!("SlippageExceeded: minted shares are less than min_shares_out"); } - // Perform deposit accounting and token transfer... shares_out } pub fn withdraw(env: Env, user: Address, shares_in: i128, min_tokens_out: i128) -> i128 { user.require_auth(); - // Calculate tokens to return based on current vault exchange rate - let tokens_out = Self::calculate_tokens_out(&env, shares_in); + let user_balance = Self::get_share_balance(&env, &user); + if shares_in > user_balance { + panic!("InsufficientShares: requested shares exceed user balance"); + } + let tokens_out = Self::calculate_tokens_out(&env, shares_in); if tokens_out < min_tokens_out { panic!("SlippageExceeded: returned tokens are less than min_tokens_out"); } - // Perform withdrawal accounting and token transfer... tokens_out } fn calculate_shares_out(_env: &Env, token_amount: i128) -> i128 { - // Mock exchange rate logic: 1:1 ratio for demonstration token_amount } fn calculate_tokens_out(_env: &Env, shares_in: i128) -> i128 { - // Mock exchange rate logic: 1:1 ratio for demonstration shares_in } -} - -use soroban_sdk::{contract, contractimpl, Address, Env}; - -#[contract] -pub struct YieldVaultGuardContract; - -#[contractimpl] -impl YieldVaultGuardContract { - pub fn withdraw(env: Env, user: Address, shares_in: i128) { - user.require_auth(); - - // 1. Query the user's current share balance from contract storage - let user_balance = Self::get_share_balance(&env, &user); - - // 2. Revert if requested shares exceed available balance - if shares_in > user_balance { - panic!("InsufficientShares: requested shares exceed user balance"); - } - - // Proceed with burning shares and transferring underlying tokens... - } fn get_share_balance(_env: &Env, _user: &Address) -> i128 { - // Mock lookup implementation: return a fixed balance for demonstration 1000 } -} \ No newline at end of file +} + +#[cfg(test)] +mod test; diff --git a/contracts/yield_vault/src/test.rs b/contracts/yield_vault/src/test.rs index 8c73991b..5e97c5eb 100644 --- a/contracts/yield_vault/src/test.rs +++ b/contracts/yield_vault/src/test.rs @@ -1,61 +1,50 @@ -#[cfg(test)] -mod test { - use super::*; - use soroban_sdk::{Env, Address}; - - #[test] - fn test_deposit_slippage_success() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register(YieldVaultContract, ()); - let client = YieldVaultContractClient::new(&env, &contract_id); - let user = Address::generate(&env); - - let shares = client.deposit(&user, &1000, &950); - assert_eq!(shares, 1000); - } - - #[test] - #[should_panic(expected = "SlippageExceeded")] - fn test_deposit_slippage_revert() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register(YieldVaultContract, ()); - let client = YieldVaultContractClient::new(&env, &contract_id); - let user = Address::generate(&env); - - // Expecting 1050 shares out when deposit yields 1000 -> should panic - client.deposit(&user, &1000, &1050); - } +use super::*; +use soroban_sdk::testutils::Address as _; +use soroban_sdk::{Address, Env}; + +#[test] +fn test_deposit_slippage_success() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(YieldVaultContract, ()); + let client = YieldVaultContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + let shares = client.deposit(&user, &1000, &950); + assert_eq!(shares, 1000); } -#[cfg(test)] -mod test { - use super::*; - use soroban_sdk::{Env, Address}; - - #[test] - fn test_withdraw_sufficient_shares_success() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register(YieldVaultGuardContract, ()); - let client = YieldVaultGuardContractClient::new(&env, &contract_id); - let user = Address::generate(&env); - - // User holds 1000 shares, withdrawing 500 should succeed - client.withdraw(&user, &500); - } - - #[test] - #[should_panic(expected = "InsufficientShares")] - fn test_withdraw_insufficient_shares_revert() { - let env = Env::default(); - env.mock_all_auths(); - let contract_id = env.register(YieldVaultGuardContract, ()); - let client = YieldVaultGuardContractClient::new(&env, &contract_id); - let user = Address::generate(&env); - - // User holds 1000 shares, attempting to withdraw 1500 should panic - client.withdraw(&user, &1500); - } -} \ No newline at end of file +#[test] +#[should_panic(expected = "SlippageExceeded")] +fn test_deposit_slippage_revert() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(YieldVaultContract, ()); + let client = YieldVaultContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + client.deposit(&user, &1000, &1050); +} + +#[test] +fn test_withdraw_sufficient_shares_success() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(YieldVaultContract, ()); + let client = YieldVaultContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + client.withdraw(&user, &500, &0); +} + +#[test] +#[should_panic(expected = "InsufficientShares")] +fn test_withdraw_insufficient_shares_revert() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(YieldVaultContract, ()); + let client = YieldVaultContractClient::new(&env, &contract_id); + let user = Address::generate(&env); + + client.withdraw(&user, &1500, &0); +} diff --git a/contracts/yield_vault/test_snapshots/test/test_deposit_slippage_revert.1.json b/contracts/yield_vault/test_snapshots/test/test_deposit_slippage_revert.1.json new file mode 100644 index 00000000..56557491 --- /dev/null +++ b/contracts/yield_vault/test_snapshots/test/test_deposit_slippage_revert.1.json @@ -0,0 +1,76 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [], + [] + ], + "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_contract_instance", + "durability": "persistent" + } + }, + [ + { + "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" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/yield_vault/test_snapshots/test/test_deposit_slippage_success.1.json b/contracts/yield_vault/test_snapshots/test/test_deposit_slippage_success.1.json new file mode 100644 index 00000000..920ae8d3 --- /dev/null +++ b/contracts/yield_vault/test_snapshots/test/test_deposit_slippage_success.1.json @@ -0,0 +1,139 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "deposit", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "i128": { + "hi": 0, + "lo": 950 + } + } + ] + } + }, + "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_contract_instance", + "durability": "persistent" + } + }, + [ + { + "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" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/yield_vault/test_snapshots/test/test_withdraw_insufficient_shares_revert.1.json b/contracts/yield_vault/test_snapshots/test/test_withdraw_insufficient_shares_revert.1.json new file mode 100644 index 00000000..56557491 --- /dev/null +++ b/contracts/yield_vault/test_snapshots/test/test_withdraw_insufficient_shares_revert.1.json @@ -0,0 +1,76 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [], + [] + ], + "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_contract_instance", + "durability": "persistent" + } + }, + [ + { + "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" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/yield_vault/test_snapshots/test/test_withdraw_sufficient_shares_success.1.json b/contracts/yield_vault/test_snapshots/test/test_withdraw_sufficient_shares_success.1.json new file mode 100644 index 00000000..4ffc8a57 --- /dev/null +++ b/contracts/yield_vault/test_snapshots/test/test_withdraw_sufficient_shares_success.1.json @@ -0,0 +1,139 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "withdraw", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 500 + } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } + } + ] + } + }, + "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_contract_instance", + "durability": "persistent" + } + }, + [ + { + "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" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file