From bb51af51b253afbdea26659c402dcb05a464b236 Mon Sep 17 00:00:00 2001 From: Edukpe David Date: Wed, 26 Aug 2026 17:41:47 +0100 Subject: [PATCH 1/4] test(refund-vault): cover amount and i128 boundaries --- Cargo.toml | 3 +- README.md | 46 +++++ contracts/refund-vault/src/fuzz_test.rs | 198 +++++++++++++++++++++- contracts/refund-vault/src/lib.rs | 16 +- contracts/refund-vault/src/test.rs | 187 ++++++++++++++++++++ contracts/refund-vault/src/yield_tests.rs | 162 ++++++++++-------- 6 files changed, 523 insertions(+), 89 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 337a09d9..62da32e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ warnings = "deny" [workspace.lints.clippy] all = "warn" - +[profile.dev] +overflow-checks = true [profile.release] opt-level = "z" diff --git a/README.md b/README.md index e518082c..94ef8824 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,52 @@ If a `BatchRecord` or `RefundRecord` is archived, it must be restored by submitt For a complete breakdown of what is stored, why it is persistent, and the rent cost implications, read the [Storage Audit](docs/storage-audit.md). +## Amount Semantics + +All `RefundVault` amounts are **integer token base units** (`i128`). +No floating-point arithmetic is used anywhere in the contract. + +### 7-decimal Stellar assets + +Stellar assets such as USDC and native XLM use **7 decimal places**: + +| Unit | Base units | +|---|---| +| 1 stroop (smallest) | `1` | +| 1 token | `10_000_000` | +| 5 USDC | `50_000_000` | + +Worked example — refunding 5 USDC: + +``` +5 USDC × 10_000_000 = 50_000_000 base units +``` + +The contract stores and transfers exactly `50_000_000` as an `i128`. + +### RefundMax + +`RefundMax` is a **reserved storage key** (`DataKey::RefundMax` in `lib.rs`) +that is not currently set, read, or enforced by any contract function. +The `AmountExceedsMax` error (code 11) is defined but unreachable from the +`refund` path today. + +When implemented, `RefundMax` would be an `i128` value in the same integer +base units as all other amounts — e.g., `10_000_000` for a 1-token limit +on a 7-decimal asset. + +### refund_window_ledgers + +`refund_window_ledgers` is denominated in **Stellar ledgers**, not seconds. +The testnet deployment uses `17_280`: + +``` +17_280 ledgers × ~5 seconds/ledger ≈ 86_400 seconds ≈ 24 hours +``` + +This is an **approximate** wall-clock duration because ledger close times +vary. Setting `0` disables the window entirely (no expiry). + ## Live on Testnet | Contract | ID | diff --git a/contracts/refund-vault/src/fuzz_test.rs b/contracts/refund-vault/src/fuzz_test.rs index 389b0889..32463cd7 100644 --- a/contracts/refund-vault/src/fuzz_test.rs +++ b/contracts/refund-vault/src/fuzz_test.rs @@ -4,7 +4,7 @@ use crate::{Error, RefundVault, RefundVaultClient}; use proptest::prelude::*; use soroban_sdk::{ testutils::{Address as _, Ledger}, - token::StellarAssetClient, + token::{StellarAssetClient, TokenClient}, Address, BytesN, Env, }; @@ -29,7 +29,9 @@ fn setup(window: u32) -> (Env, RefundVaultClient<'static>, Address, Address) { proptest! { #[test] - fn test_fuzz_deposit_extreme_amounts(amount in proptest::num::i128::ANY) { + fn test_fuzz_deposit_extreme_amounts( + amount in proptest::num::i128::ANY + ) { let (_, client, merchant, _) = setup(100); let res = client.try_deposit(&merchant, &amount); if amount <= 0 { @@ -43,12 +45,198 @@ proptest! { } #[test] - fn test_fuzz_ttl_extension(ledger in 1u32..1000000u32) { + fn test_fuzz_ttl_extension( + ledger in 1u32..1_000_000u32 + ) { let (env, client, _, _) = setup(100); env.ledger().set_sequence_number(ledger); - let payment_ref = BytesN::from_array(&env, &[0; 32]); - let res = client.try_extend_refund_ttl(&payment_ref); + let payment_ref = + BytesN::from_array(&env, &[0; 32]); + let res = client.try_extend_refund_ttl( + &payment_ref, + ); assert_eq!(res, Err(Ok(Error::RefundNotFound))); } + + #[test] + fn test_fuzz_refund_i128_boundaries( + amount in prop_oneof![ + Just(0i128), + Just(1i128), + Just(-1i128), + Just(i128::MIN), + Just(i128::MIN + 1), + Just(i128::MAX), + Just(i128::MAX - 1), + proptest::num::i128::ANY, + ] + ) { + let (env, client, merchant, _token) = + setup(100); + client.deposit(&merchant, &100); + + let payment_ref = + BytesN::from_array(&env, &[0u8; 32]); + let buyer = Address::generate(&env); + let res = client.try_refund( + &payment_ref, &buyer, &amount, &0, + ); + + if amount <= 0 { + assert_eq!( + res, Err(Ok(Error::InvalidAmount)) + ); + } else if amount > 100 { + assert_eq!( + res, + Err(Ok(Error::InsufficientFloat)) + ); + } else { + assert!(res.is_ok()); + } + } + + #[test] + fn test_fuzz_deposit_i128_boundaries( + amount in prop_oneof![ + Just(0i128), + Just(1i128), + Just(-1i128), + Just(i128::MIN), + Just(i128::MIN + 1), + Just(i128::MAX), + Just(i128::MAX - 1), + proptest::num::i128::ANY, + ] + ) { + let (_, client, merchant, _) = setup(100); + let res = client.try_deposit( + &merchant, &amount, + ); + + if amount <= 0 { + assert_eq!( + res, Err(Ok(Error::InvalidAmount)) + ); + } else if amount > FLOAT { + assert!(res.is_err()); + } else { + assert!(res.is_ok()); + } + } +} + +// ── Accounting invariant fuzz test ───────────────────────────────────────── + +#[derive(Debug, Clone)] +enum VaultOp { + Deposit(i128), + Refund(i128), + Withdraw(i128), +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(50))] + + #[test] + fn test_fuzz_vault_accounting_invariant( + ops in prop::collection::vec( + prop_oneof![ + (1i128..100_000).prop_map(VaultOp::Deposit), + (1i128..1_000).prop_map(VaultOp::Refund), + (1i128..1_000).prop_map(VaultOp::Withdraw), + ], + 0..30, + ) + ) { + let (env, client, merchant, token) = + setup(100_000_000); + let token_client = TokenClient::new( + &env, &token, + ); + + let mut total_deposits: i128 = 0; + let mut total_refunds: i128 = 0; + let mut total_withdrawals: i128 = 0; + let mut refund_counter: u32 = 0; + + for op in ops { + match op { + VaultOp::Deposit(amount) => { + if token_client.balance(&merchant) + >= amount + { + if client + .try_deposit( + &merchant, &amount, + ) + .is_ok() + { + total_deposits += amount; + } + } + } + VaultOp::Refund(amount) => { + let mut pr = [0u8; 32]; + pr[..4].copy_from_slice( + &refund_counter.to_le_bytes(), + ); + refund_counter = refund_counter + .wrapping_add(1); + let payment_ref = + BytesN::from_array(&env, &pr); + let buyer = + Address::generate(&env); + if client + .try_refund( + &payment_ref, + &buyer, + &amount, + &0, + ) + .is_ok() + { + total_refunds += amount; + } + } + VaultOp::Withdraw(amount) => { + if client + .try_withdraw( + &amount, &merchant, + ) + .is_ok() + { + total_withdrawals += amount; + } + } + } + } + + let vault_balance = token_client + .balance(&client.address); + + // Invariant 1: vault float is non-negative. + prop_assert!( + vault_balance >= 0, + "vault balance must be >= 0, got {}", + vault_balance, + ); + + // Invariant 2: without yield, vault balance + // equals net flow through the contract. + prop_assert_eq!( + vault_balance, + total_deposits + - total_refunds + - total_withdrawals, + "vault balance ({}) must equal \ + deposits ({}) - refunds ({}) \ + - withdrawals ({})", + vault_balance, + total_deposits, + total_refunds, + total_withdrawals, + ); + } } diff --git a/contracts/refund-vault/src/lib.rs b/contracts/refund-vault/src/lib.rs index 3d315748..ba587ba5 100644 --- a/contracts/refund-vault/src/lib.rs +++ b/contracts/refund-vault/src/lib.rs @@ -548,11 +548,7 @@ impl RefundVault { } // Transfer tokens to strategy and record the deposit. - token_client.transfer( - &env.current_contract_address(), - &strategy, - &amount, - ); + token_client.transfer(&env.current_contract_address(), &strategy, &amount); env.storage() .instance() @@ -619,12 +615,10 @@ impl RefundVault { .get(&DataKey::HarvestedYield) .unwrap_or(0); - env.storage() - .instance() - .set( - &DataKey::DeployedPrincipal, - &(deployed - principal_returned), - ); + env.storage().instance().set( + &DataKey::DeployedPrincipal, + &(deployed - principal_returned), + ); env.storage() .instance() .set(&DataKey::HarvestedYield, &(harvested + yield_returned)); diff --git a/contracts/refund-vault/src/test.rs b/contracts/refund-vault/src/test.rs index 033dc36a..31185183 100644 --- a/contracts/refund-vault/src/test.rs +++ b/contracts/refund-vault/src/test.rs @@ -618,3 +618,190 @@ fn test_admin_transfer_events_emitted() { ] ); } + +// ── Boundary tests (issue #57) ───────────────────────────────────────────── + +// A. Available float boundary + +#[test] +fn test_refund_exact_available_float_succeeds() { + let (env, client, merchant, token) = setup(100); + client.deposit(&merchant, &500_000); + + let payment_ref = BytesN::from_array(&env, &[20u8; 32]); + let buyer = Address::generate(&env); + client.refund(&payment_ref, &buyer, &500_000, &0); + + let token_client = TokenClient::new(&env, &token); + assert_eq!(token_client.balance(&client.address), 0); + assert_eq!(token_client.balance(&buyer), 500_000); + + let record = client.get_refund(&payment_ref).unwrap(); + assert_eq!(record.amount, 500_000); +} + +#[test] +fn test_refund_available_float_plus_one_fails() { + let (env, client, merchant, _token) = setup(100); + client.deposit(&merchant, &500_000); + + let payment_ref = BytesN::from_array(&env, &[21u8; 32]); + let buyer = Address::generate(&env); + assert_eq!( + client.try_refund(&payment_ref, &buyer, &500_001, &0), + Err(Ok(Error::InsufficientFloat)) + ); +} + +// B. Invalid amounts (separate named tests) + +#[test] +fn test_refund_zero_amount_fails() { + let (env, client, _merchant, _token) = setup(100); + let payment_ref = BytesN::from_array(&env, &[22u8; 32]); + let buyer = Address::generate(&env); + assert_eq!( + client.try_refund(&payment_ref, &buyer, &0, &0), + Err(Ok(Error::InvalidAmount)) + ); +} + +#[test] +fn test_refund_negative_one_amount_fails() { + let (env, client, _merchant, _token) = setup(100); + let payment_ref = BytesN::from_array(&env, &[23u8; 32]); + let buyer = Address::generate(&env); + assert_eq!( + client.try_refund(&payment_ref, &buyer, &-1, &0), + Err(Ok(Error::InvalidAmount)) + ); +} + +#[test] +fn test_refund_i128_min_amount_fails() { + let (env, client, _merchant, _token) = setup(100); + let payment_ref = BytesN::from_array(&env, &[24u8; 32]); + let buyer = Address::generate(&env); + assert_eq!( + client.try_refund(&payment_ref, &buyer, &i128::MIN, &0), + Err(Ok(Error::InvalidAmount)) + ); +} + +// C. Smallest unit + +#[test] +fn test_refund_smallest_unit_succeeds() { + let (env, client, merchant, token) = setup(100); + client.deposit(&merchant, &500_000); + + let payment_ref = BytesN::from_array(&env, &[25u8; 32]); + let buyer = Address::generate(&env); + // Refund exactly 1 stroop (smallest token unit). + client.refund(&payment_ref, &buyer, &1, &0); + + let token_client = TokenClient::new(&env, &token); + assert_eq!(token_client.balance(&client.address), 499_999); + assert_eq!(token_client.balance(&buyer), 1); + + let record = client.get_refund(&payment_ref).unwrap(); + assert_eq!(record.amount, 1); + assert_eq!(record.recipient, buyer); +} + +// D. RefundMax + +#[test] +fn test_refund_no_refundmax_enforced_currently() { + // RefundMax is a reserved DataKey (lib.rs:47) with no setter, getter, + // or enforcement logic. AmountExceedsMax (error 11) is defined but + // unreachable from the refund path. Document that any amount up to + // the vault float succeeds without an AmountExceedsMax error. + let (env, client, merchant, token) = setup(100); + client.deposit(&merchant, &500_000); + + let payment_ref = BytesN::from_array(&env, &[26u8; 32]); + let buyer = Address::generate(&env); + // Refund the entire float — no max limit intervenes. + client.refund(&payment_ref, &buyer, &500_000, &0); + + let token_client = TokenClient::new(&env, &token); + assert_eq!(token_client.balance(&client.address), 0); + + let record = client.get_refund(&payment_ref).unwrap(); + assert_eq!(record.amount, 500_000); +} + +// E. u32 overflow in window check + +#[test] +#[should_panic] +fn test_refund_window_u32_addition_overflow() { + // The refund path computes `paid_at_ledger + window` as u32. + // With paid_at_ledger = 1 and window = u32::MAX the addition + // overflows. overflow-checks = true (dev + release) catches this. + let (env, client, merchant, _token) = setup(u32::MAX); + client.deposit(&merchant, &500_000); + + let payment_ref = BytesN::from_array(&env, &[27u8; 32]); + let buyer = Address::generate(&env); + client.refund(&payment_ref, &buyer, &100, &1); +} + +// F. Repeated small refunds + +#[test] +fn test_repeated_small_refunds_exact_accounting() { + let (env, client, merchant, token) = setup(100); + client.deposit(&merchant, &1_000_000); + + let token_client = TokenClient::new(&env, &token); + let count: i128 = 1_000; + let each: i128 = 1; // 1 stroop per refund + + for i in 0..count { + let mut pr_bytes = [0u8; 32]; + pr_bytes[..8].copy_from_slice(&(i as u64).to_le_bytes()); + let payment_ref = BytesN::from_array(&env, &pr_bytes); + let buyer = Address::generate(&env); + client.refund(&payment_ref, &buyer, &each, &0); + } + + // Vault lost exactly count * each stroops. + assert_eq!(token_client.balance(&client.address), 1_000_000 - count); + // No rounding or drift — integer arithmetic is exact. + assert_eq!(count * each, 1_000); +} + +// Decimal semantics: 7-decimal token base units + +#[test] +fn test_7decimal_token_base_units() { + // For 7-decimal Stellar assets (USDC, XLM): + // 1 token = 10_000_000 base units + // 1 stroop = 1 base unit + // 5 USDC = 50_000_000 base units + // + // All RefundVault amounts are integer base units. + let one_token: i128 = 10_000_000; + let five_usdc: i128 = 50_000_000; + let one_stroop: i128 = 1; + + assert_eq!(one_token * 5, five_usdc); + assert_eq!(one_stroop, 1); + + // Use these values through the vault — all integer, no floats. + let (env, client, merchant, token) = setup(100); + client.deposit(&merchant, &five_usdc); + + let token_client = TokenClient::new(&env, &token); + assert_eq!(token_client.balance(&client.address), five_usdc); + + // Refund 1 USDC = 10_000_000 base units. + let payment_ref = BytesN::from_array(&env, &[28u8; 32]); + let buyer = Address::generate(&env); + client.refund(&payment_ref, &buyer, &one_token, &0); + + assert_eq!(token_client.balance(&buyer), one_token); + assert_eq!(token_client.balance(&client.address), five_usdc - one_token); +} diff --git a/contracts/refund-vault/src/yield_tests.rs b/contracts/refund-vault/src/yield_tests.rs index 23ffa137..3ed147a3 100644 --- a/contracts/refund-vault/src/yield_tests.rs +++ b/contracts/refund-vault/src/yield_tests.rs @@ -51,7 +51,11 @@ impl MockYieldStrategy { /// Admin-only: simulate yield accrual by increasing the tracked yield. /// In a real strategy this would happen organically from lending interest. pub fn simulate_yield(env: Env, amount: i128) { - let admin: Address = env.storage().instance().get(&StrategyDataKey::Admin).unwrap(); + let admin: Address = env + .storage() + .instance() + .get(&StrategyDataKey::Admin) + .unwrap(); admin.require_auth(); let current: i128 = env @@ -107,7 +111,11 @@ impl MockYieldStrategy { let total_return = principal + yield_portion; // Transfer tokens back to the vault. - let token_addr: Address = env.storage().instance().get(&StrategyDataKey::Token).unwrap(); + let token_addr: Address = env + .storage() + .instance() + .get(&StrategyDataKey::Token) + .unwrap(); let token_client = TokenClient::new(&env, &token_addr); let vault_addr = env .storage() @@ -120,9 +128,10 @@ impl MockYieldStrategy { env.storage() .instance() .set(&StrategyDataKey::TotalDeposited, &(total - principal)); - env.storage() - .instance() - .set(&StrategyDataKey::YieldAccrued, &(yield_accrued - yield_portion)); + env.storage().instance().set( + &StrategyDataKey::YieldAccrued, + &(yield_accrued - yield_portion), + ); Ok((principal, yield_portion)) } @@ -139,7 +148,11 @@ impl MockYieldStrategy { } // Transfer yield tokens to the vault. - let token_addr: Address = env.storage().instance().get(&StrategyDataKey::Token).unwrap(); + let token_addr: Address = env + .storage() + .instance() + .get(&StrategyDataKey::Token) + .unwrap(); let token_client = TokenClient::new(&env, &token_addr); let vault_addr = env .storage() @@ -156,7 +169,11 @@ impl MockYieldStrategy { } pub fn total_balance(env: Env) -> i128 { - let token_addr: Address = env.storage().instance().get(&StrategyDataKey::Token).unwrap(); + let token_addr: Address = env + .storage() + .instance() + .get(&StrategyDataKey::Token) + .unwrap(); let token_client = TokenClient::new(&env, &token_addr); token_client.balance(&env.current_contract_address()) } @@ -254,8 +271,7 @@ fn test_set_yield_strategy_uninitialized_fails() { #[test] fn test_set_yield_strategy_requires_auth() { - let (env, vault_client, _merchant, _token, _strategy, _tc) = - setup_with_strategy(2000, 8000); + let (env, vault_client, _merchant, _token, _strategy, _tc) = setup_with_strategy(2000, 8000); let new_strategy = Address::generate(&env); env.set_auths(&[]); @@ -267,8 +283,7 @@ fn test_set_yield_strategy_requires_auth() { #[test] fn test_set_reserve_ratio_invalid_fails() { - let (_env, vault_client, _merchant, _token, _strategy, _tc) = - setup_with_strategy(0, 10_000); + let (_env, vault_client, _merchant, _token, _strategy, _tc) = setup_with_strategy(0, 10_000); assert_eq!( vault_client.try_set_reserve_ratio(&10_001), @@ -278,8 +293,7 @@ fn test_set_reserve_ratio_invalid_fails() { #[test] fn test_set_max_deploy_ratio_invalid_fails() { - let (_env, vault_client, _merchant, _token, _strategy, _tc) = - setup_with_strategy(0, 10_000); + let (_env, vault_client, _merchant, _token, _strategy, _tc) = setup_with_strategy(0, 10_000); assert_eq!( vault_client.try_set_max_deploy_ratio(&10_001), @@ -291,8 +305,7 @@ fn test_set_max_deploy_ratio_invalid_fails() { #[test] fn test_deploy_to_yield_happy_path() { - let (env, vault_client, merchant, _token, _strategy, tc) = - setup_with_strategy(2000, 8000); + let (env, vault_client, merchant, _token, _strategy, tc) = setup_with_strategy(2000, 8000); // Deposit 5M into vault. vault_client.deposit(&merchant, &5_000_000); @@ -331,8 +344,7 @@ fn test_deploy_without_strategy_fails() { #[test] fn test_deploy_insufficient_reserve_fails() { - let (env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(2000, 8000); + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(2000, 8000); vault_client.deposit(&merchant, &5_000_000); @@ -345,8 +357,7 @@ fn test_deploy_insufficient_reserve_fails() { #[test] fn test_deploy_exceeds_max_ratio_fails() { - let (env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(0, 5000); // 0% reserve, 50% max deploy + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(0, 5000); // 0% reserve, 50% max deploy vault_client.deposit(&merchant, &5_000_000); @@ -359,8 +370,7 @@ fn test_deploy_exceeds_max_ratio_fails() { #[test] fn test_deploy_insufficient_float_fails() { - let (env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &1_000_000); @@ -373,8 +383,7 @@ fn test_deploy_insufficient_float_fails() { #[test] fn test_deploy_zero_fails() { - let (_env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(2000, 8000); + let (_env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(2000, 8000); vault_client.deposit(&merchant, &5_000_000); @@ -386,8 +395,7 @@ fn test_deploy_zero_fails() { #[test] fn test_deploy_when_paused_fails() { - let (_env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(2000, 8000); + let (_env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(2000, 8000); vault_client.deposit(&merchant, &5_000_000); vault_client.pause(); @@ -400,8 +408,7 @@ fn test_deploy_when_paused_fails() { #[test] fn test_deploy_multiple_times() { - let (env, vault_client, merchant, _token, _strategy, tc) = - setup_with_strategy(1000, 8000); + let (env, vault_client, merchant, _token, _strategy, tc) = setup_with_strategy(1000, 8000); vault_client.deposit(&merchant, &5_000_000); @@ -418,8 +425,7 @@ fn test_deploy_multiple_times() { #[test] fn test_withdraw_from_yield_returns_principal_and_yield() { - let (env, vault_client, merchant, _token, strategy_addr, tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, strategy_addr, tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -449,8 +455,7 @@ fn test_withdraw_from_yield_returns_principal_and_yield() { #[test] fn test_withdraw_more_than_deployed_fails() { - let (env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&2_000_000); @@ -463,8 +468,7 @@ fn test_withdraw_more_than_deployed_fails() { #[test] fn test_withdraw_zero_fails() { - let (_env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(0, 10_000); + let (_env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&2_000_000); @@ -498,8 +502,7 @@ fn test_withdraw_without_strategy_fails() { #[test] fn test_withdraw_full_principal() { - let (env, vault_client, merchant, _token, strategy_addr, tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, strategy_addr, tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -522,8 +525,7 @@ fn test_withdraw_full_principal() { #[test] fn test_harvest_yield_happy_path() { - let (env, vault_client, merchant, _token, strategy_addr, tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, strategy_addr, tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -539,13 +541,15 @@ fn test_harvest_yield_happy_path() { let info = vault_client.get_yield_info(); assert_eq!(info.harvested_yield, 200_000); assert_eq!(info.deployed_principal, 3_000_000); // Principal untouched. - assert_eq!(tc.balance(&vault_client.address), vault_balance_before + 200_000); + assert_eq!( + tc.balance(&vault_client.address), + vault_balance_before + 200_000 + ); } #[test] fn test_harvest_nothing_fails() { - let (env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -559,8 +563,7 @@ fn test_harvest_nothing_fails() { #[test] fn test_harvest_accumulates() { - let (env, vault_client, merchant, _token, strategy_addr, _tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, strategy_addr, _tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -583,8 +586,7 @@ fn test_harvest_accumulates() { #[test] fn test_refund_succeeds_after_deploy_within_reserve() { - let (env, vault_client, merchant, _token, _strategy, tc) = - setup_with_strategy(2000, 8000); + let (env, vault_client, merchant, _token, _strategy, tc) = setup_with_strategy(2000, 8000); vault_client.deposit(&merchant, &5_000_000); // Deploy 3M, leaving 2M liquid (>= 20% reserve of 5M = 1M). @@ -601,8 +603,7 @@ fn test_refund_succeeds_after_deploy_within_reserve() { #[test] fn test_refund_exceeding_liquid_after_deploy_fails() { - let (env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(2000, 8000); + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(2000, 8000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); // 2M liquid remaining. @@ -618,8 +619,7 @@ fn test_refund_exceeding_liquid_after_deploy_fails() { #[test] fn test_refund_after_withdraw_from_yield() { - let (env, vault_client, merchant, _token, _strategy, tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, _strategy, tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&4_000_000); // 1M liquid. @@ -638,8 +638,7 @@ fn test_refund_after_withdraw_from_yield() { #[test] fn test_operator_withdraw_harvested_yield() { - let (env, vault_client, merchant, _token, strategy_addr, tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, strategy_addr, tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -662,8 +661,7 @@ fn test_operator_withdraw_harvested_yield() { #[test] fn test_yield_accounting_after_full_cycle() { - let (env, vault_client, merchant, _token, strategy_addr, tc) = - setup_with_strategy(1000, 8000); + let (env, vault_client, merchant, _token, strategy_addr, tc) = setup_with_strategy(1000, 8000); // 1. Deposit. vault_client.deposit(&merchant, &5_000_000); @@ -702,8 +700,7 @@ fn test_yield_accounting_after_full_cycle() { #[test] fn test_deploy_when_paused() { - let (_env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(2000, 8000); + let (_env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(2000, 8000); vault_client.deposit(&merchant, &5_000_000); vault_client.pause(); @@ -716,8 +713,7 @@ fn test_deploy_when_paused() { #[test] fn test_withdraw_from_yield_when_paused() { - let (env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -731,8 +727,7 @@ fn test_withdraw_from_yield_when_paused() { #[test] fn test_harvest_when_paused() { - let (env, vault_client, merchant, _token, strategy_addr, _tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, strategy_addr, _tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -742,10 +737,7 @@ fn test_harvest_when_paused() { vault_client.pause(); - assert_eq!( - vault_client.try_harvest_yield(), - Err(Ok(Error::Paused)) - ); + assert_eq!(vault_client.try_harvest_yield(), Err(Ok(Error::Paused))); } // ── Yield events tests ───────────────────────────────────────────────────── @@ -755,8 +747,7 @@ fn test_yield_deployed_event() { use soroban_sdk::testutils::Events; use soroban_sdk::{vec, IntoVal, Symbol}; - let (env, vault_client, merchant, _token, strategy_addr, _tc) = - setup_with_strategy(2000, 8000); + let (env, vault_client, merchant, _token, strategy_addr, _tc) = setup_with_strategy(2000, 8000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&2_000_000); @@ -779,8 +770,7 @@ fn test_yield_harvested_event() { use soroban_sdk::testutils::Events; use soroban_sdk::{vec, IntoVal, Symbol}; - let (env, vault_client, merchant, _token, strategy_addr, _tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, strategy_addr, _tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); vault_client.deploy_to_yield(&3_000_000); @@ -807,8 +797,7 @@ fn test_yield_harvested_event() { #[test] fn test_zero_reserve_full_deploy() { - let (env, vault_client, merchant, _token, _strategy, tc) = - setup_with_strategy(0, 10_000); + let (env, vault_client, merchant, _token, _strategy, tc) = setup_with_strategy(0, 10_000); vault_client.deposit(&merchant, &5_000_000); @@ -822,8 +811,7 @@ fn test_zero_reserve_full_deploy() { #[test] fn test_full_reserve_cannot_deploy() { - let (env, vault_client, merchant, _token, _strategy, _tc) = - setup_with_strategy(10_000, 10_000); // 100% reserve. + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(10_000, 10_000); // 100% reserve. vault_client.deposit(&merchant, &5_000_000); @@ -838,8 +826,7 @@ fn test_full_reserve_cannot_deploy() { #[test] fn test_existing_deposit_refund_withdraw_still_works() { - let (env, vault_client, merchant, token, _strategy, _tc) = - setup_with_strategy(2000, 8000); + let (env, vault_client, merchant, token, _strategy, _tc) = setup_with_strategy(2000, 8000); // Standard deposit-refund-withdraw flow, no yield involved. vault_client.deposit(&merchant, &5_000_000); @@ -855,3 +842,34 @@ fn test_existing_deposit_refund_withdraw_still_works() { vault_client.withdraw(&200_000, &merchant); assert_eq!(tc.balance(&vault_client.address), 4_680_000); } + +// ── i128 overflow boundary tests (issue #57) ─────────────────────────────── + +#[test] +#[should_panic] +fn test_deploy_to_yield_i128_multiplication_overflow() { + // The deploy_to_yield path computes: + // total_value = token_balance + deployed - harvested + // reserve_required = total_value * reserve_ratio / 10_000 + // + // If total_value is large enough and reserve_ratio > 0, the + // multiplication overflows i128. overflow-checks = true catches + // this as a panic, preventing silent wrapping. + let (env, vault_client, merchant, _token, _strategy, _tc) = setup_with_strategy(10_000, 10_000); + + // Mint a huge amount and deposit it so total_value is enormous. + let huge: i128 = i128::MAX / 10; + let sac = StellarAssetClient::new( + &env, + &env.storage() + .instance() + .get::<_, Address>(&crate::DataKey::Token) + .unwrap(), + ); + sac.mint(&merchant, &huge); + vault_client.deposit(&merchant, &huge); + + // total_value = huge, reserve_ratio = 10_000 (100 %). + // huge * 10_000 overflows i128. + vault_client.deploy_to_yield(&1); +} From ff6825307d5e84400f549f9ce248cf9746da204e Mon Sep 17 00:00:00 2001 From: Edukpe David Date: Wed, 26 Aug 2026 18:22:24 +0100 Subject: [PATCH 2/4] ops: harden deploy script for pubnet --- DEPLOYMENTS.md | 64 ++++++-- deploy.sh | 220 ++++++++++++++++++++++--- deployments/pubnet.env | 24 +++ docs/MAINNET_DEPLOYMENT.md | 269 +++++++++++++++++++++++++----- tests/test_deploy.sh | 325 +++++++++++++++++++++++++++++++++++++ 5 files changed, 827 insertions(+), 75 deletions(-) create mode 100644 deployments/pubnet.env create mode 100644 tests/test_deploy.sh diff --git a/DEPLOYMENTS.md b/DEPLOYMENTS.md index 55e3bf47..32d14d66 100644 --- a/DEPLOYMENTS.md +++ b/DEPLOYMENTS.md @@ -1,11 +1,13 @@ # Deployments -Every Accensa contract deployment is recorded here with its contract ID and the -transaction that created it, so anyone can verify the deployment independently -without trusting this repository. +Every Accensa contract deployment is recorded here with its contract ID and +provenance, so anyone can verify the deployment independently without trusting +this repository. -Machine-readable values live in [`deployments/testnet.env`](deployments/testnet.env) -and are produced by [`deploy.sh`](deploy.sh). +Machine-readable values live in [`deployments/.env`](deployments/) and +are produced by [`deploy.sh`](deploy.sh). + +--- ## Testnet @@ -52,12 +54,12 @@ Deployed 2026-07-22 with `soroban-sdk` 27.0.0, built for `wasm32v1-none`. | Initialize `RefundVault` | [`5c77fc34…`](https://stellar.expert/explorer/testnet/tx/5c77fc346943f56e10fc3666f4640211d721c1754886f107aac9fa696897662e) | | Anchor batch #1 | [`99d0481b…`](https://stellar.expert/explorer/testnet/tx/99d0481bf2b4a00b51f1ca7c3e633d8675dc84ede8eefc6804a00686ff7b8c9a) | -## Verifying the live deployment yourself +### Verifying the live testnet deployment yourself -Batch #1 is anchored on-chain over four demo receipts. Its Merkle root was computed -off-chain by the TypeScript SDK (`packages/sdk` in -[`accensa-app`](https://github.com/accensa/accensa-app)) and verified on-chain by -`ReceiptAnchor.verify_receipt` — the two implementations agree on the same +Batch #1 is anchored on-chain over four demo receipts. Its Merkle root was +computed off-chain by the TypeScript SDK (`packages/sdk` in +[`accensa-app`](https://github.com/accensa/accensa-app)) and verified on-chain +by `ReceiptAnchor.verify_receipt` — the two implementations agree on the same sorted-pair SHA-256 convention. Read the anchored batch: @@ -93,13 +95,45 @@ stellar contract invoke \ Both are read-only simulations and cost nothing to run. -## Redeploying +--- + +## Pubnet (Mainnet) + +> **No pubnet deployment has been performed yet.** +> +> See [`docs/MAINNET_DEPLOYMENT.md`](docs/MAINNET_DEPLOYMENT.md) for the +> pre-deployment checklist covering upgradeability, audit status, key custody, +> USDC SAC verification, refund window configuration, and rent funding. + +| Contract | Contract ID | Explorer | +|---|---|---| +| `ReceiptAnchor` | *(pending deployment)* | *(pending)* | +| `RefundVault` | *(pending deployment)* | *(pending)* | + +Once deployed, this section will include: + +- Contract IDs and explorer links +- Merchant / admin address +- Refund token (verified USDC SAC address) +- Refund window configuration +- Version and commit SHA +- WASM hashes for both contracts +- Deployment transactions + +Machine-readable values will be recorded in [`deployments/pubnet.env`](deployments/pubnet.env). + +--- + +## Deploying ```bash -./deploy.sh # testnet, identity "deployer" -NETWORK=futurenet ./deploy.sh # another network -TOKEN= ./deploy.sh # settle refunds in USDC instead of XLM +./deploy.sh # testnet (default), identity "deployer" +NETWORK=futurenet ./deploy.sh # another network +TOKEN= ./deploy.sh # settle refunds in USDC instead of XLM + +# Pubnet — requires clean working tree, main branch, and explicit confirmation: +NETWORK=pubnet TOKEN= ./deploy.sh ``` -The script writes `deployments/.env`. Commit that file so the record +The script writes `deployments/.env`. Commit that file so the record stays reproducible. diff --git a/deploy.sh b/deploy.sh index e24acab5..26593bf5 100755 --- a/deploy.sh +++ b/deploy.sh @@ -4,6 +4,9 @@ # committed, independently verifiable trail instead of scrolling past in stdout. set -euo pipefail +# --------------------------------------------------------------------------- +# Defaults +# --------------------------------------------------------------------------- NETWORK="${NETWORK:-testnet}" IDENTITY="${IDENTITY:-deployer}" # Testnet native XLM SAC. Override with TOKEN=... to use USDC or another asset. @@ -12,11 +15,156 @@ TOKEN="${TOKEN:-CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC}" REFUND_WINDOW_LEDGERS="${REFUND_WINDOW_LEDGERS:-17280}" OUT_DIR="deployments" -OUT_FILE="$OUT_DIR/$NETWORK.env" +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +# Compute SHA-256 of a file in a portable way (Linux sha256sum / macOS shasum). +sha256_of() { + local file="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$file" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$file" | awk '{print $1}' + else + echo "unknown" + fi +} + +# Parse command-line arguments. Safe to call from tests. +parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + --network) + if [ -z "${2:-}" ]; then + echo "Error: --network requires a value" >&2 + exit 1 + fi + NETWORK="$2" + shift 2 + ;; + *) + echo "Error: unknown argument '$1'" >&2 + echo "Usage: $0 [--network testnet|futurenet|pubnet]" >&2 + exit 1 + ;; + esac + done +} + +# Validate pubnet deployment prerequisites. +# Exits with a non-zero status if any check fails. +validate_pubnet() { + local errors=0 + + echo "" + echo "🔒 Pubnet pre-flight checks" + echo "----------------------------------------------------------" + + # 1. Clean working tree + if ! git diff --quiet 2>/dev/null; then + echo "❌ FAIL: Working tree has uncommitted changes." >&2 + echo " Commit or stash all changes before deploying to pubnet." >&2 + errors=$((errors + 1)) + fi + if ! git diff --cached --quiet 2>/dev/null; then + echo "❌ FAIL: Staged changes in working tree." >&2 + echo " Commit or unstage all changes before deploying to pubnet." >&2 + errors=$((errors + 1)) + fi + + # 2. Current branch is main + local branch + branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") + if [ "$branch" != "main" ]; then + echo "❌ FAIL: Current branch is '$branch', not 'main'." >&2 + echo " Pubnet deployments must originate from the main branch." >&2 + errors=$((errors + 1)) + fi + + # 3. Commit SHA + local commit_sha + commit_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown") + if [ "$commit_sha" = "unknown" ]; then + echo "❌ FAIL: Cannot determine commit SHA." >&2 + errors=$((errors + 1)) + fi + + if [ "$errors" -gt 0 ]; then + echo "" >&2 + echo "❌ $errors pre-flight check(s) failed. Aborting." >&2 + exit 1 + fi + + echo "✅ Working tree is clean" + echo "✅ Branch is main" + echo "✅ Commit SHA: $commit_sha" + echo "" +} + +# --------------------------------------------------------------------------- +# Argument parsing +# --------------------------------------------------------------------------- +parse_args "$@" + +# --------------------------------------------------------------------------- +# Pubnet safety gate: all validation and confirmation happens BEFORE any +# deploy command. This is the single control point for pubnet access. +# --------------------------------------------------------------------------- +if [ "$NETWORK" = "pubnet" ]; then + validate_pubnet +fi + +# --------------------------------------------------------------------------- +# Build +# --------------------------------------------------------------------------- echo "🚀 Building contracts..." stellar contract build +# --------------------------------------------------------------------------- +# Compute WASM hashes (must be after build, before deploy) +# --------------------------------------------------------------------------- +ANCHOR_WASM="target/wasm32v1-none/release/receipt_anchor.wasm" +VAULT_WASM="target/wasm32v1-none/release/refund_vault.wasm" + +ANCHOR_HASH=$(sha256_of "$ANCHOR_WASM") +VAULT_HASH=$(sha256_of "$VAULT_WASM") + +ANCHOR_VERSION=$(grep -m 1 "^version" contracts/receipt-anchor/Cargo.toml | cut -d '"' -f 2) +VAULT_VERSION=$(grep -m 1 "^version" contracts/refund-vault/Cargo.toml | cut -d '"' -f 2) +COMMIT_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown") + +# --------------------------------------------------------------------------- +# Pubnet: display artifacts and require explicit WASM hash confirmation +# --------------------------------------------------------------------------- +if [ "$NETWORK" = "pubnet" ]; then + echo "" + echo "===========================================================" + echo "⚠️ PUBNET DEPLOYMENT — ARTIFACT VERIFICATION" + echo "===========================================================" + echo "" + echo "Contract versions: ReceiptAnchor $ANCHOR_VERSION, RefundVault $VAULT_VERSION" + echo "Commit SHA: $COMMIT_SHA" + echo "" + echo "WASM artifacts to deploy:" + echo " ReceiptAnchor $ANCHOR_HASH ($ANCHOR_WASM)" + echo " RefundVault $VAULT_HASH ($VAULT_WASM)" + echo "" + echo "Token: $TOKEN" + echo "Refund window: $REFUND_WINDOW_LEDGERS ledgers" + echo "" + read -r -p "Type YES to confirm these are the correct artifacts for pubnet: " confirm + if [ "$confirm" != "YES" ]; then + echo "Aborted: confirmation not received." >&2 + exit 1 + fi + echo "" +fi + +# --------------------------------------------------------------------------- +# Identity setup +# --------------------------------------------------------------------------- echo "🔑 Using identity '$IDENTITY' on network '$NETWORK'..." if ! stellar keys address "$IDENTITY" >/dev/null 2>&1; then echo " Identity not found, generating..." @@ -30,15 +178,18 @@ if [ "$NETWORK" = "testnet" ] || [ "$NETWORK" = "futurenet" ]; then stellar keys fund "$IDENTITY" --network "$NETWORK" || true fi +# --------------------------------------------------------------------------- +# Deploy +# --------------------------------------------------------------------------- echo "🚢 Deploying ReceiptAnchor..." ANCHOR_ID=$(stellar contract deploy \ - --wasm target/wasm32v1-none/release/receipt_anchor.wasm \ + --wasm "$ANCHOR_WASM" \ --source "$IDENTITY" --network "$NETWORK" 2>/dev/null | tail -n 1) echo " ReceiptAnchor: $ANCHOR_ID" echo "🚢 Deploying RefundVault..." VAULT_ID=$(stellar contract deploy \ - --wasm target/wasm32v1-none/release/refund_vault.wasm \ + --wasm "$VAULT_WASM" \ --source "$IDENTITY" --network "$NETWORK" 2>/dev/null | tail -n 1) echo " RefundVault: $VAULT_ID" @@ -51,25 +202,12 @@ stellar contract invoke --id "$VAULT_ID" --source "$IDENTITY" --network "$NETWOR -- initialize --merchant "$DEPLOYER" --token "$TOKEN" \ --refund_window_ledgers "$REFUND_WINDOW_LEDGERS" -ANCHOR_WASM="target/wasm32v1-none/release/receipt_anchor.wasm" -VAULT_WASM="target/wasm32v1-none/release/refund_vault.wasm" - -COMMIT_SHA=$(git rev-parse HEAD || echo "unknown") -ANCHOR_VERSION=$(grep -m 1 "^version" contracts/receipt-anchor/Cargo.toml | cut -d '"' -f 2) -VAULT_VERSION=$(grep -m 1 "^version" contracts/refund-vault/Cargo.toml | cut -d '"' -f 2) - -if command -v sha256sum >/dev/null 2>&1; then - ANCHOR_HASH=$(sha256sum "$ANCHOR_WASM" | awk '{print $1}') - VAULT_HASH=$(sha256sum "$VAULT_WASM" | awk '{print $1}') -elif command -v shasum >/dev/null 2>&1; then - ANCHOR_HASH=$(shasum -a 256 "$ANCHOR_WASM" | awk '{print $1}') - VAULT_HASH=$(shasum -a 256 "$VAULT_WASM" | awk '{print $1}') -else - ANCHOR_HASH="unknown" - VAULT_HASH="unknown" -fi - +# --------------------------------------------------------------------------- +# Record deployment metadata +# --------------------------------------------------------------------------- mkdir -p "$OUT_DIR" +OUT_FILE="$OUT_DIR/$NETWORK.env" + cat > "$OUT_FILE" </dev/null || echo "UNKNOWN") + echo "$anchor_meta_version" + if [ "$anchor_meta_version" != "$ANCHOR_VERSION" ]; then + echo " ⚠️ Version mismatch! Expected $ANCHOR_VERSION, got $anchor_meta_version" >&2 + verify_ok=false + fi + + echo -n " RefundVault version: " + vault_meta_version=$(stellar contract invoke --id "$VAULT_ID" --network "$NETWORK" \ + --source "$IDENTITY" -- get_version 2>/dev/null || echo "UNKNOWN") + echo "$vault_meta_version" + if [ "$vault_meta_version" != "$VAULT_VERSION" ]; then + echo " ⚠️ Version mismatch! Expected $VAULT_VERSION, got $vault_meta_version" >&2 + verify_ok=false + fi + + if [ "$verify_ok" = false ]; then + echo "" >&2 + echo "❌ Post-deployment verification FAILED. Review the output above." >&2 + echo " Deployment metadata has been written to $OUT_FILE." >&2 + exit 1 + fi + + echo "✅ Deployed contract versions match source" + echo "" + echo "===========================================================" + echo "✅ PUBNET DEPLOYMENT VERIFIED" + echo "===========================================================" +fi diff --git a/deployments/pubnet.env b/deployments/pubnet.env new file mode 100644 index 00000000..e675b966 --- /dev/null +++ b/deployments/pubnet.env @@ -0,0 +1,24 @@ +# Deployments are recorded here by deploy.sh after a successful pubnet run. +# +# DO NOT edit this file manually. It is written automatically by: +# NETWORK=pubnet TOKEN= ./deploy.sh +# +# No pubnet deployment has been performed yet. The values below are +# placeholders that will be replaced by deploy.sh on the first real run. +# +# See docs/MAINNET_DEPLOYMENT.md for the pre-deployment checklist and +# DEPLOYMENTS.md for the human-readable deployment record. + +# --- filled by deploy.sh after deployment --- +# Generated by deploy.sh on +# Network: pubnet +# Commit: +NEXT_PUBLIC_RECEIPT_ANCHOR_ID= +NEXT_PUBLIC_REFUND_VAULT_ID= +MERCHANT_ADDRESS= +TOKEN_ADDRESS= +REFUND_WINDOW_LEDGERS= +RECEIPT_ANCHOR_VERSION= +RECEIPT_ANCHOR_WASM_HASH= +REFUND_VAULT_VERSION= +REFUND_VAULT_WASM_HASH= diff --git a/docs/MAINNET_DEPLOYMENT.md b/docs/MAINNET_DEPLOYMENT.md index d00d39df..8cb79acc 100644 --- a/docs/MAINNET_DEPLOYMENT.md +++ b/docs/MAINNET_DEPLOYMENT.md @@ -1,63 +1,254 @@ # Mainnet Deployment Guide -Deploying `accensa-contracts` to the Stellar Mainnet is similar to the testnet process, but requires careful handling of real funds, precise configuration of the USDC SAC token, and an understanding of transaction and storage rent fees. +Deploying `accensa-contracts` to Stellar Mainnet (pubnet) requires deliberate +preparation. This document covers every decision and action required before, +during, and after a pubnet deployment. -## Step-by-Step Mainnet Deployment +> **No pubnet deployment has been performed yet.** The values in +> [`deployments/pubnet.env`](../deployments/pubnet.env) are placeholders. +> [`deploy.sh`](../deploy.sh) will not deploy to pubnet unless explicitly +> instructed with `--network pubnet` and confirmed interactively. -### 1. Fund a Deployer Account -You need a Stellar account with sufficient XLM to cover base reserves, transaction fees, and storage rent. -- Create an account on the Stellar Mainnet. -- Fund it with at least 50-100 XLM to comfortably cover contract storage rent and execution fees. +--- + +## Pre-Deployment Checklist + +Every item in this section **must be completed before running `deploy.sh`**. +The deployment will fail with `set -euo pipefail` if any required configuration +is missing. + +### 1. Upgradeability Decision (Issue #55) + +Soroban contracts are **not upgradeable** by default. Once deployed, a contract +ID is bound to its uploaded WASM. A new deployment mints a new contract ID. + +- [ ] Confirm whether the contracts will be deployed as immutable or whether a + upgradeability mechanism (e.g., a router proxy, a WASM replacement via the + Stellar `upgrade` facility, or a key rotation) is in scope. +- [ ] If upgradeability is desired, document the mechanism and ensure it is + implemented and tested before deployment. +- [ ] If the contracts are immutable, acknowledge that any future bug fix or + feature addition requires a new deployment and coordinated migration. + +**Status:** Open — tracked in +[#55](https://github.com/accensa/accensa-contracts/issues/55). + +### 2. Audit Position (Issue #60) + +The smart contracts are currently **unaudited** (see [`SECURITY.md`](../SECURITY.md)). + +- [ ] Determine whether an external audit is required before pubnet deployment. +- [ ] If an audit is commissioned, record the audit firm, report URL, and the + commit SHA that was audited. +- [ ] If proceeding without an audit, document the risk acceptance and the + rationale (e.g., limited blast radius, testnet validation period). + +**Status:** Open — tracked in +[#60](https://github.com/accensa/accensa-contracts/issues/60). + +### 3. Admin Key Custody and Multisig + +The admin (merchant) key is the single point of trust for `ReceiptAnchor` and +`RefundVault`. A compromised key allows an attacker to drain vault float, +pause operations, or prune receipt batches. + +- [ ] Decide whether the admin key will be a single ed25519 keypair or a + multisig / smart-account signer from day one. +- [ ] If using multisig: configure the signer set, thresholds, and recovery + procedure. Document the signer addresses. +- [ ] If using a single key: document the key custody procedure (HSM, air-gapped + machine, key sharding, etc.). +- [ ] Ensure the deployer identity used by `deploy.sh` has the correct key + material available on the deployment machine. +- [ ] Verify that the admin key can sign Soroban `invokeAuth` transactions by + performing a dry-run on testnet with the production key. + +### 4. USDC Stellar Asset Contract (SAC) Address + +`RefundVault` settles refunds in USDC via the Stellar Asset Contract. The SAC +address is network-specific and **must be verified** against the authoritative +source. + +- [ ] Look up the current USDC SAC address on Stellar Mainnet from the + [Stellar USDC issuer account](https://stellar.expert/explorer/public/asset/USDC-GA5ZSEJYB37JDE5B6L17IAZEMAZ2Z2KSS6Y72Y2E5M4NOBYPCU6U5AIN) + or the [Circle / Stellar documentation](https://www.stellar.org/developers/guides/issuing-assets.html). +- [ ] Record the verified address here: + + **Mainnet USDC SAC address:** `______________________________` + +- [ ] Pass this address as the `TOKEN` environment variable during deployment: + + ```bash + NETWORK=pubnet TOKEN= ./deploy.sh + ``` + +- [ ] After deployment, verify the vault was initialized with the correct token + by reading back the contract state. + +> ⚠️ **Never guess or copy a token address from a testnet deployment.** +> Testnet and mainnet SAC addresses are different. Deploying with the wrong +> token address means refunds will attempt cross-asset transfers and fail. + +### 5. Refund Window Configuration + +The `REFUND_WINDOW_LEDGERS` parameter controls how long after payment a refund +can be claimed. + +- [ ] Decide the production refund window: + - `17280` ledgers ≈ 24 hours (testnet default) + - `34560` ledgers ≈ 48 hours + - `0` disables the window entirely +- [ ] Document the chosen value and the rationale. +- [ ] Pass it via the environment variable during deployment: + + ```bash + REFUND_WINDOW_LEDGERS= NETWORK=pubnet TOKEN= ./deploy.sh + ``` + +### 6. Rent Funding and Monitoring + +Soroban persistent storage incurs rent. See the +[Storage Audit](storage-audit.md) for per-record costs and projections. + +- [ ] Fund the deployer account with sufficient XLM to cover: + - Base reserves for contract instances (2 XLM per contract) + - Transaction fees for deployment and initialization + - Initial storage rent for `BatchRecord` and `RefundRecord` entries +- [ ] Set up monitoring for the deployer account balance and storage rent. +- [ ] Decide who is responsible for funding rent extensions + (`extend_batch_ttl`, `extend_refund_ttl`) in production. +- [ ] Document the monitoring and rent-funding procedure. + +--- + +## Deployment Commands + +The following commands assume all pre-deployment checklist items above are +complete. + +### 1. Verify the deployment target + +```bash +# Confirm the USDC SAC address resolves on mainnet +stellar contract invoke \ + --id \ + --network pubnet --source \ + -- balance --id +``` + +### 2. Run deploy.sh -### 2. Configure Your Environment -Set up your Stellar CLI identity and network for Mainnet: ```bash -stellar network add mainnet \ - --rpc-url https://soroban-testnet.stellar.org \ - --network-passphrase "Public Global Stellar Network ; September 2015" +NETWORK=pubnet \ +TOKEN= \ +REFUND_WINDOW_LEDGERS= \ +IDENTITY= \ + ./deploy.sh +``` + +The script will: +1. Verify a clean git working tree and the `main` branch. +2. Build the WASM artifacts. +3. Display the WASM hashes and require explicit `YES` confirmation. +4. Deploy and initialize both contracts. +5. Write `deployments/pubnet.env` with contract IDs and provenance. +6. Verify the deployed contract metadata by reading it back. + +### 3. Record the deployment -stellar keys generate deployer --network mainnet -# (Make sure to fund the generated public key) +```bash +git add deployments/pubnet.env DEPLOYMENTS.md +git commit -m "docs: record pubnet deployment" ``` -### 3. Deploy and Initialize -Unlike testnet, where the `deploy.sh` script defaults to the native XLM token, Mainnet deployments should use the official USDC Stellar Asset Contract (SAC). -- **USDC SAC Address (Mainnet)**: `CEQ...` (replace with the actual Mainnet USDC SAC ID). +--- + +## Post-Deployment Verification + +After deployment, verify the contracts independently: + +### Read contract metadata -Run the deployment script with the Mainnet parameters: ```bash -NETWORK=mainnet TOKEN= ./deploy.sh +stellar contract invoke \ + --id \ + --network pubnet --source \ + -- get_version + +stellar contract invoke \ + --id \ + --network pubnet --source \ + -- get_version ``` -This will compile the contracts, deploy them, initialize them with your deployer identity as the admin, and output the contract IDs to `deployments/mainnet.env`. + +### Verify the vault token address + +Read the vault's stored token address and confirm it matches the intended USDC +SAC: + +```bash +stellar contract invoke \ + --id \ + --network pubnet --source \ + -- get_token +``` + +### Verify on Stellar Explorer + +Check both contracts on +[stellar.expert](https://stellar.expert/explorer/public/): + +- `https://stellar.expert/explorer/public/contract/` +- `https://stellar.expert/explorer/public/contract/` + +### Anchor and verify a test receipt + +After the indexer is running against pubnet, anchor a small batch and verify +a receipt against it to confirm the full end-to-end flow. + +--- ## Fee and Rent Analysis -### Transaction Fees (Testnet Proxies) -Soroban transaction fees are generally highly predictable. Based on testnet benchmarks, here are the measured fee projections for core operations: +### Transaction Fees + +Soroban transaction fees are highly predictable. Based on testnet benchmarks: | Operation | Estimated Fee (XLM) | Notes | |---|---|---| -| `anchor_batch` | ~0.02 - 0.05 XLM | Scales slightly with the number of persistent storage reads/writes. | -| `refund` | ~0.015 - 0.03 XLM | Involves cross-contract calls to the USDC SAC. | -| `verify_receipt` | 0 XLM | Read-only simulation. | +| `anchor_batch` | ~0.02 – 0.05 | Scales with persistent storage reads/writes | +| `refund` | ~0.015 – 0.03 | Includes cross-contract calls to the USDC SAC | +| `verify_receipt` | 0 | Read-only simulation | ### Rent Cost Projection -Stellar's state archiving mechanism requires paying "rent" to keep data in `Persistent` storage. -A single `BatchRecord` contains: -- `root`: 32 bytes -- `count`: 4 bytes -- `period_start`: 8 bytes -- `period_end`: 8 bytes -- Overhead: ~50 bytes +Soroban state archiving requires paying "rent" to keep data in `Persistent` +storage. A single `BatchRecord` occupies ~100 bytes. + +**Scenario:** 500-payment batches, 1 year retention, 10,000 payments/day: + +- 20 batches/day × 365 days = 7,300 batches +- Storage: 7,300 × 100 bytes ≈ 730 KB +- Rent: ~0.5 XLM/KB/year ≈ **365 XLM/year** +- Per-payment cost: negligible fraction of a cent + +This makes the on-chain verifiable receipt architecture highly economical at +any reasonable transaction volume. -**Total size per batch**: ~100 bytes. +--- -**Scenario**: 500-payment batches, 1 year retention. -- If you process 10,000 payments a day in batches of 500, that is **20 batches per day**. -- **Yearly volume**: 7,300 batches. -- **Storage required**: 7,300 * 100 bytes = ~730 KB. -- **Rent cost**: Persistent storage on Stellar costs roughly **0.5 XLM per KB per year**. -- **Total projected rent**: ~365 XLM per year for archiving the anchors of 3.65 million payments. +## Downstream Integrations + +Once pubnet contract IDs are known, the following downstream systems will +need their configuration updated: + +| System | Repository | What to update | +|---|---|---| +| Dashboard | [`accensa-app`](https://github.com/accensa/accensa-app) | Network config, contract ID references | +| Indexer | [`accensa-app`](https://github.com/accensa/accensa-app) | Contract IDs, token address, network passphrase | +| SDK | [`accensa-app`](https://github.com/accensa/accensa-app) | Network configuration, contract addresses | +| README / docs | This repository | `DEPLOYMENTS.md`, badges, explorer links | -This amortizes to a negligible fraction of a cent per payment, making the on-chain verifiable receipt architecture highly economical. +These updates should only be made after the actual deployment IDs are +available from `deployments/pubnet.env`. Do not pre-emptively change +references to values that do not yet exist. diff --git a/tests/test_deploy.sh b/tests/test_deploy.sh new file mode 100644 index 00000000..f33cc6ce --- /dev/null +++ b/tests/test_deploy.sh @@ -0,0 +1,325 @@ +#!/bin/bash +# Tests for deploy.sh argument parsing, pubnet safety checks, and helpers. +# +# Run with: bash tests/test_deploy.sh +# +# These tests mock `git` and `stellar` to exercise the script's validation +# logic without deploying to any network. +set -euo pipefail + +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 + +pass() { + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + echo " ✓ $1" +} + +fail() { + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + echo " ✗ $1" + [ -n "${2:-}" ] && echo " expected: $2" && echo " got: $3" +} + +assert_eq() { + local expected="$1" actual="$2" msg="$3" + if [ "$expected" = "$actual" ]; then + pass "$msg" + else + fail "$msg" "$expected" "$actual" + fi +} + +# Run a command; expect non-zero exit. +assert_exit_nonzero() { + local msg="$1"; shift + if "$@" >/dev/null 2>&1; then + fail "$msg" "non-zero exit" "exit 0" + else + pass "$msg" + fi +} + +# Run a command; expect zero exit. +assert_exit_zero() { + local msg="$1"; shift + if "$@" >/dev/null 2>&1; then + pass "$msg" + else + fail "$msg" "exit 0" "non-zero exit" + fi +} + +# --------------------------------------------------------------------------- +# Setup: create a temp dir, mock git and stellar, source deploy.sh functions. +# --------------------------------------------------------------------------- +setup() { + MOCK_TMPDIR=$(mktemp -d) + MOCK_GIT_DIR="$MOCK_TMPDIR/bin" + mkdir -p "$MOCK_GIT_DIR" + + # Default mock git: clean tree, main branch, valid HEAD + _write_clean_main_git + + # Mock stellar: does nothing, succeeds + cat > "$MOCK_GIT_DIR/stellar" <<'STELLARMOCK' +#!/bin/bash +exit 0 +STELLARMOCK + chmod +x "$MOCK_GIT_DIR/stellar" + + export PATH="$MOCK_GIT_DIR:$PATH" + + # Extract only the function definitions from deploy.sh so we can test them + # in isolation without triggering any top-level deployment commands. + SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" + SCRIPT="$SCRIPT_DIR/deploy.sh" + + eval "$(sed -n '/^sha256_of()/,/^# ----/p' "$SCRIPT" | head -n -1)" + eval "$(sed -n '/^parse_args()/,/^# ----/p' "$SCRIPT" | head -n -1)" + eval "$(sed -n '/^validate_pubnet()/,/^# ----/p' "$SCRIPT" | head -n -1)" + + # Reset globals + NETWORK="testnet" + IDENTITY="deployer" +} + +_write_clean_main_git() { + cat > "$MOCK_GIT_DIR/git" <<'GITMOCK' +#!/bin/bash +case "$1" in + diff) + if [ "${2:-}" = "--cached" ]; then + exit 0 + fi + exit 0 + ;; + rev-parse) + case "${2:-}" in + --abbrev-ref) echo "main" ;; + HEAD) echo "abc123def456789012345678901234567890abcd" ;; + *) exit 1 ;; + esac + ;; + *) exit 1 ;; +esac +GITMOCK + chmod +x "$MOCK_GIT_DIR/git" +} + +_write_dirty_git() { + cat > "$MOCK_GIT_DIR/git" <<'GITMOCK' +#!/bin/bash +case "$1" in + diff) + if [ "${2:-}" = "--cached" ]; then + exit 1 + fi + exit 1 + ;; + rev-parse) + case "${2:-}" in + --abbrev-ref) echo "main" ;; + HEAD) echo "abc123" ;; + *) exit 1 ;; + esac + ;; + *) exit 1 ;; +esac +GITMOCK + chmod +x "$MOCK_GIT_DIR/git" +} + +_write_wrong_branch_git() { + cat > "$MOCK_GIT_DIR/git" <<'GITMOCK' +#!/bin/bash +case "$1" in + diff) + if [ "${2:-}" = "--cached" ]; then + exit 0 + fi + exit 0 + ;; + rev-parse) + case "${2:-}" in + --abbrev-ref) echo "feature/my-branch" ;; + HEAD) echo "abc123" ;; + *) exit 1 ;; + esac + ;; + *) exit 1 ;; +esac +GITMOCK + chmod +x "$MOCK_GIT_DIR/git" +} + +_write_no_head_git() { + cat > "$MOCK_GIT_DIR/git" <<'GITMOCK' +#!/bin/bash +case "$1" in + diff) + if [ "${2:-}" = "--cached" ]; then + exit 0 + fi + exit 0 + ;; + rev-parse) + case "${2:-}" in + --abbrev-ref) echo "main" ;; + HEAD) exit 1 ;; + *) exit 1 ;; + esac + ;; + *) exit 1 ;; +esac +GITMOCK + chmod +x "$MOCK_GIT_DIR/git" +} + +cleanup() { + rm -rf "$MOCK_TMPDIR" +} + +trap cleanup EXIT + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- +echo "" +echo "===========================================================" +echo "deploy.sh test suite" +echo "===========================================================" +echo "" + +setup + +echo "--- parse_args: --network flag ---" + +NETWORK="testnet" +parse_args --network pubnet +assert_eq "pubnet" "$NETWORK" "--network pubnet sets NETWORK to pubnet" + +NETWORK="testnet" +parse_args --network testnet +assert_eq "testnet" "$NETWORK" "--network testnet sets NETWORK to testnet" + +NETWORK="testnet" +parse_args --network futurenet +assert_eq "futurenet" "$NETWORK" "--network futurenet sets NETWORK to futurenet" + +echo "" +echo "--- parse_args: default (no flag) ---" + +NETWORK="testnet" +parse_args +assert_eq "testnet" "$NETWORK" "no flag preserves default testnet" + +echo "" +echo "--- parse_args: error handling ---" + +NETWORK="testnet" +assert_exit_nonzero "unknown argument produces error" \ + bash -c 'NETWORK=testnet; parse_args --invalid' + +assert_exit_nonzero "--network without value produces error" \ + bash -c 'NETWORK=testnet; parse_args --network' + +echo "" +echo "--- validate_pubnet: happy path ---" + +setup +NETWORK="pubnet" +assert_exit_zero "clean tree + main branch passes validate_pubnet" \ + validate_pubnet + +echo "" +echo "--- validate_pubnet: dirty working tree ---" + +setup +_write_dirty_git +NETWORK="pubnet" +assert_exit_nonzero "dirty working tree rejected" \ + bash -c 'NETWORK=pubnet validate_pubnet' + +echo "" +echo "--- validate_pubnet: wrong branch ---" + +setup +_write_wrong_branch_git +NETWORK="pubnet" +assert_exit_nonzero "non-main branch rejected" \ + bash -c 'NETWORK=pubnet validate_pubnet' + +echo "" +echo "--- validate_pubnet: unknown commit SHA ---" + +setup +_write_no_head_git +NETWORK="pubnet" +assert_exit_nonzero "cannot determine commit SHA rejected" \ + bash -c 'NETWORK=pubnet validate_pubnet' + +echo "" +echo "--- validate_pubnet: not called for testnet ---" + +setup +_write_dirty_git +NETWORK="testnet" +# deploy.sh only calls validate_pubnet when NETWORK=pubnet. +# Demonstrate that validate_pubnet itself WOULD fail in this scenario: +assert_exit_nonzero "validate_pubnet would fail with dirty tree (deploy.sh skips it for testnet)" \ + bash -c 'NETWORK=testnet validate_pubnet' + +echo "" +echo "--- sha256_of: hash computation ---" + +setup + +TEST_FILE="$MOCK_TMPDIR/testfile.txt" +echo "hello world" > "$TEST_FILE" + +HASH=$(sha256_of "$TEST_FILE") +EXPECTED="a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447" +assert_eq "$EXPECTED" "$HASH" "sha256_of computes correct hash" + +HASH_ALT=$(PATH=/nonexistent sha256_of "$TEST_FILE") +assert_eq "unknown" "$HASH_ALT" "sha256_of returns 'unknown' when no sha256 tool is available" + +echo "" +echo "--- pubnet confirmation requires YES ---" + +setup + +CONFIRM_YES=$(echo "YES" | bash -c ' + read -r -p "Type YES to confirm: " confirm + if [ "$confirm" = "YES" ]; then echo "confirmed"; else echo "aborted"; exit 1; fi +') +assert_eq "confirmed" "$CONFIRM_YES" "confirmation accepts YES" + +CONFIRM_NO=$(echo "no" | bash -c ' + read -r -p "Type YES to confirm: " confirm + if [ "$confirm" = "YES" ]; then echo "confirmed"; else echo "aborted"; exit 1; fi +' 2>/dev/null || true) +assert_eq "aborted" "$CONFIRM_NO" "confirmation rejects lowercase input" + +CONFIRM_EMPTY=$(echo "" | bash -c ' + read -r -p "Type YES to confirm: " confirm + if [ "$confirm" = "YES" ]; then echo "confirmed"; else echo "aborted"; exit 1; fi +' 2>/dev/null || true) +assert_eq "aborted" "$CONFIRM_EMPTY" "confirmation rejects empty input" + +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- +echo "" +echo "===========================================================" +echo "Results: $TESTS_PASSED passed, $TESTS_FAILED failed, $TESTS_RUN total" +echo "===========================================================" + +if [ "$TESTS_FAILED" -gt 0 ]; then + exit 1 +fi +exit 0 From cd12c89c06aa80fb069d3a19577ca1912591d0e3 Mon Sep 17 00:00:00 2001 From: Edukpe David Date: Wed, 26 Aug 2026 18:52:42 +0100 Subject: [PATCH 3/4] docs: resolve upto upstream specification questions --- docs/ADR-002-upto-scheme.md | 137 +++++++-- docs/upto-upstream-notes.md | 534 ++++++++++++++++++++++++++++++++++++ 2 files changed, 646 insertions(+), 25 deletions(-) create mode 100644 docs/upto-upstream-notes.md diff --git a/docs/ADR-002-upto-scheme.md b/docs/ADR-002-upto-scheme.md index a7d22ba9..480154a5 100644 --- a/docs/ADR-002-upto-scheme.md +++ b/docs/ADR-002-upto-scheme.md @@ -1,9 +1,9 @@ # ADR 002: The `upto` Settlement Scheme on Stellar -> **Status: DRAFT — design exploration, not an accepted decision.** -> Nothing here has been validated against the upstream `upto` specification, against a -> running contract, or against Soroban's authorization semantics in practice. §6 lists -> what must be confirmed before any of this is proposed as a network spec. Do not cite +> **Status: DRAFT — §6.1 resolved; remaining open questions pending.** +> §6.1 answers the upstream-specification research question (issue #61). The remaining +> open questions (§6.2–6.6) require Soroban-specific implementation work, not upstream +> research. The design in §4 is **not excluded** by the upstream `upto` spec. Do not cite > this document as a design that works; cite it as the design being investigated. ## 1. Context @@ -155,30 +155,117 @@ ADR.** ## 6. Open questions — resolve before proposing this upstream -1. **Does the upstream `upto` spec permit a two-invocation construction at all**, or does - its wire format assume a single settlement call? The EVM and SVM specs must be read - before any Stellar spec is drafted; this ADR was written from the RFP's summary of - `upto`, not from the specs themselves. -2. **Can one signed auth entry cover both `authorize` and the nested `approve`?** The - construction in §4 assumes the buyer signs a single auth tree with `approve` as a - sub-invocation. This needs confirming against Soroban's authorization semantics — if - it requires two separate buyer signatures, the UX argument for this design weakens - considerably. -3. **What does `settle` cost**, and does the pair stay within per-transaction CPU, - memory, read, and write limits under realistic load? -4. **Sequence-number contention.** Agent traffic is bursty and the facilitator submits - every settlement. Channel accounts are the standard answer; that needs designing, not - naming. -5. **Refund interaction.** `RefundVault` in this repo keys refunds on a payment - reference. If an `upto` payment settles for less than its cap, what is the refundable - amount — and does anything need to change here? -6. **Does the facilitator need `authorize` at all**, or can the buyer call it directly? - Fee sponsorship (`extra.areFeesSponsored`) suggests the facilitator submits, but that - should follow from the spec rather than convenience. +### 6.1 ✅ Does the upstream `upto` spec permit a two-invocation construction? + +**Answer: Yes — the two-invocation construction is VIABLE.** + +Researched against `x402-foundation/x402` at commit +`b32b5640557ff793c3ecbfac6f933b0ad3b2170b` (2026-08-26). See +[`docs/upto-upstream-notes.md`](upto-upstream-notes.md) for the full research +notes, direct quotations, and per-question analysis. + +**The upstream specification does NOT require exactly one settlement call.** The core +spec explicitly permits multiple settles: + +> "`/settle` MAY be invoked more than once for a single payment (for example, the +> `escrow` flow settles a deposit before the resource executes and the final charge +> after). A scheme defining multiple settles MUST specify how the facilitator +> distinguishes them from payload content." — x402-specification-v2.md §7.2 + +**The SVM `upto` spec uses two settle calls** (the `escrow` payment flow): + +> "Settlement happens after the resource server executes the metered work and before +> it returns the response to the client. The overall order is +> `settle(deposit)` → resource execution → `settle(claim)` → serve." — +> scheme_upto_svm.md §5 + +**The five normative `upto` properties** (from scheme_upto.md) are: + +1. Single-use authorization — "Each authorization MUST be settled at most once." +2. Time-bound authorization — MUST have `validAfter` and `deadline`. +3. Recipient binding — MUST cryptographically bind the recipient address. +4. Maximum amount enforcement — settled amount MUST be `<=` authorized maximum. +5. Phase-dependent `amount` semantics — `PaymentRequirements.amount` is max at + verify, actual at settle. + +The Stellar two-invocation construction (§4: `authorize` → `settle`) maps directly to +the `escrow` flow: + +| Escrow step | Stellar equivalent | +|---|---| +| First `settle(deposit)` | `authorize()` — commits ceiling, recipient, creates binding | +| Resource execution | Metering | +| Second `settle(claim)` | `settle(actual)` — transfers actual amount, sets consumed | + +**The upstream spec architecture explicitly supports network-specific constructions.** +Both EVM and SVM use fundamentally different mechanisms (Permit2 vs. payment channels) +that both satisfy the same five properties. The `extra` field, per-network scheme +documents, and scheme templates all indicate the architecture expects variation. + +**Critical distinction — what is normative vs. implementation detail:** + +| Category | What it covers | Example | +|---|---|---| +| **Normative MUST** (protocol-level) | The five core `upto` properties | Single-use, time-bound, recipient binding, max enforcement, phase-dependent amount | +| **Implementation-specific** | How a network realizes those properties | Permit2 on EVM, payment channels on SVM, authorization-binding contract on Stellar | +| **Unstated** | Behavior the spec is silent on | Exact number of `/settle` calls, transaction structure, on-chain state model | + +**Remaining Stellar-specific design work required:** + +The two-invocation construction is not excluded, but is not automatically valid either. +The following Stellar-specific work is still needed: + +1. **Auth entry expiration.** Soroban `signatureExpirationLedger` is short (~12 + ledgers, ~60s). If `authorize` and `settle` are in separate transactions, the auth + entry must survive until settlement. If both are in one transaction (as §4 + proposes), this is not a problem. +2. **Protocol flow declaration.** The Stellar `upto` spec should declare + `extra.paymentFlow: "escrow"` to match the SVM precedent. +3. **Deposit vs. claim distinction.** The facilitator must distinguish the two settle + calls. On SVM this is done from payload content (voucher present → claim; no voucher + → deposit). Stellar needs an equivalent. +4. **State lifecycle.** Authorization record TTL, cleanup, and rent payment must be + designed. +5. **Cost analysis.** Two Soroban invocations per metered payment must be priced + against the RFP's per-transaction overhead constraints. + +### 6.2 Can one signed auth entry cover both invocations? + +**Open.** The construction in §4 assumes the buyer signs a single auth tree with +`approve` as a sub-invocation. This needs confirming against Soroban's authorization +semantics — if it requires two separate buyer signatures, the UX weakens considerably. +This is a Soroban-specific question, not an upstream-spec question. + +### 6.3 What does `settle` cost? + +**Open.** Does the pair stay within per-transaction CPU, memory, read, and write +limits under realistic load? Requires implementation and benchmarking. + +### 6.4 Sequence-number contention + +**Open.** Agent traffic is bursty and the facilitator submits every settlement. +Channel accounts are the standard answer; that needs designing, not naming. + +### 6.5 Refund interaction + +**Open.** `RefundVault` keys refunds on a payment reference. If an `upto` payment +settles for less than its cap, what is the refundable amount — and does anything need +to change here? + +### 6.6 Does the facilitator need `authorize` at all? + +**Open.** Fee sponsorship (`extra.areFeesSponsored`) suggests the facilitator submits, +but the buyer calling `authorize` directly may be viable. Should follow from the +spec design rather than convenience. ## References - `rfp.md` §3.4 (settlement schemes), §3.5 (Stellar-specific considerations), §3.6 (audit scope) - `docs/ADR-001-merkle-structure.md` — prior ADR format - `docs/SECURITY_MODEL.md`, `docs/storage-audit.md` — existing TTL and storage analysis -- Upstream: `x402-foundation/x402`, `specs/schemes/` — **not yet read; see §6.1** +- Upstream: `x402-foundation/x402` @ `b32b5640557ff793c3ecbfac6f933b0ad3b2170b` + - `specs/schemes/upto/scheme_upto.md` — chain-agnostic upto spec + - `specs/schemes/upto/scheme_upto_evm.md` — EVM upto implementation spec + - `specs/schemes/upto/scheme_upto_svm.md` — SVM/Solana upto implementation spec + - `specs/x402-specification-v2.md` — core x402 v2 protocol +- [`docs/upto-upstream-notes.md`](upto-upstream-notes.md) — full research notes for §6.1 diff --git a/docs/upto-upstream-notes.md b/docs/upto-upstream-notes.md new file mode 100644 index 00000000..a7c404e6 --- /dev/null +++ b/docs/upto-upstream-notes.md @@ -0,0 +1,534 @@ +# Upstream `upto` Specification Research Notes + +> **Purpose:** Detailed research notes from reading the upstream x402 `upto` +> specifications. These notes support ADR-002 §6.1 and answer the five research +> questions listed there. This document is a reference; the ADR contains the +> concise conclusion. + +## Source Record + +- **Upstream repository:** `x402-foundation/x402` +- **Exact commit SHA:** `b32b5640557ff793c3ecbfac6f933b0ad3b2170b` +- **Date of research:** 2026-08-26 +- **Files/specifications inspected:** + - `specs/schemes/upto/scheme_upto.md` — chain-agnostic upto spec + - `specs/schemes/upto/scheme_upto_evm.md` — EVM upto implementation spec + - `specs/schemes/upto/scheme_upto_svm.md` — SVM/Solana upto implementation spec + - `specs/x402-specification-v2.md` — core x402 v2 protocol specification + - `specs/schemes/exact/scheme_exact_stellar.md` — existing Stellar exact spec (for reference patterns) + - `specs/schemes/exact/scheme_exact.md` — exact scheme chain-agnostic spec (for flow reference) + - `specs/schemes/auth-capture/scheme_auth_capture.md` — auth-capture scheme (for lifecycle reference) + - `specs/scheme_template.md` — scheme template + - `specs/scheme_impl_template.md` — scheme implementation template + - `specs/README.md` — specs overview + +**Note on the SVM specification:** The upstream repository at this commit contains a +finalized `scheme_upto_svm.md`. It is NOT a draft or RFC — it is a complete +implementation-specific specification at the same maturity level as `scheme_upto_evm.md`. + +--- + +## Research Question 1: Wire Format + +### What the upto flow carries + +#### PaymentPayload structure (core spec §5.2) + +The `PaymentPayload` is the outer envelope. From the core x402 spec: + +```json +{ + "x402Version": 2, + "resource": { /* ResourceInfo */ }, + "accepted": { /* PaymentRequirements */ }, + "payload": { /* scheme-specific */ }, + "extensions": {} +} +``` + +The `accepted` field is a `PaymentRequirements` object. The `payload` field is +scheme-specific. + +#### EVM upto PaymentPayload payload + +From `scheme_upto_evm.md`, the `payload` field contains: + +| Field | Description | +|---|---| +| `signature` | EIP-712 signature for `permitWitnessTransferFrom` | +| `permit2Authorization.permitted.token` | ERC-20 token address | +| `permit2Authorization.permitted.amount` | **Maximum** authorized amount (the ceiling) | +| `permit2Authorization.from` | Payer wallet address | +| `permit2Authorization.spender` | Permit2 proxy contract address | +| `permit2Authorization.nonce` | Unique nonce for replay protection | +| `permit2Authorization.deadline` | Expiry timestamp | +| `permit2Authorization.witness.to` | **Recipient** (bound at sign time) | +| `permit2Authorization.witness.facilitator` | Bound facilitator address | +| `permit2Authorization.witness.validAfter` | Start time | + +#### SVM upto PaymentPayload payload (`UptoPayload`) + +From `scheme_upto_svm.md`, the `payload` field contains: + +| Field | Description | +|---|---| +| `from` | Payer wallet | +| `maxAmount` | Signed ceiling (must equal verification-phase `amount`) | +| `expiresAt` | Deadline (Unix seconds), signed into server voucher | +| `validAfter` | Activation time (Unix seconds) | +| `nonce` | Unique salt for channel PDA derivation | +| `openSlot` | Slot for channel PDA seed | +| `channelId` | Channel PDA (derived before signing) | +| `deposit` | On-chain escrow amount (must equal `maxAmount`) | +| `authorizedSigner` | Must equal `extra.receiverAuthorizer` | +| `openTransaction` | Base64 partially-signed `open` transaction | +| `voucherSignature` | *(settlement-time only)* Server's Ed25519 voucher | + +**Critical:** The `voucherSignature` is NOT part of the client's `PAYMENT-SIGNATURE` +payload. The client signs only `open`. After metering, the server signs an Ed25519 +voucher and attaches it to the settle-time `paymentPayload.payload`. From the SVM spec: + +> "The voucher is not carried in the client's `PAYMENT-SIGNATURE` payload — the +> client signs only `open`. After metering, the server signs an Ed25519 voucher +> with `receiverAuthorizer` and transmits it to the facilitator in the settlement +> request (`payload.voucherSignature`)." + +#### PaymentRequirements fields + +From the chain-agnostic `scheme_upto.md`: + +| Field | Type | Description | +|---|---|---| +| `scheme` | string | `"upto"` | +| `network` | string | CAIP-2 network identifier | +| `amount` | string | **Phase-dependent:** maximum at verify, actual at settle | +| `asset` | string | Token address | +| `payTo` | string | Recipient address | +| `maxTimeoutSeconds` | number | Completion window | +| `extra` | object | Scheme/network-specific additional info | + +#### Phase-dependent amount semantics (MUST-level requirement) + +From `scheme_upto.md` §Phase-Dependent `amount` Semantics: + +> "At **verification** time, `amount` represents the **maximum** amount the client +> authorizes. At **settlement** time, `amount` represents the **actual amount to +> settle**, which MUST be less than or equal to the previously authorized maximum." +> +> "The actual settled amount is communicated by the resource server to the facilitator +> via the `amount` field in the settlement-time `PaymentRequirements`." + +From the core spec §7.2: + +> "While the request structure is identical, some payment schemes may assign different +> semantics to fields at settlement time versus verification time. For example, in the +> `upto` scheme, the `amount` field in `paymentRequirements` represents the maximum +> authorized amount at verification time, but the actual amount to settle at settlement +> time." + +#### What the facilitator receives during verify + +The facilitator receives the `PaymentPayload` (containing the client's signed +authorization with the ceiling) and `PaymentRequirements` (where `amount` is the +maximum). From the EVM spec §Phase 3: + +1. Verify signature is valid and recovers to `permit2Authorization.from`. +2. Verify client has Permit2 approval. +3. Verify client has sufficient balance for `amount`. +4. **Verify `permit2Authorization.permitted.amount` equals `requirements.amount`.** + *(This equality check applies at verification time only, where both carry the + ceiling.)* +5. Verify deadline and validAfter. +6. Verify token and network match. +7. Simulate settlement with full `amount` (worst case). + +#### What the facilitator receives during settle + +The facilitator receives the same `PaymentPayload` structure, but `PaymentRequirements +.amount` now carries the **actual settlement amount** (set by the resource server). + +From the EVM spec §Settle-Time Verification: + +> "Before executing an on-chain settlement, the facilitator MUST re-verify the client's +> signature. Because the `upto` scheme uses phase-dependent `amount` semantics, the +> `/settle` request will carry `paymentRequirements.amount` set to the **actual settlement +> amount**... which may be less than `paymentPayload.payload.permit2Authorization +> .permitted.amount`." + +Settlement steps: + +1. **Verify the signature against `permitted.amount`** — NOT against + `paymentRequirements.amount`. +2. **Validate `paymentRequirements.amount <= permit2Authorization.permitted.amount`.** +3. **Execute the on-chain transfer for `paymentRequirements.amount`.** + +From the EVM spec: + +> "**Conformance note**: A facilitator that enforces +> `paymentRequirements.amount === permit2Authorization.permitted.amount` at settle time +> will reject all partial settlements, breaking the core `upto` value proposition." + +#### How the actual amount is represented + +The actual amount is NOT a separate payload field. It is the `PaymentRequirements.amount` +field at settlement time, supplied by the **resource server**. From `scheme_upto.md`: + +> "The actual settled amount is communicated by the resource server to the facilitator +> via the `amount` field in the settlement-time `PaymentRequirements`. This allows the +> resource server to determine the final charge based on actual resource consumption +> (e.g., tokens generated, bytes transferred) and communicate it to the facilitator +> without requiring additional fields or a separate settlement type." + +On EVM, the facilitator then calls `x402Permit2Proxy.settle` with this actual amount. +On SVM, the server signs a voucher for the actual amount and the facilitator builds +`settle_and_seal` + `distribute`. + +#### SettlementResponse + +From `scheme_upto_evm.md` §3: + +| Field | Type | Required | Description | +|---|---|---|---| +| `success` | boolean | yes | Whether settlement succeeded | +| `errorReason` | string | no | Error if failed | +| `payer` | string | no | Payer wallet address | +| `transaction` | string | yes | Blockchain tx hash (empty string if $0) | +| `network` | string | yes | CAIP-2 network | +| `amount` | string | yes | **Actual** amount charged (may be 0) | + +--- + +## Research Question 2: Invocation Count + +### Whether EVM settlement is one on-chain call + +**Yes.** On EVM, settlement is a single on-chain call: +`x402Permit2Proxy.settle(permit, actualAmount, owner, witness, signature)`. +The Permit2 `permitWitnessTransferFrom` does everything in one call: validates the +signature, checks the nonce, and transfers tokens. + +### Whether SVM settlement is one or multiple on-chain transactions + +**Multiple instructions in a settlement sequence.** From `scheme_upto_svm.md`: + +> "Settlement happens after the resource server executes the metered work and before +> it returns the response to the client. The overall order is +> `settle(deposit)` → resource execution → `settle(claim)` → serve." + +The SVM spec uses the **`escrow` payment flow** (x402 v2 §6.1): + +| Flow | Ordering | +|---|---| +| `escrow` | settle → resource → settle → respond | + +The settlement-side instructions are: `settle_and_seal` (optionally with Ed25519 +voucher) then `distribute`. These are typically bundled in one Solana transaction, but +the protocol-level flow involves **two `/settle` HTTP calls** (deposit and claim). + +### Whether the spec REQUIRES one on-chain invocation + +**No.** The upstream specification does NOT require exactly one on-chain invocation or +one `/settle` call. From the core spec §7.2: + +> "`/settle` MAY be invoked more than once for a single payment (for example, the +> `escrow` flow settles a deposit before the resource executes and the final charge +> after). A scheme defining multiple settles MUST specify how the facilitator +> distinguishes them from payload content." + +### The distinction: protocol-level vs. implementation-specific + +**Protocol-level MUST requirements** (from `scheme_upto.md`): + +1. **Single-Use Authorization:** "Each authorization MUST be settled at most once." +2. **Time-Bound Authorization:** MUST have `validAfter` and `deadline`. +3. **Recipient Binding:** MUST cryptographically bind the recipient address. +4. **Maximum Amount Enforcement:** Settled amount MUST be `<=` authorized maximum. +5. **Phase-dependent `amount` semantics.** + +**NOT a protocol-level requirement:** + +- Exactly one `/settle` HTTP call. +- Exactly one on-chain transaction. +- Any particular transaction structure. + +The "single-use" requirement constrains the **authorization** (it can be settled at +most once), not the number of HTTP settle calls or on-chain instructions used to +achieve that settlement. + +### Evaluation of Stellar two-invocation construction + +The proposed Stellar construction in ADR-002 §4 uses: + +``` +authorize(payment_id, from, to, cap, expiry) → settle(payment_id, actual) +``` + +This maps directly to the `escrow` flow: + +| Escrow step | Stellar equivalent | +|---|---| +| First `settle(deposit)` | `authorize()` — commits ceiling, recipient, and creates on-chain binding | +| Resource execution | Metering happens | +| Second `settle(claim)` | `settle(actual)` — transfers actual amount, sets consumed flag | + +**The two-invocation construction is VIABLE.** It is structurally analogous to the SVM +`upto` escrow flow. The upstream spec explicitly permits multiple settle calls (core +spec §7.2) and the SVM `upto` spec explicitly uses the `escrow` flow with two settle +calls. + +**What still needs Stellar-specific design work:** + +1. **Auth entry expiration:** Soroban `signatureExpirationLedger` is short (~12 + ledgers, ~60 seconds). The `authorize` call's auth entry must cover the time needed + for metering + settle. If metering takes longer than the auth entry lifetime, a + different auth mechanism is needed. +2. **Single-transaction vs. two-transaction:** If both invocations happen in one + transaction (as ADR-002 §4 suggests), the auth entry expiration is not a problem. + If they are separate transactions, the auth entry must survive until settlement. +3. **Protocol flow naming:** The Stellar spec should declare `extra.paymentFlow: + "escrow"` to match the SVM precedent, rather than defaulting to `authorization`. +4. **Distinguishing deposit vs. claim settles:** The protocol requires the facilitator + to distinguish the two settles. On SVM this is done from payload content (voucher + present → claim; no voucher → deposit). Stellar needs an equivalent mechanism. +5. **Authorization record state management:** The `authorize` call creates on-chain + state that `settle` later reads. The lifecycle, TTL, and cleanup of this state must + be designed. + +--- + +## Research Question 3: CAP + +### Maximum authorized amount representation + +- **EVM:** `permit2Authorization.permitted.amount` in the client's signed payload. + The Permit2 contract enforces this on-chain. +- **SVM:** `deposit` escrowed on-chain in the payment channel. The verifier pins + `deposit == maxAmount`. From the SVM spec: "Onchain `deposit` is the ceiling and + vouchers must satisfy `settled < cumulative_amount <= deposit`; the verifier pins + `deposit == maxAmount` so the x402 ceiling is exact, not advisory." +- **Stellar (proposed):** Would be the `cap` argument to `authorize()`, stored in the + authorization record on-chain. + +### What value the client signs + +- **EVM:** The client signs `permit2Authorization` which includes `permitted.amount` + (the ceiling). The signature commits to this value. +- **SVM:** The client signs the `open` transaction, which commits to `deposit` (the + ceiling) via the `open` instruction. The `open` instruction MUST encode + `deposit == payload.maxAmount`. +- **Stellar (proposed):** The client signs an auth entry that commits to the contract + invocation arguments, which would include `cap`. + +### How settlement is constrained to ≤ maximum + +From the chain-agnostic spec: + +> "The settled `amount` MUST be `<=` the authorized maximum" + +- **EVM facilitator check:** `paymentRequirements.amount <= + permit2Authorization.permitted.amount` (checked at settle time). +- **SVM program check:** `settled < cumulative_amount <= deposit` enforced on-chain. + Plus facilitator off-chain check: `paymentRequirements.amount <= maxAmount`. +- **Stellar (proposed):** The `settle` call would assert `actual <= cap` by reading + the authorization record. + +### Recipient binding + +From `scheme_upto.md`: + +> "The authorization MUST cryptographically bind the recipient address. The +> server/facilitator cannot redirect funds to a different address than what the client +> signed." + +- **EVM:** `permit2Authorization.witness.to` is bound in the EIP-712 signature. The + `x402Permit2Proxy` enforces that the transfer goes to `witness.to`. +- **SVM:** The distribution `[{ recipient: payTo, bps: 10000 }]` is committed at + `open` via `distribution_hash`. The program re-checks `distribution_hash` at + `distribute`. From the SVM spec: "The distribution fixed at `open` sends settled + funds to `payTo`, and the program re-checks `distribution_hash` at `distribute`." +- **Stellar (proposed):** The `to` address is recorded at authorization time from the + client's signed auth entry and is NOT an argument to `settle`. The facilitator cannot + redirect because it never supplies the destination. + +### How the facilitator is prevented from redirecting funds + +- **EVM:** The Permit2 witness pattern binds `witness.to`. The `x402Permit2Proxy` + enforces recipient correctness on-chain. +- **SVM:** The channel distribution is fixed at `open`. The program re-checks at + `distribute`. The facilitator (as zero-share `payee`) has no claim on settled funds. + From the SVM spec: "The facilitator can close a channel at its current settled + watermark; it cannot redirect funds or settle any nonzero amount on its own." +- **Stellar (proposed):** The contract stores `to` at authorize time and enforces it + at settle time. The facilitator is a spender, not a holder (ADR-002 §4: "No custody + — the contract is a spender, never a holder"). + +--- + +## Research Question 4: Timing + +### validAfter semantics + +From `scheme_upto.md`: + +> "**Start time** (`validAfter`): Authorization is not valid before this timestamp" + +- **EVM:** `permit2Authorization.witness.validAfter` — checked at verify time + (EVM spec §Phase 3, step 5: "Verify the `deadline` (not expired) and + `witness.validAfter` (active).") +- **SVM:** `validAfter` is in `extra` of `PaymentRequirements` and also in the + `UptoPayload`. From the SVM spec: "`validAfter` is offchain verify-time policy. + Neither value is client-bound; the client signs only `open`." +- **Stellar (proposed):** Would be a field in the authorization record, checked at + both `authorize` and `settle` time. + +### deadline/expiry semantics + +From `scheme_upto.md`: + +> "**End time** (`deadline`): Authorization expires after this timestamp" + +- **EVM:** `permit2Authorization.deadline` — enforced by Permit2 on-chain. From the + EVM spec: "Verify the `deadline` (not expired)." +- **SVM:** `expiresAt` — signed by `receiverAuthorizer` into the voucher and enforced + by the payment-channels program (`now < expiresAt`). From the SVM spec: + "Although the program supports `expires_at == 0` as no expiry, SVM `upto` MUST + reject `expiresAt == 0`." +- **Stellar (proposed):** Two expiry concepts (ADR-002 §4): + 1. `signatureExpirationLedger` on the auth entry (~12 ledgers, ~60s). + 2. The authorization record's own `expiry` (longer, bounds metering window). + +### How long an authorization remains usable + +- **EVM:** From creation until `deadline` (Unix timestamp) or nonce consumption, + whichever comes first. +- **SVM:** From `validAfter` until `expiresAt`, bounded by + `maxChannelLifetimeSecs`. The SVM spec adds: "Facilitators MAY reject + verify/deposit above `maxChannelLifetimeSecs`." +- **Stellar (proposed):** From `authorize` until the authorization record's `expiry`. + The `signatureExpirationLedger` is a separate, shorter bound on the signed auth + entry. + +### What happens between authorization and settlement + +- **EVM:** Nothing on-chain happens between the verify and settle HTTP calls. The + Permit2 approval is a one-time setup; the actual `permitWitnessTransferFrom` call + happens at settle time. +- **SVM:** Between deposit settle (`open`) and claim settle (`settle_and_seal` + + `distribute`), the resource executes. The channel is `Open` on-chain. The server + meters usage and signs a voucher. The client can call `request_close` as an escape + hatch if the server never settles. +- **Stellar (proposed):** Between `authorize` and `settle`, the authorization record + exists on-chain. Metering happens. The facilitator later calls `settle` with the + actual amount. + +### Whether settlement must happen within the same transaction + +- **EVM:** Not required by the spec. The verify and settle HTTP calls are separate. + The on-chain Permit2 call happens at settle time. +- **SVM:** Not required. The deposit and claim settle are separate HTTP calls and + separate on-chain transactions. +- **Stellar (proposed):** Can be either. ADR-002 §4 shows both in one transaction, + but two separate transactions are also viable (subject to auth entry expiration). + +### What happens if the authorization expires before settlement + +From `scheme_upto.md`: + +> "Each authorization MUST have explicit validity time constraints" + +- **EVM:** Permit2 rejects the transfer if `deadline` has passed. The settle fails. + The authorization expires unused. From the EVM spec: "If the settled `amount = 0`, + no on-chain transaction is required. The authorization simply expires unused." +- **SVM:** The program rejects the voucher if `expiresAt` has passed. The facilitator + cannot seal. The client can use `request_close` to recover the deposit. + From the SVM spec: "If the server does not settle, the payer can start forced close + with `request_close`, wait the grace period, then recover unspent deposit." +- **Stellar (proposed):** ADR-002 §4: "If it lapses, the correct behaviour is that + `settle` fails and the allowance is reclaimable by the buyer." + +--- + +## Research Question 5: Extension Points + +### Whether the upstream spec permits network-specific constructions + +**Yes, explicitly.** The entire specification architecture is designed for this: + +1. **Network-specific scheme specifications exist.** The chain-agnostic `scheme_upto.md` + ends with: "Network-specific rules and implementation details are defined in the + per-network scheme documents: EVM chains: See `scheme_upto_evm.md`." The same + pattern exists for `exact`, `batch-settlement`, and `auth-capture`. + +2. **The `extra` field is explicitly network-specific.** From the chain-agnostic spec: + "scheme extensions; `extra`" — the `extra` field in `PaymentRequirements` is + designed for network-specific and scheme-specific information. + +3. **The specs template creates network-specific docs.** `scheme_impl_template.md` + provides a template for network-specific implementation specs, indicating the + architecture expects them. + +4. **The core spec defines extension points.** From `x402-specification-v2.md` §6.1: + "extra.scheme-specific additional information" — the `extra` field is reserved for + this purpose. + +5. **Different networks use fundamentally different constructions.** EVM uses Permit2 + with `permitWitnessTransferFrom`. SVM uses payment channels with escrow, vouchers, + and `settle_and_seal` + `distribute`. These are structurally very different but + both conform to the five core `upto` requirements. + +### Whether a Stellar implementation may differ structurally + +**Yes.** The upstream spec's architecture explicitly supports this. The five core +properties from `scheme_upto.md` are: + +1. Single-use authorization +2. Time-bound authorization +3. Recipient binding +4. Maximum amount enforcement +5. Phase-dependent `amount` semantics + +A Stellar implementation must enforce these five properties using Stellar-native +mechanisms (Soroban auth entries, SEP-41 token transfers, on-chain authorization +records). The structural approach (two invocations, authorization-binding contract, +escrow flow) is a valid implementation choice, provided the five properties hold. + +The SVM `upto` spec demonstrates that a fundamentally different construction (payment +channels, vouchers, escrow flow) can satisfy the same chain-agnostic requirements. +The Stellar two-invocation construction is another such variation. + +### Requirements for equivalent security properties + +From `scheme_upto.md`: + +> "Other networks MUST implement equivalent replay protection." +> "Other networks MUST implement equivalent time bounds." +> "Other networks MUST implement equivalent recipient binding." + +These are MUST-level requirements on the **properties**, not on the **mechanism**. +A Stellar implementation must provide equivalent security properties using +Stellar-native primitives, which is exactly what ADR-002 §4 proposes. + +--- + +## Summary of Key Findings + +1. **The two-invocation Stellar construction is VIABLE.** It is structurally + analogous to the SVM `upto` escrow flow and is explicitly permitted by the + upstream protocol spec (core spec §7.2). + +2. **The protocol does NOT require one settlement call.** It explicitly permits + multiple settles (core spec §7.2) and the SVM implementation uses two. + +3. **The five core `upto` properties** (single-use, time-bound, recipient binding, + max enforcement, phase-dependent amount) are the normative requirements. The rest + is implementation-specific. + +4. **The upstream spec architecture explicitly supports network-specific constructions** + through per-network scheme documents, the `extra` field, and the scheme template. + +5. **The actual settlement amount** is communicated via `PaymentRequirements.amount` + at settlement time, supplied by the resource server. It is NOT a separate payload + field. + +6. **The upstream specs are complete for EVM and SVM.** Both are finalized + implementation specifications, not drafts. From 78ad4da38bbb1438c98396887355f858a89013e8 Mon Sep 17 00:00:00 2001 From: Edukpe David Date: Thu, 27 Aug 2026 00:34:45 +0100 Subject: [PATCH 4/4] test: spike nested approve authorization for upto --- Cargo.lock | 7 + Cargo.toml | 1 + docs/ADR-002-upto-scheme.md | 53 +- docs/upto-nested-approve-spike.md | 185 ++++++ spikes/upto-nested-approve/Cargo.toml | 18 + spikes/upto-nested-approve/src/lib.rs | 132 ++++ spikes/upto-nested-approve/src/test.rs | 460 +++++++++++++ ...10_mock_wrong_approve_amount_panics.1.json | 353 ++++++++++ ...1_mock_wrong_approve_spender_panics.1.json | 353 ++++++++++ ..._recording_auth_tree_nested_approve.1.json | 526 +++++++++++++++ .../test_20_budget_nested_construction.1.json | 562 ++++++++++++++++ ...test_21_budget_separate_invocations.1.json | 574 ++++++++++++++++ ...ing_full_flow_authorize_then_settle.1.json | 629 ++++++++++++++++++ ...ecording_auth_tree_structure_detail.1.json | 526 +++++++++++++++ ..._4_recording_double_settle_rejected.1.json | 626 +++++++++++++++++ ...rding_settle_exceeding_cap_rejected.1.json | 526 +++++++++++++++ ...g_settle_without_authorize_rejected.1.json | 329 +++++++++ .../test_7_recording_zero_cap_rejected.1.json | 329 +++++++++ ...t_8_recording_state_after_authorize.1.json | 527 +++++++++++++++ ..._mock_missing_sub_invocation_panics.1.json | 353 ++++++++++ 20 files changed, 7059 insertions(+), 10 deletions(-) create mode 100644 docs/upto-nested-approve-spike.md create mode 100644 spikes/upto-nested-approve/Cargo.toml create mode 100644 spikes/upto-nested-approve/src/lib.rs create mode 100644 spikes/upto-nested-approve/src/test.rs create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_10_mock_wrong_approve_amount_panics.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_11_mock_wrong_approve_spender_panics.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_1_recording_auth_tree_nested_approve.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_20_budget_nested_construction.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_21_budget_separate_invocations.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_2_recording_full_flow_authorize_then_settle.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_3_recording_auth_tree_structure_detail.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_4_recording_double_settle_rejected.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_5_recording_settle_exceeding_cap_rejected.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_6_recording_settle_without_authorize_rejected.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_7_recording_zero_cap_rejected.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_8_recording_state_after_authorize.1.json create mode 100644 spikes/upto-nested-approve/test_snapshots/test/test_9_mock_missing_sub_invocation_panics.1.json diff --git a/Cargo.lock b/Cargo.lock index 0f18a78c..c3285248 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1720,6 +1720,13 @@ dependencies = [ "wasmparser-nostd", ] +[[package]] +name = "spike-upto-nested-approve" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "spin" version = "0.9.8" diff --git a/Cargo.toml b/Cargo.toml index 62da32e9..40cda806 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "contracts/receipt-anchor", "contracts/refund-vault", + "spikes/upto-nested-approve", ] resolver = "2" diff --git a/docs/ADR-002-upto-scheme.md b/docs/ADR-002-upto-scheme.md index 480154a5..d1c90dc8 100644 --- a/docs/ADR-002-upto-scheme.md +++ b/docs/ADR-002-upto-scheme.md @@ -1,10 +1,12 @@ # ADR 002: The `upto` Settlement Scheme on Stellar -> **Status: DRAFT — §6.1 resolved; remaining open questions pending.** -> §6.1 answers the upstream-specification research question (issue #61). The remaining -> open questions (§6.2–6.6) require Soroban-specific implementation work, not upstream -> research. The design in §4 is **not excluded** by the upstream `upto` spec. Do not cite -> this document as a design that works; cite it as the design being investigated. +> **Status: DRAFT — §6.1 and §6.2 resolved; remaining open questions pending.** +> §6.1 answers the upstream-specification research question (issue #61). +> §6.2 answers whether one auth entry covers a nested approve (issue #62). +> The remaining open questions (§6.3–6.6) require Soroban-specific +> implementation work. The design in §4 is **not excluded** by the upstream +> `upto` spec. Do not cite this document as a design that works; cite it as +> the design being investigated. ## 1. Context @@ -229,12 +231,43 @@ The following Stellar-specific work is still needed: 5. **Cost analysis.** Two Soroban invocations per metered payment must be priced against the RFP's per-transaction overhead constraints. -### 6.2 Can one signed auth entry cover both invocations? +### 6.2 ✅ Can one signed auth entry cover both invocations? -**Open.** The construction in §4 assumes the buyer signs a single auth tree with -`approve` as a sub-invocation. This needs confirming against Soroban's authorization -semantics — if it requires two separate buyer signatures, the UX weakens considerably. -This is a Soroban-specific question, not an upstream-spec question. +**Answer: Yes — one auth entry covers the full tree, including the nested approve.** + +Researched via isolated spike: `spikes/upto-nested-approve/`. See +[`docs/upto-nested-approve-spike.md`](upto-nested-approve-spike.md) for full +experiment details, auth tree dumps, negative controls, and budget measurements. + +**The recorded auth tree proves the buyer's single signature commits to both +the parent `authorize` call and the nested `token.approve` sub-invocation.** + +Recorded structure (simplified): + +``` +Payer auth entry: + Root: authorize(payment_id, from, to, cap, expiry) ← UptoAuthorization + Sub[0]: approve(from, spender=contract, cap, expiry) ← SEP-41 token +``` + +**The sub-invocation's arguments are fully committed by the signature.** +Enforcing-mode negative controls confirm that mismatched trees are rejected: + +| Attack | Result | +|---|---| +| Auth tree omits the approve sub-invocation | Rejected (`InvalidAction`) | +| Approve amount does not match actual call | Rejected (`InvalidAction`) | +| Approve spender does not match actual call | Rejected (`InvalidAction`) | + +**The only implementation requirement** is that `from.require_auth()` must be +called **before** the nested `token_client.approve()` in the `authorize` +function. Without this, Soroban refuses the nested approve because no auth +entry exists for `from` at that point in the invocation. + +**Budget: nested construction has no cost penalty.** CPU: 209K (nested) vs. +214K (separate invocations). Memory: identical. + +**Recommendation: proceed with the nested auth construction in §4.** ### 6.3 What does `settle` cost? diff --git a/docs/upto-nested-approve-spike.md b/docs/upto-nested-approve-spike.md new file mode 100644 index 00000000..14e785de --- /dev/null +++ b/docs/upto-nested-approve-spike.md @@ -0,0 +1,185 @@ +# Spike: Can One Soroban Auth Entry Cover a Nested Approve? + +> **Status: RESOLVED — Yes, with sub-invocation in the auth tree.** +> Research for ADR-002 §6.2. Issue #62. + +## 1. Question + +In the ADR-002 §4 `upto` construction, the buyer signs a single auth entry that +covers both the parent `authorize()` call and its nested `token.approve()` +sub-invocation. Does Soroban actually allow this, or does it require two +separate signatures? + +## 2. Environment + +| Component | Version / Detail | +|---|---| +| `soroban-sdk` | 27.0.4 | +| `soroban-env-host` | 27.0.1 | +| Rust | 1.97.1 stable | +| Platform | Windows x86_64-pc-windows-gnu | +| Test mode | `mock_all_auths_allowing_non_root_auth()` (recording) + `mock_auths()` (enforcing) | + +### Test infrastructure notes + +Soroban's test utilities offer three auth simulation modes: + +| Mode | API | Behavior | +|---|---|---| +| **Recording** | `env.mock_all_auths()` | All `require_auth()` calls succeed and are recorded. `env.auths()` returns the captured tree. | +| **Recording (non-root)** | `env.mock_all_auths_allowing_non_root_auth()` | Same as recording, but also captures auth for addresses that are not the root transaction sender. Required when a nested call authorizes on behalf of a third party. | +| **Enforcing** | `env.mock_auths(&[...])` | Registers a mock `__check_auth` contract at each address. Auth entries must match exactly — mismatches are rejected with `InvalidAction`. | + +The spike uses recording mode to capture auth tree structures and enforcing +mode to prove negative controls (mismatched trees are rejected). + +## 3. Spike contract + +Minimal contract with two functions: + +```rust +pub fn authorize(env, payment_id, from, to, cap, expiry) -> Result<(), SpikeError> { + from.require_auth(); // ← gates the nested approve + + let token_client = token::Client::new(&env, &token_addr); + token_client.approve(&from, &env.current_contract_address(), &cap, &expiry); + + env.storage().persistent().set(&payment_id, AuthRecord { from, to, cap, expiry, consumed: false }); + Ok(()) +} + +pub fn settle(env, payment_id, actual) -> Result<(), SpikeError> { + // ... validation ... + token_client.transfer_from(&self, &record.from, &record.to, &actual); + token_client.approve(&record.from, &self, &0, &0); // clear allowance + record.consumed = true; + // ... +} +``` + +Key implementation detail: `from.require_auth()` is called **before** the +nested `token_client.approve()`. Without this, Soroban refuses the nested +approve because no auth entry covers `from`'s authorization for the token +call. This was the root cause of all initial test failures (7 of 12 original +tests failed with `Error(Auth, InvalidAction)`). + +## 4. Results + +### 4.1 Auth tree structure — THE ANSWER + +**Yes, one signed auth entry covers both calls.** The recorded auth tree: + +``` +Payer: Contract(CAAA...D2KM) +Root invocation: Contract((Contract(CAAA...TA4), Symbol(authorize), [...])) +Sub-invocations count: 1 + Sub[0]: Contract((Contract(CBUS...IUNF), Symbol(approve), [...]))) +``` + +The payer's single auth entry contains: +- **Root**: `authorize(payment_id, from, to, cap, expiry)` on the UptoAuthorization contract +- **Sub-invocation**: `approve(from, spender=contract, amount=cap, expiry)` on the SEP-41 token + +The payer's signature commits to **both** the `authorize` arguments AND the +exact `approve` arguments (spender, amount, expiry). This is a binding +commitment — the buyer cannot later claim they authorized a different approve. + +### 4.2 Negative controls — enforced auth tree matching + +| Test | What it proves | Result | +|---|---|---| +| **Missing sub-invocation** | Auth tree without `approve` as sub-invocation is rejected | Rejected (`InvalidAction`) | +| **Wrong approve amount** | Auth tree with `approve(amount=999_999)` when actual call is `1_000_000` is rejected | Rejected (`InvalidAction`) | +| **Wrong approve spender** | Auth tree with `approve(spender=wrong_address)` is rejected | Rejected (`InvalidAction`) | + +These three tests prove Soroban enforces exact argument matching on the +sub-invocation. A malicious or buggy facilitator cannot submit an auth tree +that grants less than the contract actually uses, nor redirect the approve +to a different spender. + +### 4.3 Business logic — full flow verified + +| Test | What it proves | Result | +|---|---|---| +| Full flow (authorize → settle) | Allowance set correctly, transfer executes, allowance cleared | Pass | +| Double settle rejected | Second `settle()` on same `payment_id` fails with `AlreadyConsumed` | Pass | +| Exceed cap rejected | `settle(amount > cap)` fails with `AmountExceedsCap` | Pass | +| Settle without authorize | `settle()` on nonexistent `payment_id` fails with `NotSettled` | Pass | +| Zero cap rejected | `authorize(cap=0)` fails with `AmountExceedsCap` | Pass | +| State inspection | On-chain `AuthRecord` matches call arguments exactly | Pass | + +### 4.4 Budget measurements + +| Construction | CPU instructions | Memory bytes | +|---|---|---| +| **Nested** (authorize calls token.approve) | 209,231 | 90,553 | +| **Separate** (token.approve + authorize independently) | 213,927 | 89,949 | + +The nested construction is actually **~2% cheaper** in CPU than separate +invocations. This is likely because the nested path avoids redundant setup +overhead. The memory difference is negligible (< 1%). + +**Conclusion: nested construction has no measurable cost penalty.** + +## 5. Security implications + +### What the buyer's signature commits to + +When the buyer signs the auth entry, they are signing a tree that includes: + +1. `authorize(payment_id, buyer, seller, cap, expiry)` — binding to recipient +2. `approve(buyer, upto_contract, cap, expiry)` — binding the approve arguments + +This means: +- The **recipient** (`seller`) is cryptographically bound at auth time — the + facilitator cannot redirect the payment. +- The **cap** is committed in both the `authorize` args and the `approve` args — + cannot be inflated after signing. +- The **spender** (UptoAuthorization contract address) is committed — the + approve cannot be redirected to a different contract. +- The **expiry** is committed — the allowance cannot outlive the agreed window. + +### What a malicious facilitator cannot do + +1. **Cannot settle for more than cap** — enforced by contract logic + approve amount. +2. **Cannot redirect to a different recipient** — `to` is in the auth entry and not + an argument to `settle`. +3. **Cannot split into multiple settlements** — `consumed` flag prevents double-settle. +4. **Cannot modify the approve arguments** — Soroban enforces exact matching of the + sub-invocation in the auth tree. + +### Trust model + +The buyer trusts: +- The UptoAuthorization contract code (auditable). +- The SEP-41 token's `approve` and `transfer_from` semantics. +- The facilitator to submit the correct auth tree (enforced by Soroban — the + facilitator cannot forge or modify the buyer's signature). + +The buyer does NOT trust: +- The facilitator with redirect authority (recipient is bound). +- The facilitator with unlimited spending (cap is committed). + +## 6. Recommendation + +**VIABLE — proceed with nested auth construction in ADR-002 §4.** + +The experiment conclusively demonstrates that: +1. One Soroban auth entry CAN cover a parent invocation with a nested approve. +2. The sub-invocation's arguments are fully committed by the buyer's signature. +3. Soroban enforces exact matching — mismatched trees are rejected. +4. The nested construction has no cost penalty vs. separate invocations. + +The only implementation requirement is that `from.require_auth()` must be +called **before** the nested `token_client.approve()` in the `authorize` +function. Without this, Soroban refuses the nested approve because no auth +entry exists for `from` at that point in the invocation. + +## 7. Files + +| File | Purpose | +|---|---| +| `spikes/upto-nested-approve/src/lib.rs` | Spike contract: `UptoAuthorization` with `authorize` + `settle` | +| `spikes/upto-nested-approve/src/test.rs` | 13 tests: auth tree inspection, positive flow, negative controls, budget | +| `spikes/upto-nested-approve/Cargo.toml` | Package config (crate-type `rlib`, no `cdylib`) | +| `spikes/upto-nested-approve/test_snapshots/` | 13 test snapshot JSON files | diff --git a/spikes/upto-nested-approve/Cargo.toml b/spikes/upto-nested-approve/Cargo.toml new file mode 100644 index 00000000..ea119e11 --- /dev/null +++ b/spikes/upto-nested-approve/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "spike-upto-nested-approve" +version = "0.1.0" +edition = "2021" +publish = false +description = "Research spike: can one Soroban auth entry cover a nested approve? (ADR-002 §6.2)" + +[lints] +workspace = true + +[lib] +crate-type = ["rlib"] + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/spikes/upto-nested-approve/src/lib.rs b/spikes/upto-nested-approve/src/lib.rs new file mode 100644 index 00000000..0a73590a --- /dev/null +++ b/spikes/upto-nested-approve/src/lib.rs @@ -0,0 +1,132 @@ +//! # Spike: UptoAuthorization with Nested SEP-41 Approve +//! +//! **This is experimental research code for ADR-002 §6.2.** +//! It is NOT production code. It does NOT implement the final upto scheme. +//! +//! Purpose: Determine whether a single Soroban authorization entry can cover +//! a parent contract invocation that makes a nested sub-invocation to a +//! SEP-41 token's `approve` function. + +use soroban_sdk::{contract, contracterror, contractimpl, contracttype, token, Address, Env}; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SpikeError { + AlreadyConsumed = 1, + Expired = 2, + AmountExceedsCap = 3, + NotSettled = 4, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AuthRecord { + pub from: Address, + pub to: Address, + pub cap: i128, + pub expiry: u32, + pub consumed: bool, +} + +#[contract] +pub struct UptoAuthorization; + +#[contractimpl] +impl UptoAuthorization { + /// Bind a recipient and approve a token allowance for this contract. + /// + /// The nested `token.approve(from, spender=self, amount, expiry)` is the + /// sub-invocation whose authorization coverage is under investigation. + /// + /// In the ADR-002 §4 construction, the payer signs a single auth tree + /// covering both this call and the nested approve. + pub fn authorize( + env: Env, + payment_id: u32, + from: Address, + to: Address, + cap: i128, + expiry: u32, + ) -> Result<(), SpikeError> { + if cap <= 0 { + return Err(SpikeError::AmountExceedsCap); + } + + // REQUEST AUTH: `from` must authorize this call so that the subsequent + // nested `token.approve(from, ...)` is covered by `from`'s auth entry. + // Without this, Soroban refuses the nested approve on behalf of `from`. + from.require_auth(); + + let token_addr: Address = env + .storage() + .instance() + .get(&"token") + .ok_or(SpikeError::NotSettled)?; + let token_client = token::Client::new(&env, &token_addr); + + // NESTED SUB-INVOCATION: approve this contract as spender on behalf of `from`. + // This is the critical call — does the payer's auth entry cover it? + token_client.approve(&from, &env.current_contract_address(), &cap, &expiry); + + let record = AuthRecord { + from, + to, + cap, + expiry, + consumed: false, + }; + env.storage().persistent().set(&payment_id, &record); + + Ok(()) + } + + /// Settle an authorization by transferring the actual amount. + /// Clears the approval after settlement. + pub fn settle(env: Env, payment_id: u32, actual: i128) -> Result<(), SpikeError> { + let mut record: AuthRecord = env + .storage() + .persistent() + .get(&payment_id) + .ok_or(SpikeError::NotSettled)?; + + if record.consumed { + return Err(SpikeError::AlreadyConsumed); + } + if actual > record.cap || actual <= 0 { + return Err(SpikeError::AmountExceedsCap); + } + + let token_addr: Address = env + .storage() + .instance() + .get(&"token") + .ok_or(SpikeError::NotSettled)?; + let token_client = token::Client::new(&env, &token_addr); + + // Transfer actual amount from the buyer to the seller. + // The spender (this contract) uses the allowance. + token_client.transfer_from( + &env.current_contract_address(), // spender (authorized via approve) + &record.from, // from + &record.to, // to + &actual, // amount + ); + + // Clear the approval. + token_client.approve(&record.from, &env.current_contract_address(), &0, &0); + + record.consumed = true; + env.storage().persistent().set(&payment_id, &record); + + Ok(()) + } + + /// Initialize with the token address. + pub fn initialize(env: Env, token: Address) { + env.storage().instance().set(&"token", &token); + } +} + +// ── Test module ────────────────────────────────────────────────────────────── +#[cfg(test)] +mod test; diff --git a/spikes/upto-nested-approve/src/test.rs b/spikes/upto-nested-approve/src/test.rs new file mode 100644 index 00000000..dc0df911 --- /dev/null +++ b/spikes/upto-nested-approve/src/test.rs @@ -0,0 +1,460 @@ +#![cfg(test)] +//! # Spike Tests: Nested Authorization for UptoAuthorization +//! +//! These tests empirically investigate whether a single Soroban auth entry +//! can cover a parent contract invocation with a nested SEP-41 token approve. +//! +//! **Approach:** +//! - All setups use `mock_all_auths()` for the token minting that happens in setup. +//! - Tests use `env.auths()` to inspect the recorded authorization tree. +//! - `mock_all_auths()` puts the host in **recording** mode: all `require_auth` +//! calls succeed and are recorded. `env.auths()` returns the recorded tree. +//! - This is sufficient to prove the auth tree structure Soroban creates and +//! that a single payer auth entry covers the nested approve. + +use super::*; +use soroban_sdk::{ + testutils::{Address as _, AuthorizedFunction, MockAuth, MockAuthInvoke}, + token::{StellarAssetClient, TokenClient}, + Address, Env, IntoVal, +}; + +const FLOAT: i128 = 10_000_000; + +// ── Setup ───────────────────────────────────────────────────────────────────── + +fn setup() -> (Env, UptoAuthorizationClient<'static>, Address, Address) { + let env = Env::default(); + env.mock_all_auths_allowing_non_root_auth(); + + let payer = Address::generate(&env); + let token_admin = Address::generate(&env); + let sac = env.register_stellar_asset_contract_v2(token_admin); + let token = sac.address(); + StellarAssetClient::new(&env, &token).mint(&payer, &FLOAT); + + let contract_id = env.register(UptoAuthorization, ()); + UptoAuthorizationClient::new(&env, &contract_id).initialize(&token); + let client = UptoAuthorizationClient::new(&env, &contract_id); + + (env, client, payer, token) +} + +// ── A. Recording-mode: auth tree inspection ─────────────────────────────────── +// +// These tests use recording mode to capture the exact authorization tree +// Soroban creates. This directly answers: "Can a single signed auth entry +// contain the parent authorization plus nested approve sub-invocation?" + +#[test] +fn test_1_recording_auth_tree_nested_approve() { + let (env, client, payer, token) = setup(); + + let payment_id: u32 = 1; + let cap: i128 = 1_000_000; + let seller = Address::generate(&env); + + // Execute: this triggers from.require_auth() + token.approve(from, ...) + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + + // Capture the recorded authorization tree. + let auths = env.auths(); + + // Find payer's auth entry. + let payer_auth = auths + .iter() + .find(|(addr, _)| *addr == payer) + .expect("payer must have an auth entry in the recorded tree"); + + let (_, payer_invocation) = payer_auth; + + // The payer's invocation must be: authorize(payment_id, from, to, cap, expiry) + match &payer_invocation.function { + AuthorizedFunction::Contract((addr, fn_name, _args)) => { + assert_eq!(*addr, client.address); + assert_eq!(fn_name.to_string(), "authorize"); + } + other => panic!( + "Expected Contract invocation, got: {:?}", + std::mem::discriminant(other) + ), + } + + // CRITICAL: The payer's auth tree MUST contain the nested approve as a + // sub-invocation. This is the tree structure that the payer signs. + // If this works, it proves that one signed auth entry covers both calls. + let has_nested_approve = payer_invocation.sub_invocations.iter().any(|sub| { + matches!( + &sub.function, + AuthorizedFunction::Contract((addr, fn_name, _)) + if *addr == token && fn_name.to_string() == "approve" + ) + }); + + assert!( + has_nested_approve, + "The payer's auth tree MUST contain token.approve as a sub-invocation. \ + This proves the payer's single signature commits to the exact approve arguments." + ); + + // Verify token state: the nested approve executed. + let token_client = TokenClient::new(&env, &token); + assert_eq!(token_client.allowance(&payer, &client.address), cap); +} + +#[test] +fn test_2_recording_full_flow_authorize_then_settle() { + let (env, client, payer, token) = setup(); + + let payment_id: u32 = 2; + let cap: i128 = 2_500_000; + let actual: i128 = 750_000; + let seller = Address::generate(&env); + + // Phase 1: authorize + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + + let token_client = TokenClient::new(&env, &token); + assert_eq!(token_client.allowance(&payer, &client.address), cap); + + // Phase 2: settle + client.settle(&payment_id, &actual); + + // Verify: allowance cleared, balances reflect transfer. + assert_eq!(token_client.allowance(&payer, &client.address), 0); + assert_eq!(token_client.balance(&payer), FLOAT - actual); + assert_eq!(token_client.balance(&seller), actual); +} + +#[test] +fn test_3_recording_auth_tree_structure_detail() { + // This test inspects the EXACT structure of the auth tree to document + // what the payer's signature commits to. + let (env, client, payer, token) = setup(); + + let payment_id: u32 = 3; + let cap: i128 = 1_000_000; + let seller = Address::generate(&env); + + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + + let auths = env.auths(); + let (_, payer_invocation) = auths + .iter() + .find(|(addr, _)| *addr == payer) + .expect("payer auth entry must exist"); + + // Print the full auth tree for documentation. + println!("=== AUTH TREE STRUCTURE ==="); + println!("Payer: {payer:?}"); + println!("Root invocation: {:?}", payer_invocation.function); + println!( + "Sub-invocations count: {}", + payer_invocation.sub_invocations.len() + ); + for (i, sub) in payer_invocation.sub_invocations.iter().enumerate() { + println!(" Sub[{i}]: {:?}", sub.function); + for (j, nested) in sub.sub_invocations.iter().enumerate() { + println!(" Nested[{j}]: {:?}", nested.function); + } + } + println!("============================"); + + // Verify: exactly 1 sub-invocation (the approve). + assert_eq!( + payer_invocation.sub_invocations.len(), + 1, + "Expected exactly 1 sub-invocation (the token.approve)" + ); + + // Verify: the sub-invocation is token.approve. + let sub = &payer_invocation.sub_invocations[0]; + match &sub.function { + AuthorizedFunction::Contract((addr, fn_name, args)) => { + assert_eq!(*addr, token); + assert_eq!(fn_name.to_string(), "approve"); + // args: [from=payer, spender=contract, cap, expiry] + assert_eq!(args.len(), 4); + } + other => panic!("Expected Contract sub-invocation, got: {:?}", other), + } + + // Verify token state. + let token_client = TokenClient::new(&env, &token); + assert_eq!(token_client.allowance(&payer, &client.address), cap); +} + +// ── B. Positive flow tests ──────────────────────────────────────────────────── + +#[test] +fn test_4_recording_double_settle_rejected() { + let (env, client, payer, _token) = setup(); + + let payment_id: u32 = 4; + let cap: i128 = 1_000_000; + let seller = Address::generate(&env); + + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + client.settle(&payment_id, &500_000); + + let result = client.try_settle(&payment_id, &100); + assert_eq!(result, Err(Ok(SpikeError::AlreadyConsumed))); +} + +#[test] +fn test_5_recording_settle_exceeding_cap_rejected() { + let (env, client, payer, _token) = setup(); + + let payment_id: u32 = 5; + let cap: i128 = 1_000_000; + let seller = Address::generate(&env); + + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + + let result = client.try_settle(&payment_id, &(cap + 1)); + assert_eq!(result, Err(Ok(SpikeError::AmountExceedsCap))); +} + +#[test] +fn test_6_recording_settle_without_authorize_rejected() { + let (_env, client, _payer, _token) = setup(); + assert_eq!( + client.try_settle(&999, &100), + Err(Ok(SpikeError::NotSettled)) + ); +} + +#[test] +fn test_7_recording_zero_cap_rejected() { + let (env, client, payer, _token) = setup(); + let seller = Address::generate(&env); + assert_eq!( + client.try_authorize(&999, &payer, &seller, &0, &100_000), + Err(Ok(SpikeError::AmountExceedsCap)) + ); +} + +#[test] +fn test_8_recording_state_after_authorize() { + let (env, client, payer, token) = setup(); + + let payment_id: u32 = 80; + let cap: i128 = 2_500_000; + let seller = Address::generate(&env); + + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + + // Read the authorization record from contract storage. + env.as_contract(&client.address, || { + let record: AuthRecord = env.storage().persistent().get(&payment_id).unwrap(); + assert_eq!(record.from, payer); + assert_eq!(record.to, seller); + assert_eq!(record.cap, cap); + assert_eq!(record.expiry, 100_000); + assert!(!record.consumed); + }); + + let token_client = TokenClient::new(&env, &token); + assert_eq!(token_client.allowance(&payer, &client.address), cap); +} + +// ── C. Negative control: mock_auths with missing sub_invocation ─────────────── +// +// These tests use `mock_auths()` (enforcing mode) to demonstrate that +// incomplete or mismatched auth trees are rejected by Soroban. + +#[test] +fn test_9_mock_missing_sub_invocation_panics() { + // If the auth tree does NOT include the nested approve as a sub-invocation, + // Soroban MUST reject it. The payer cannot cover the nested approve + // with just the parent auth entry. + let (env, client, payer, _token) = setup(); + + let payment_id: u32 = 9; + let cap: i128 = 1_000_000; + let seller = Address::generate(&env); + let contract_addr = client.address.clone(); + + let auth_args = soroban_sdk::vec![ + &env, + payment_id.into_val(&env), + payer.into_val(&env), + seller.into_val(&env), + cap.into_val(&env), + 100_000u32.into_val(&env), + ]; + + // Explicit auth entry WITHOUT sub_invokes — missing the nested approve. + env.mock_auths(&[MockAuth { + address: &payer, + invoke: &MockAuthInvoke { + contract: &contract_addr, + fn_name: "authorize", + args: auth_args, + sub_invokes: &[], // <-- MISSING: no nested approve + }, + }]); + + // Must panic: the nested approve has no auth coverage. + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + })); + assert!( + result.is_err(), + "authorize MUST fail when the auth tree lacks the nested approve sub-invocation" + ); +} + +#[test] +fn test_10_mock_wrong_approve_amount_panics() { + // If the sub-invocation's approve amount doesn't match the actual call, + // Soroban MUST reject it. The signed payload is immutable. + let (env, client, payer, tok) = setup(); + + let payment_id: u32 = 10; + let cap: i128 = 1_000_000; + let wrong_amount: i128 = 999_999; + let seller = Address::generate(&env); + let contract_addr = client.address.clone(); + + let auth_args = soroban_sdk::vec![ + &env, + payment_id.into_val(&env), + payer.into_val(&env), + seller.into_val(&env), + cap.into_val(&env), + 100_000u32.into_val(&env), + ]; + let approve_args = soroban_sdk::vec![ + &env, + payer.into_val(&env), + contract_addr.into_val(&env), + wrong_amount.into_val(&env), + 100_000u32.into_val(&env), + ]; + + env.mock_auths(&[MockAuth { + address: &payer, + invoke: &MockAuthInvoke { + contract: &contract_addr, + fn_name: "authorize", + args: auth_args, + sub_invokes: &[MockAuthInvoke { + contract: &tok, + fn_name: "approve", + args: approve_args, + sub_invokes: &[], + }], + }, + }]); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + })); + assert!( + result.is_err(), + "authorize MUST fail when the sub-invocation approve amount is wrong" + ); +} + +#[test] +fn test_11_mock_wrong_approve_spender_panics() { + // If the sub-invocation's spender is wrong, Soroban MUST reject it. + // The payer cannot redirect the approve to a different spender. + let (env, client, payer, tok) = setup(); + + let payment_id: u32 = 11; + let cap: i128 = 1_000_000; + let seller = Address::generate(&env); + let contract_addr = client.address.clone(); + let wrong_spender = Address::generate(&env); + + let auth_args = soroban_sdk::vec![ + &env, + payment_id.into_val(&env), + payer.into_val(&env), + seller.into_val(&env), + cap.into_val(&env), + 100_000u32.into_val(&env), + ]; + let approve_args = soroban_sdk::vec![ + &env, + payer.into_val(&env), + wrong_spender.into_val(&env), + cap.into_val(&env), + 100_000u32.into_val(&env), + ]; + + env.mock_auths(&[MockAuth { + address: &payer, + invoke: &MockAuthInvoke { + contract: &contract_addr, + fn_name: "authorize", + args: auth_args, + sub_invokes: &[MockAuthInvoke { + contract: &tok, + fn_name: "approve", + args: approve_args, + sub_invokes: &[], + }], + }, + }]); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + })); + assert!( + result.is_err(), + "authorize MUST fail when the sub-invocation approve spender is wrong" + ); +} + +// ── D. Budget measurements ──────────────────────────────────────────────────── + +#[test] +fn test_20_budget_nested_construction() { + let (env, client, payer, _token) = setup(); + + let payment_id: u32 = 200; + let cap: i128 = 1_000_000; + let seller = Address::generate(&env); + + // Nested: authorize calls token.approve as a sub-invocation. + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + + let budget = env.cost_estimate().budget(); + let cpu = budget.cpu_instruction_cost(); + let mem = budget.memory_bytes_cost(); + + println!("=== BUDGET: NESTED CONSTRUCTION ==="); + println!(" CPU instructions: {cpu}"); + println!(" Memory bytes: {mem}"); + println!(" (authorize + nested token.approve)"); + println!("===================================="); +} + +#[test] +fn test_21_budget_separate_invocations() { + let (env, client, payer, token) = setup(); + + let payment_id: u32 = 210; + let cap: i128 = 1_000_000; + let seller = Address::generate(&env); + + // Separate: token.approve called directly, then authorize (no nested call). + let token_client = TokenClient::new(&env, &token); + token_client.approve(&payer, &client.address, &cap, &100_000); + client.authorize(&payment_id, &payer, &seller, &cap, &100_000); + + let budget = env.cost_estimate().budget(); + let cpu = budget.cpu_instruction_cost(); + let mem = budget.memory_bytes_cost(); + + println!("=== BUDGET: SEPARATE INVOCATIONS ==="); + println!(" CPU instructions: {cpu}"); + println!(" Memory bytes: {mem}"); + println!(" (token.approve direct + authorize without nested)"); + println!("======================================"); + + assert_eq!(token_client.allowance(&payer, &client.address), cap); +} diff --git a/spikes/upto-nested-approve/test_snapshots/test/test_10_mock_wrong_approve_amount_panics.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_10_mock_wrong_approve_amount_panics.1.json new file mode 100644 index 00000000..b1eb63d2 --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_10_mock_wrong_approve_amount_panics.1.json @@ -0,0 +1,353 @@ +{ + "generators": { + "address": 5, + "nonce": 1, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_11_mock_wrong_approve_spender_panics.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_11_mock_wrong_approve_spender_panics.1.json new file mode 100644 index 00000000..72565763 --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_11_mock_wrong_approve_spender_panics.1.json @@ -0,0 +1,353 @@ +{ + "generators": { + "address": 6, + "nonce": 1, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_1_recording_auth_tree_nested_approve.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_1_recording_auth_tree_nested_approve.1.json new file mode 100644 index 00000000..0216b243 --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_1_recording_auth_tree_nested_approve.1.json @@ -0,0 +1,526 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "authorize", + "args": [ + { + "u32": 1 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "u32": 1 + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cap" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "consumed" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "expiry" + }, + "val": { + "u32": 100000 + } + }, + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "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": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 100000 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 100000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_20_budget_nested_construction.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_20_budget_nested_construction.1.json new file mode 100644 index 00000000..eee4ed07 --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_20_budget_nested_construction.1.json @@ -0,0 +1,562 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "authorize", + "args": [ + { + "u32": 200 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "u32": 200 + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cap" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "consumed" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "expiry" + }, + "val": { + "u32": 100000 + } + }, + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "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": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 100000 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 100000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "approve" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + ], + "data": { + "vec": [ + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/spikes/upto-nested-approve/test_snapshots/test/test_21_budget_separate_invocations.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_21_budget_separate_invocations.1.json new file mode 100644 index 00000000..eacbf83f --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_21_budget_separate_invocations.1.json @@ -0,0 +1,574 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "authorize", + "args": [ + { + "u32": 210 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "u32": 210 + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cap" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "consumed" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "expiry" + }, + "val": { + "u32": 100000 + } + }, + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "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": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 100000 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 100000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_2_recording_full_flow_authorize_then_settle.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_2_recording_full_flow_authorize_then_settle.1.json new file mode 100644 index 00000000..5e5107ca --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_2_recording_full_flow_authorize_then_settle.1.json @@ -0,0 +1,629 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "authorize", + "args": [ + { + "u32": 2 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "2500000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "2500000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "0" + }, + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "u32": 2 + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cap" + }, + "val": { + "i128": "2500000" + } + }, + { + "key": { + "symbol": "consumed" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "expiry" + }, + "val": { + "u32": 100000 + } + }, + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "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": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 0 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 100000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "9250000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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": "750000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_3_recording_auth_tree_structure_detail.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_3_recording_auth_tree_structure_detail.1.json new file mode 100644 index 00000000..cab8551f --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_3_recording_auth_tree_structure_detail.1.json @@ -0,0 +1,526 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "authorize", + "args": [ + { + "u32": 3 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "u32": 3 + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cap" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "consumed" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "expiry" + }, + "val": { + "u32": 100000 + } + }, + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "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": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 100000 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 100000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_4_recording_double_settle_rejected.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_4_recording_double_settle_rejected.1.json new file mode 100644 index 00000000..282778fd --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_4_recording_double_settle_rejected.1.json @@ -0,0 +1,626 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "authorize", + "args": [ + { + "u32": 4 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "0" + }, + { + "u32": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "u32": 4 + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cap" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "consumed" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "expiry" + }, + "val": { + "u32": 100000 + } + }, + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "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": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 0 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 100000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "9500000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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": "500000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_5_recording_settle_exceeding_cap_rejected.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_5_recording_settle_exceeding_cap_rejected.1.json new file mode 100644 index 00000000..bf6b4a4c --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_5_recording_settle_exceeding_cap_rejected.1.json @@ -0,0 +1,526 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "authorize", + "args": [ + { + "u32": 5 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "1000000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "u32": 5 + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cap" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "consumed" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "expiry" + }, + "val": { + "u32": 100000 + } + }, + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "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": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1000000" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 100000 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 100000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_6_recording_settle_without_authorize_rejected.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_6_recording_settle_without_authorize_rejected.1.json new file mode 100644 index 00000000..cecab659 --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_6_recording_settle_without_authorize_rejected.1.json @@ -0,0 +1,329 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_7_recording_zero_cap_rejected.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_7_recording_zero_cap_rejected.1.json new file mode 100644 index 00000000..671119c4 --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_7_recording_zero_cap_rejected.1.json @@ -0,0 +1,329 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_8_recording_state_after_authorize.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_8_recording_state_after_authorize.1.json new file mode 100644 index 00000000..518db680 --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_8_recording_state_after_authorize.1.json @@ -0,0 +1,527 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "authorize", + "args": [ + { + "u32": 80 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "i128": "2500000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "approve", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "i128": "2500000" + }, + { + "u32": 100000 + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "u32": 80 + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "cap" + }, + "val": { + "i128": "2500000" + } + }, + { + "key": { + "symbol": "consumed" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "expiry" + }, + "val": { + "u32": 100000 + } + }, + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "to" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "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": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "2500000" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 100000 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 100000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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/spikes/upto-nested-approve/test_snapshots/test/test_9_mock_missing_sub_invocation_panics.1.json b/spikes/upto-nested-approve/test_snapshots/test/test_9_mock_missing_sub_invocation_panics.1.json new file mode 100644 index 00000000..b1eb63d2 --- /dev/null +++ b/spikes/upto-nested-approve/test_snapshots/test/test_9_mock_missing_sub_invocation_panics.1.json @@ -0,0 +1,353 @@ +{ + "generators": { + "address": 5, + "nonce": 1, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "i128": "10000000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 27, + "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": { + "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" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "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": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "string": "token" + }, + "val": { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "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" + }, + "live_until": 120960 + }, + { + "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