From 0de4efdbb740330358e5d5639be2da23dd2b85c1 Mon Sep 17 00:00:00 2001 From: Dannyswiss1 Date: Tue, 18 Aug 2026 05:16:22 +0100 Subject: [PATCH 1/2] [Smart Contracts] Add error expiration/TTL with configurable retention and automatic cleanup --- .../contracts/agent_registry/src/lib.rs | 210 +++++++++- ..._expired_errors_ignores_unknown_ids.1.json | 102 +++++ ...ired_errors_removes_expired_entries.1.json | 390 ++++++++++++++++++ .../error_expires_after_configured_ttl.1.json | 228 ++++++++++ ...imate_gas_cleanup_scales_with_count.1.json | 77 ++++ .../get_error_ttl_defaults_when_unset.1.json | 76 ++++ ...eport_error_sets_default_expiration.1.json | 266 ++++++++++++ ...s_already_resolved_fails_atomically.1.json | 32 ++ .../test/resolve_errors_batch_success.1.json | 48 +++ ...ve_errors_partial_failure_is_atomic.1.json | 16 + .../resolve_errors_requires_admin_auth.1.json | 16 + .../set_error_ttl_requires_admin_auth.1.json | 167 ++++++++ .../set_gas_config_requires_admin_auth.1.json | 32 ++ 13 files changed, 1656 insertions(+), 4 deletions(-) create mode 100644 smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_ignores_unknown_ids.1.json create mode 100644 smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_removes_expired_entries.1.json create mode 100644 smart-contracts/contracts/agent_registry/test_snapshots/test/error_expires_after_configured_ttl.1.json create mode 100644 smart-contracts/contracts/agent_registry/test_snapshots/test/estimate_gas_cleanup_scales_with_count.1.json create mode 100644 smart-contracts/contracts/agent_registry/test_snapshots/test/get_error_ttl_defaults_when_unset.1.json create mode 100644 smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_sets_default_expiration.1.json create mode 100644 smart-contracts/contracts/agent_registry/test_snapshots/test/set_error_ttl_requires_admin_auth.1.json diff --git a/smart-contracts/contracts/agent_registry/src/lib.rs b/smart-contracts/contracts/agent_registry/src/lib.rs index e085f54..c0a9bc4 100644 --- a/smart-contracts/contracts/agent_registry/src/lib.rs +++ b/smart-contracts/contracts/agent_registry/src/lib.rs @@ -61,12 +61,20 @@ pub const GAS_REGISTER_AGENT_MARGINAL: u64 = 55_556; pub const GAS_RESOLVE_ERROR: u64 = 50_000; /// Marginal cost of each additional error resolution in a batch. pub const GAS_RESOLVE_ERROR_MARGINAL: u64 = 30_000; +/// Full cost of checking/removing a single expired error (includes overhead). +pub const GAS_CLEANUP_ERROR: u64 = 20_000; +/// Marginal cost of each additional error checked in a cleanup batch. +pub const GAS_CLEANUP_ERROR_MARGINAL: u64 = 10_000; /// Default TTL threshold (ledgers remaining) below which we extend. pub const TTL_THRESHOLD: u32 = 100_000; /// Target TTL after extension (~31 days at 5s ledgers: 535_680). pub const TTL_EXTEND_TO: u32 = 535_680; +/// Default error entry retention, in ledger sequences (~30 days at 5s/ledger). +/// Overridable via `set_error_ttl`. +pub const DEFAULT_ERROR_TTL: u64 = 518_400; + // ─── Types ─────────────────────────────────────────────────────────────────── /// Input / stored agent record. @@ -115,6 +123,10 @@ pub struct ErrorEntry { pub message: String, pub resolved: bool, pub resolution: Resolution, + /// Ledger sequence at which this entry was created. + pub created_at: u64, + /// Ledger sequence at/after which this entry is eligible for cleanup. + pub expires_at: u64, } /// Empirical gas budget parameters (instance storage). @@ -126,6 +138,8 @@ pub struct GasConfig { pub register_agent_marginal: u64, pub resolve_error: u64, pub resolve_error_marginal: u64, + pub cleanup_error: u64, + pub cleanup_error_marginal: u64, } impl GasConfig { @@ -136,6 +150,8 @@ impl GasConfig { register_agent_marginal: GAS_REGISTER_AGENT_MARGINAL, resolve_error: GAS_RESOLVE_ERROR, resolve_error_marginal: GAS_RESOLVE_ERROR_MARGINAL, + cleanup_error: GAS_CLEANUP_ERROR, + cleanup_error_marginal: GAS_CLEANUP_ERROR_MARGINAL, } } } @@ -150,6 +166,8 @@ pub enum DataKey { FrozenAgent(Symbol), ErrorRecord(BytesN<32>), GasConfig, + /// Configurable TTL (in ledger sequences) applied to new error entries. + ErrorTTL, } /// Per-item outcome for batch registration (`Ok(agent_id)` / `Err(code)`). @@ -249,6 +267,13 @@ fn gas_config(env: &Env) -> GasConfig { .unwrap_or_else(GasConfig::default_config) } +fn error_ttl(env: &Env) -> u64 { + env.storage() + .instance() + .get(&DataKey::ErrorTTL) + .unwrap_or(DEFAULT_ERROR_TTL) +} + fn extend_ttl_for_key(env: &Env, key: &DataKey) { // Only extend when the entry exists; extend_ttl panics on missing keys. if env.storage().persistent().has(key) { @@ -657,6 +682,7 @@ impl AgentRegistryContract { return Err(Error::AlreadyExists); } + let created_at = env.ledger().sequence() as u64; let entry = ErrorEntry { id: error_id, reporter, @@ -664,12 +690,55 @@ impl AgentRegistryContract { resolved: false, // Placeholder until resolve_errors overwrites with a real resolution. resolution: Resolution::Fixed, + created_at, + expires_at: created_at + error_ttl(&env), }; env.storage().persistent().set(&key, &entry); extend_ttl_for_key(&env, &key); Ok(()) } + /// Configure how many ledger sequences newly reported errors live for + /// before becoming eligible for `cleanup_expired_errors`. + pub fn set_error_ttl(env: Env, ttl_ledgers: u64) -> Result<(), Error> { + require_admin(&env)?; + env.storage() + .instance() + .set(&DataKey::ErrorTTL, &ttl_ledgers); + Ok(()) + } + + /// Read the currently configured error retention (defaults if never set). + pub fn get_error_ttl(env: Env) -> u64 { + error_ttl(&env) + } + + /// Remove expired error entries from persistent storage. + /// + /// Soroban has no iterator over all contract keys, so callers pass the + /// set of error ids to check (e.g. from off-chain indexing of + /// `report_error` events). Permissionless: anyone can pay to garbage + /// collect entries that are already past their `expires_at`, unresolved + /// or not. Returns the number of entries actually removed. + pub fn cleanup_expired_errors(env: Env, error_ids: Vec>) -> u32 { + let current_seq = env.ledger().sequence() as u64; + let mut removed = 0u32; + + for i in 0..error_ids.len() { + let id = error_ids.get(i).unwrap(); + let key = DataKey::ErrorRecord(id); + let entry: Option = env.storage().persistent().get(&key); + if let Some(entry) = entry { + if entry.expires_at <= current_seq { + env.storage().persistent().remove(&key); + removed += 1; + } + } + } + + removed + } + /// Resolve multiple errors in one transaction (atomic all-or-nothing). /// /// Validates every id first; writes only if all succeed. Per-item results @@ -731,10 +800,14 @@ impl AgentRegistryContract { } /// Fetch a single error entry (for tests / off-chain indexing). + /// Extends the entry's storage TTL on access, like agent records. pub fn get_error(env: Env, error_id: BytesN<32>) -> Option { - env.storage() - .persistent() - .get(&DataKey::ErrorRecord(error_id)) + let key = DataKey::ErrorRecord(error_id); + let entry = env.storage().persistent().get(&key); + if entry.is_some() { + extend_ttl_for_key(&env, &key); + } + entry } // ── Gas budget estimation ──────────────────────────────────────────────── @@ -744,6 +817,7 @@ impl AgentRegistryContract { /// `operation` is one of: /// - `"register_agent"` / `"register_agents"` /// - `"resolve_error"` / `"resolve_errors"` + /// - `"cleanup_expired_errors"` /// /// Returns `0` for unknown operations. Values come from [`GasConfig`] /// (defaults match the tables in `docs/gas_costs.md`). @@ -757,6 +831,7 @@ impl AgentRegistryContract { let register_agents = String::from_str(&env, "register_agents"); let resolve_error = String::from_str(&env, "resolve_error"); let resolve_errors = String::from_str(&env, "resolve_errors"); + let cleanup_expired_errors = String::from_str(&env, "cleanup_expired_errors"); if operation == register_agent || operation == register_agents { // First item pays full single-call cost; rest pay marginal. @@ -769,6 +844,11 @@ impl AgentRegistryContract { + cfg .resolve_error_marginal .saturating_mul((count - 1) as u64) + } else if operation == cleanup_expired_errors { + cfg.cleanup_error + + cfg + .cleanup_error_marginal + .saturating_mul((count - 1) as u64) } else { 0 } @@ -798,7 +878,10 @@ mod test { use super::*; use soroban_sdk::xdr::ToXdr; - use soroban_sdk::{testutils::Address as _, BytesN, Env}; + use soroban_sdk::{ + testutils::{Address as _, Ledger as _}, + BytesN, Env, + }; /// Creates a fresh in-memory test environment with the contract registered. /// @@ -1203,6 +1286,8 @@ mod test { register_agent_marginal: 20_000, resolve_error: 25_000, resolve_error_marginal: 15_000, + cleanup_error: 8_000, + cleanup_error_marginal: 4_000, }; // Test non-admin cannot set gas config @@ -1457,4 +1542,121 @@ mod test { let v = client.estimate_gas(&String::from_str(&env, "register_agents"), &0); assert_eq!(v, 0); } + + #[test] + fn estimate_gas_cleanup_scales_with_count() { + let (env, client) = setup(); + let one = client.estimate_gas(&String::from_str(&env, "cleanup_expired_errors"), &1); + let ten = client.estimate_gas(&String::from_str(&env, "cleanup_expired_errors"), &10); + + assert_eq!(one, GAS_CLEANUP_ERROR); + assert_eq!(ten, GAS_CLEANUP_ERROR + GAS_CLEANUP_ERROR_MARGINAL * 9); + assert!(ten < GAS_CLEANUP_ERROR * 10); + } + + // ── Error TTL / expiration ─────────────────────────────────────────────── + + #[test] + fn report_error_sets_default_expiration() { + let (env, client, _admin) = setup_with_admin(); + let reporter = Address::generate(&env); + let id = error_id(&env, 90); + + env.ledger().set_sequence_number(1_000); + client.report_error(&id, &reporter, &String::from_str(&env, "boom")); + + let entry = client.get_error(&id).unwrap(); + assert_eq!(entry.created_at, 1_000); + assert_eq!(entry.expires_at, 1_000 + DEFAULT_ERROR_TTL); + } + + #[test] + fn error_expires_after_configured_ttl() { + let (env, client, _admin) = setup_with_admin(); + let reporter = Address::generate(&env); + let id = error_id(&env, 91); + + env.ledger().set_sequence_number(500); + client.set_error_ttl(&100); + client.report_error(&id, &reporter, &String::from_str(&env, "flaky")); + + let entry = client.get_error(&id).unwrap(); + assert_eq!(entry.expires_at, 600); + + // Not yet expired: cleanup is a no-op, entry survives. + env.ledger().set_sequence_number(599); + assert_eq!( + client.cleanup_expired_errors(&Vec::from_array(&env, [id.clone()])), + 0 + ); + assert!(client.get_error(&id).is_some()); + + // At/after expiry: entry is eligible for cleanup and gets removed. + env.ledger().set_sequence_number(600); + assert_eq!( + client.cleanup_expired_errors(&Vec::from_array(&env, [id.clone()])), + 1 + ); + assert!(client.get_error(&id).is_none()); + } + + #[test] + fn cleanup_expired_errors_removes_expired_entries() { + let (env, client, _admin) = setup_with_admin(); + let reporter = Address::generate(&env); + let expired_id = error_id(&env, 92); + let live_id = error_id(&env, 93); + + env.ledger().set_sequence_number(1_000); + client.set_error_ttl(&50); + client.report_error(&expired_id, &reporter, &String::from_str(&env, "old")); + + env.ledger().set_sequence_number(1_040); + client.report_error(&live_id, &reporter, &String::from_str(&env, "new")); + + // Advance past expired_id's expiry (1050) but not live_id's (1090). + env.ledger().set_sequence_number(1_060); + + let ids = Vec::from_array(&env, [expired_id.clone(), live_id.clone()]); + let removed = client.cleanup_expired_errors(&ids); + + assert_eq!(removed, 1); + assert!(client.get_error(&expired_id).is_none()); + assert!(client.get_error(&live_id).is_some()); + } + + #[test] + fn cleanup_expired_errors_ignores_unknown_ids() { + let (env, client, _admin) = setup_with_admin(); + let ghost_id = error_id(&env, 94); + let removed = client.cleanup_expired_errors(&Vec::from_array(&env, [ghost_id])); + assert_eq!(removed, 0); + } + + #[test] + fn set_error_ttl_requires_admin_auth() { + let env = Env::default(); + let contract_id = env.register(AgentRegistryContract, ()); + let client = AgentRegistryContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + + env.mock_all_auths(); + client.initialize(&admin); + + // Non-admin cannot configure TTL. + env.mock_auths(&[]); + let result = client.try_set_error_ttl(&1_000); + assert!(result.is_err()); + + // Admin succeeds and the new value is reflected in get_error_ttl. + env.mock_all_auths(); + client.set_error_ttl(&1_000); + assert_eq!(client.get_error_ttl(), 1_000); + } + + #[test] + fn get_error_ttl_defaults_when_unset() { + let (env, client) = setup(); + assert_eq!(client.get_error_ttl(), DEFAULT_ERROR_TTL); + } } diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_ignores_unknown_ids.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_ignores_unknown_ids.1.json new file mode 100644 index 0000000..04f65ff --- /dev/null +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_ignores_unknown_ids.1.json @@ -0,0 +1,102 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_removes_expired_entries.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_removes_expired_entries.1.json new file mode 100644 index 0000000..2846cbf --- /dev/null +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_removes_expired_entries.1.json @@ -0,0 +1,390 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_error_ttl", + "args": [ + { + "u64": 50 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "report_error", + "args": [ + { + "bytes": "5c00000000000000000000000000000000000000000000000000000000000000" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "old" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "report_error", + "args": [ + { + "bytes": "5d00000000000000000000000000000000000000000000000000000000000000" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "new" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 1060, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "ErrorRecord" + }, + { + "bytes": "5d00000000000000000000000000000000000000000000000000000000000000" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "ErrorRecord" + }, + { + "bytes": "5d00000000000000000000000000000000000000000000000000000000000000" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 1040 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 1090 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "bytes": "5d00000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "message" + }, + "val": { + "string": "new" + } + }, + { + "key": { + "symbol": "reporter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "Fixed" + } + ] + } + }, + { + "key": { + "symbol": "resolved" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 536720 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "ErrorTTL" + } + ] + }, + "val": { + "u64": 50 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6313039 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/error_expires_after_configured_ttl.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/error_expires_after_configured_ttl.1.json new file mode 100644 index 0000000..2b37c90 --- /dev/null +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/error_expires_after_configured_ttl.1.json @@ -0,0 +1,228 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_error_ttl", + "args": [ + { + "u64": 100 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "report_error", + "args": [ + { + "bytes": "5b00000000000000000000000000000000000000000000000000000000000000" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "flaky" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 600, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "ErrorTTL" + } + ] + }, + "val": { + "u64": 100 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312499 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312499 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/estimate_gas_cleanup_scales_with_count.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/estimate_gas_cleanup_scales_with_count.1.json new file mode 100644 index 0000000..90577a3 --- /dev/null +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/estimate_gas_cleanup_scales_with_count.1.json @@ -0,0 +1,77 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/get_error_ttl_defaults_when_unset.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/get_error_ttl_defaults_when_unset.1.json new file mode 100644 index 0000000..a90f00a --- /dev/null +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/get_error_ttl_defaults_when_unset.1.json @@ -0,0 +1,76 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_sets_default_expiration.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_sets_default_expiration.1.json new file mode 100644 index 0000000..ca31a3c --- /dev/null +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_sets_default_expiration.1.json @@ -0,0 +1,266 @@ +{ + "generators": { + "address": 3, + "nonce": 0 + }, + "auth": [ + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "report_error", + "args": [ + { + "bytes": "5a00000000000000000000000000000000000000000000000000000000000000" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "string": "boom" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 1000, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "ErrorRecord" + }, + { + "bytes": "5a00000000000000000000000000000000000000000000000000000000000000" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "ErrorRecord" + }, + { + "bytes": "5a00000000000000000000000000000000000000000000000000000000000000" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 519400 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "bytes": "5a00000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "key": { + "symbol": "message" + }, + "val": { + "string": "boom" + } + }, + { + "key": { + "symbol": "reporter" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "Fixed" + } + ] + } + }, + { + "key": { + "symbol": "resolved" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 536680 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_already_resolved_fails_atomically.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_already_resolved_fails_atomically.1.json index 2b5829b..5f99251 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_already_resolved_fails_atomically.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_already_resolved_fails_atomically.1.json @@ -168,6 +168,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" @@ -258,6 +274,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_batch_success.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_batch_success.1.json index 6c1720a..310a175 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_batch_success.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_batch_success.1.json @@ -166,6 +166,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" @@ -256,6 +272,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" @@ -346,6 +378,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_partial_failure_is_atomic.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_partial_failure_is_atomic.1.json index 4e750c3..8c929f6 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_partial_failure_is_atomic.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_partial_failure_is_atomic.1.json @@ -113,6 +113,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_requires_admin_auth.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_requires_admin_auth.1.json index 49b3163..313617d 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_requires_admin_auth.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_requires_admin_auth.1.json @@ -110,6 +110,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/set_error_ttl_requires_admin_auth.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/set_error_ttl_requires_admin_auth.1.json new file mode 100644 index 0000000..fda52e2 --- /dev/null +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/set_error_ttl_requires_admin_auth.1.json @@ -0,0 +1,167 @@ +{ + "generators": { + "address": 2, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "set_error_ttl", + "args": [ + { + "u64": 1000 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "ErrorTTL" + } + ] + }, + "val": { + "u64": 1000 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Paused" + } + ] + }, + "val": { + "bool": false + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/set_gas_config_requires_admin_auth.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/set_gas_config_requires_admin_auth.1.json index 2d11a68..860b39e 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/set_gas_config_requires_admin_auth.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/set_gas_config_requires_admin_auth.1.json @@ -18,6 +18,22 @@ "args": [ { "map": [ + { + "key": { + "symbol": "cleanup_error" + }, + "val": { + "u64": 8000 + } + }, + { + "key": { + "symbol": "cleanup_error_marginal" + }, + "val": { + "u64": 4000 + } + }, { "key": { "symbol": "register_agent" @@ -124,6 +140,22 @@ }, "val": { "map": [ + { + "key": { + "symbol": "cleanup_error" + }, + "val": { + "u64": 8000 + } + }, + { + "key": { + "symbol": "cleanup_error_marginal" + }, + "val": { + "u64": 4000 + } + }, { "key": { "symbol": "register_agent" From d2efc74d3bd41614684c77b44d6b9bf7da873700 Mon Sep 17 00:00:00 2001 From: Dannyswiss1 Date: Thu, 27 Aug 2026 17:09:24 +0100 Subject: [PATCH 2/2] fix(agent-registry): restore ErrorTTL after main merge, fix duplicate cfg(test) attr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'Merge branch main into feature/agent-registry-error-ttl' merge (5b5edc0) dropped several ErrorTTL pieces while leaving their call sites, and pulled in a pre-existing main bug — both breaking 'cargo clippy -D warnings': - DataKey::ErrorTTL variant was dropped (error_ttl()/set_error_ttl() still referenced it) - error_ttl() helper fn was dropped - GAS_CLEANUP_ERROR/GAS_CLEANUP_ERROR_MARGINAL consts and the GasConfig.cleanup_error/cleanup_error_marginal fields were dropped in favor of main's slash_bond/deregister_with_bond fields instead of merging both - the estimate_gas() cleanup_expired_errors branch was dropped - all 8 TTL/cleanup unit tests were dropped Restored all of the above (merging with main's bond/multisig additions rather than replacing them), and updated the two GasConfig{} test literals to include the new fields. Also fixes a pre-existing main bug unrelated to this PR: lib.rs had both '#[cfg(test)] mod test_multisig;' and test_multisig.rs's own internal '#![cfg(test)]', which clippy's duplicated_attributes lint (-D warnings) rejects. Removed the redundant outer attribute. cargo fmt --check, cargo clippy -D warnings, and cargo test (86/86) all pass locally. Co-Authored-By: Claude Sonnet 5 --- .../contracts/agent_registry/src/lib.rs | 149 +++++++++++++++++- ...ired_errors_removes_expired_entries.1.json | 6 +- ..._custom_config_used_by_estimate_gas.1.json | 32 ++++ ...rt_error_emits_error_reported_event.1.json | 16 ++ ...eport_error_sets_default_expiration.1.json | 6 +- ..._emits_one_event_per_resolved_error.1.json | 48 ++++++ ...errors_failed_batch_emits_no_events.1.json | 16 ++ ...ors_resolution_code_matches_variant.1.json | 16 ++ .../set_gas_config_requires_admin_auth.1.json | 32 ++++ 9 files changed, 309 insertions(+), 12 deletions(-) diff --git a/smart-contracts/contracts/agent_registry/src/lib.rs b/smart-contracts/contracts/agent_registry/src/lib.rs index c82c05b..30a3f2c 100644 --- a/smart-contracts/contracts/agent_registry/src/lib.rs +++ b/smart-contracts/contracts/agent_registry/src/lib.rs @@ -80,6 +80,10 @@ pub const GAS_RESOLVE_ERROR_MARGINAL: u64 = 30_000; pub const GAS_SLASH_BOND: u64 = 60_000; /// Full cost of a `deregister_agent` that also returns a bond. pub const GAS_DEREGISTER_WITH_BOND: u64 = 80_000; +/// Full cost of checking/removing a single expired error (includes overhead). +pub const GAS_CLEANUP_ERROR: u64 = 20_000; +/// Marginal cost of each additional error checked in a cleanup batch. +pub const GAS_CLEANUP_ERROR_MARGINAL: u64 = 10_000; /// Default minimum bond required to register an agent, in stroops. /// 10 XLM = 100_000_000 stroops. Admin can override via `set_min_bond`. @@ -180,6 +184,8 @@ pub struct GasConfig { pub resolve_error_marginal: u64, pub slash_bond: u64, pub deregister_with_bond: u64, + pub cleanup_error: u64, + pub cleanup_error_marginal: u64, } impl GasConfig { @@ -192,6 +198,8 @@ impl GasConfig { resolve_error_marginal: GAS_RESOLVE_ERROR_MARGINAL, slash_bond: GAS_SLASH_BOND, deregister_with_bond: GAS_DEREGISTER_WITH_BOND, + cleanup_error: GAS_CLEANUP_ERROR, + cleanup_error_marginal: GAS_CLEANUP_ERROR_MARGINAL, } } } @@ -214,6 +222,8 @@ pub enum DataKey { FrozenAgent(Symbol), ErrorRecord(BytesN<32>), GasConfig, + /// Configurable TTL (in ledger sequences) applied to new error entries. + ErrorTTL, /// Minimum bond required for registration, in stroops (instance storage). MinBond, /// Ledger number at which the cooldown expires for a deregistering agent. @@ -260,6 +270,13 @@ fn gas_config(env: &Env) -> GasConfig { .unwrap_or_else(GasConfig::default_config) } +fn error_ttl(env: &Env) -> u64 { + env.storage() + .instance() + .get(&DataKey::ErrorTTL) + .unwrap_or(DEFAULT_ERROR_TTL) +} + fn get_storage_config_internal(env: &Env) -> StorageConfig { env.storage() .instance() @@ -1485,6 +1502,7 @@ impl AgentRegistryContract { let resolve_errors = String::from_str(&env, "resolve_errors"); let slash_bond_op = String::from_str(&env, "slash_bond"); let deregister_bond_op = String::from_str(&env, "deregister_with_bond"); + let cleanup_expired_errors = String::from_str(&env, "cleanup_expired_errors"); if operation == register_agent || operation == register_agents { // First item pays full single-call cost; rest pay marginal. @@ -1497,6 +1515,11 @@ impl AgentRegistryContract { + cfg .resolve_error_marginal .saturating_mul((count - 1) as u64) + } else if operation == cleanup_expired_errors { + cfg.cleanup_error + + cfg + .cleanup_error_marginal + .saturating_mul((count - 1) as u64) } else if operation == slash_bond_op { cfg.slash_bond.saturating_mul(count as u64) } else if operation == deregister_bond_op { @@ -1963,6 +1986,8 @@ mod test { resolve_error_marginal: 15_000, slash_bond: 30_000, deregister_with_bond: 40_000, + cleanup_error: 8_000, + cleanup_error_marginal: 4_000, }; // Test non-admin cannot set gas config @@ -2357,7 +2382,7 @@ mod test { fn gas_benchmark_custom_config_used_by_estimate_gas() { let (env, client, _admin) = setup_with_admin(); - // Override with custom values — all seven fields required. + // Override with custom values — all fields required. let custom = GasConfig { tx_overhead: 10_000, register_agent: 80_000, @@ -2366,6 +2391,8 @@ mod test { resolve_error_marginal: 20_000, slash_bond: GAS_SLASH_BOND, deregister_with_bond: GAS_DEREGISTER_WITH_BOND, + cleanup_error: GAS_CLEANUP_ERROR, + cleanup_error_marginal: GAS_CLEANUP_ERROR_MARGINAL, }; client.set_gas_config(&custom); @@ -3026,7 +3053,125 @@ mod test { let res = client.try_set_storage_config(&cfg); assert!(res.is_err()); } + + // ── Error TTL / expiration ─────────────────────────────────────────────── + + #[test] + fn estimate_gas_cleanup_scales_with_count() { + let (env, client) = setup(); + let one = client.estimate_gas(&String::from_str(&env, "cleanup_expired_errors"), &1); + let ten = client.estimate_gas(&String::from_str(&env, "cleanup_expired_errors"), &10); + + assert_eq!(one, GAS_CLEANUP_ERROR); + assert_eq!(ten, GAS_CLEANUP_ERROR + GAS_CLEANUP_ERROR_MARGINAL * 9); + assert!(ten < GAS_CLEANUP_ERROR * 10); + } + + #[test] + fn report_error_sets_default_expiration() { + let (env, client, _admin) = setup_with_admin(); + let reporter = Address::generate(&env); + let id = error_id(&env, 90); + + env.ledger().set_sequence_number(1_000); + client.report_error(&id, &reporter, &String::from_str(&env, "boom")); + + let entry = client.get_error(&id).unwrap(); + assert_eq!(entry.created_at, 1_000); + assert_eq!(entry.expires_at, 1_000 + DEFAULT_ERROR_TTL); + } + + #[test] + fn error_expires_after_configured_ttl() { + let (env, client, _admin) = setup_with_admin(); + let reporter = Address::generate(&env); + let id = error_id(&env, 91); + + env.ledger().set_sequence_number(500); + client.set_error_ttl(&100); + client.report_error(&id, &reporter, &String::from_str(&env, "flaky")); + + let entry = client.get_error(&id).unwrap(); + assert_eq!(entry.expires_at, 600); + + // Not yet expired: cleanup is a no-op, entry survives. + env.ledger().set_sequence_number(599); + assert_eq!( + client.cleanup_expired_errors(&Vec::from_array(&env, [id.clone()])), + 0 + ); + assert!(client.get_error(&id).is_some()); + + // At/after expiry: entry is eligible for cleanup and gets removed. + env.ledger().set_sequence_number(600); + assert_eq!( + client.cleanup_expired_errors(&Vec::from_array(&env, [id.clone()])), + 1 + ); + assert!(client.get_error(&id).is_none()); + } + + #[test] + fn cleanup_expired_errors_removes_expired_entries() { + let (env, client, _admin) = setup_with_admin(); + let reporter = Address::generate(&env); + let expired_id = error_id(&env, 92); + let live_id = error_id(&env, 93); + + env.ledger().set_sequence_number(1_000); + client.set_error_ttl(&50); + client.report_error(&expired_id, &reporter, &String::from_str(&env, "old")); + + env.ledger().set_sequence_number(1_040); + client.report_error(&live_id, &reporter, &String::from_str(&env, "new")); + + // Advance past expired_id's expiry (1050) but not live_id's (1090). + env.ledger().set_sequence_number(1_060); + + let ids = Vec::from_array(&env, [expired_id.clone(), live_id.clone()]); + let removed = client.cleanup_expired_errors(&ids); + + assert_eq!(removed, 1); + assert!(client.get_error(&expired_id).is_none()); + assert!(client.get_error(&live_id).is_some()); + } + + #[test] + fn cleanup_expired_errors_ignores_unknown_ids() { + let (env, client, _admin) = setup_with_admin(); + let ghost_id = error_id(&env, 94); + let removed = client.cleanup_expired_errors(&Vec::from_array(&env, [ghost_id])); + assert_eq!(removed, 0); + } + + #[test] + fn set_error_ttl_requires_admin_auth() { + let env = Env::default(); + let contract_id = env.register(AgentRegistryContract, ()); + let client = AgentRegistryContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + + env.mock_all_auths(); + client.initialize(&admin); + + // Non-admin cannot configure TTL. + env.mock_auths(&[]); + let result = client.try_set_error_ttl(&1_000); + assert!(result.is_err()); + + // Admin succeeds and the new value is reflected in get_error_ttl. + env.mock_all_auths(); + client.set_error_ttl(&1_000); + assert_eq!(client.get_error_ttl(), 1_000); + } + + #[test] + fn get_error_ttl_defaults_when_unset() { + let (_env, client) = setup(); + assert_eq!(client.get_error_ttl(), DEFAULT_ERROR_TTL); + } } -#[cfg(test)] +// `test_multisig.rs` gates itself internally via `#![cfg(test)]`; an outer +// `#[cfg(test)]` here would duplicate that attribute (clippy::duplicated_attributes). mod test_multisig; diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_removes_expired_entries.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_removes_expired_entries.1.json index 2846cbf..8218c10 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_removes_expired_entries.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/cleanup_expired_errors_removes_expired_entries.1.json @@ -171,11 +171,7 @@ "symbol": "resolution" }, "val": { - "vec": [ - { - "symbol": "Fixed" - } - ] + "u32": 0 } }, { diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/gas_benchmark_custom_config_used_by_estimate_gas.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/gas_benchmark_custom_config_used_by_estimate_gas.1.json index 65c0a53..3879921 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/gas_benchmark_custom_config_used_by_estimate_gas.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/gas_benchmark_custom_config_used_by_estimate_gas.1.json @@ -17,6 +17,22 @@ "args": [ { "map": [ + { + "key": { + "symbol": "cleanup_error" + }, + "val": { + "u64": 20000 + } + }, + { + "key": { + "symbol": "cleanup_error_marginal" + }, + "val": { + "u64": 10000 + } + }, { "key": { "symbol": "deregister_with_bond" @@ -143,6 +159,22 @@ }, "val": { "map": [ + { + "key": { + "symbol": "cleanup_error" + }, + "val": { + "u64": 20000 + } + }, + { + "key": { + "symbol": "cleanup_error_marginal" + }, + "val": { + "u64": 10000 + } + }, { "key": { "symbol": "deregister_with_bond" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_emits_error_reported_event.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_emits_error_reported_event.1.json index 28b746d..c0d93fc 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_emits_error_reported_event.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_emits_error_reported_event.1.json @@ -78,6 +78,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_sets_default_expiration.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_sets_default_expiration.1.json index ca31a3c..d425092 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_sets_default_expiration.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/report_error_sets_default_expiration.1.json @@ -125,11 +125,7 @@ "symbol": "resolution" }, "val": { - "vec": [ - { - "symbol": "Fixed" - } - ] + "u32": 0 } }, { diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_emits_one_event_per_resolved_error.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_emits_one_event_per_resolved_error.1.json index 050e50a..864f417 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_emits_one_event_per_resolved_error.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_emits_one_event_per_resolved_error.1.json @@ -161,6 +161,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" @@ -247,6 +263,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" @@ -333,6 +365,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_failed_batch_emits_no_events.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_failed_batch_emits_no_events.1.json index a1557fd..d41f2c6 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_failed_batch_emits_no_events.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_failed_batch_emits_no_events.1.json @@ -108,6 +108,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_resolution_code_matches_variant.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_resolution_code_matches_variant.1.json index 6ae11e4..a2cd00b 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_resolution_code_matches_variant.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/resolve_errors_resolution_code_matches_variant.1.json @@ -105,6 +105,22 @@ "durability": "persistent", "val": { "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": 0 + } + }, + { + "key": { + "symbol": "expires_at" + }, + "val": { + "u64": 518400 + } + }, { "key": { "symbol": "id" diff --git a/smart-contracts/contracts/agent_registry/test_snapshots/test/set_gas_config_requires_admin_auth.1.json b/smart-contracts/contracts/agent_registry/test_snapshots/test/set_gas_config_requires_admin_auth.1.json index ab13dfd..847c74f 100644 --- a/smart-contracts/contracts/agent_registry/test_snapshots/test/set_gas_config_requires_admin_auth.1.json +++ b/smart-contracts/contracts/agent_registry/test_snapshots/test/set_gas_config_requires_admin_auth.1.json @@ -18,6 +18,22 @@ "args": [ { "map": [ + { + "key": { + "symbol": "cleanup_error" + }, + "val": { + "u64": 8000 + } + }, + { + "key": { + "symbol": "cleanup_error_marginal" + }, + "val": { + "u64": 4000 + } + }, { "key": { "symbol": "deregister_with_bond" @@ -140,6 +156,22 @@ }, "val": { "map": [ + { + "key": { + "symbol": "cleanup_error" + }, + "val": { + "u64": 8000 + } + }, + { + "key": { + "symbol": "cleanup_error_marginal" + }, + "val": { + "u64": 4000 + } + }, { "key": { "symbol": "deregister_with_bond"