diff --git a/.gitignore b/.gitignore index e6b57103..85ebe4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,11 @@ target *.rs.bk .DS_Store .env +temp_auto_push.bat .env.* /target/ /target_local/ .md-* hidden +.gitignore +branch_structure.json diff --git a/contracts/registry/src/admin.rs b/contracts/registry/src/admin.rs index 00040862..e606bf72 100644 --- a/contracts/registry/src/admin.rs +++ b/contracts/registry/src/admin.rs @@ -1,31 +1,41 @@ //! Admin cooldown enforcement for the Callora registry. -//! -//! Implements a per-developer cool-off window between registrations to prevent -//! rapid abuse by the same developer while allowing different developers to -//! register concurrently. Every admin-gated entrypoint in -//! [`crate::CalloraRegistry`] must call [`require_cooldown`] before -//! mutating state and [`update_cooldown`] after a successful mutation. -//! -//! # Storage -//! -//! The last-registration timestamp for each developer is stored in persistent -//! storage under [`crate::StorageKey::DeveloperCooldown`]`(developer)`. -//! There is exactly one authoritative key per developer; the former global -//! `Symbol`-based key has been removed. +/// +/// Implements a per-developer cool-off window between registrations to prevent +/// rapid abuse by the same developer while allowing different developers to +/// register concurrently. Every admin-gated entrypoint in +/// [`crate::CalloraRegistry`] must call [[`require_cooldown`] before +/// mutating state and [[`update_cooldown`] after a successful mutation. +/// +/// # Storage +/// +/// The last-registration timestamp for each developer is stored in persistent +/// storage under [`crate::StorageKey::DeveloperCooldown`]`(developer)`. +/// There is exactly one authoritative key per developer; the former global +/// `Symbol`-based key has been removed. +/// +/// # TTL +/// +/// Cooldown markers are extended on every write and on every successful read +/// so they cannot archive while the cooldown window is still effective. If a +/// marker did archive, the next registration would either fail until the entry +/// is restored or silently skip the cooldown after expiry. use soroban_sdk::{Address, Env}; -use crate::{RegistryError, StorageKey}; +use crate::{RegistryError, StorageKey, COOLDOWN_TTL_EXTENSION, COOLDOWN_TTL_THRESHOLD}; /// Cooldown window in seconds between registrations by the same developer. /// /// Set to 3 600 (1 hour): the same developer must wait at least this long /// after one registration before performing another one. Different developers /// are unaffected by each other's cooldown windows. -pub const COOLDOWN_SECONDS: u64 = 3_600; +pub const COOLDOWN_SECONDS: u64 = 3,600; /// Return the ledger timestamp of the last registration by `developer`, or /// `None` if this developer has not yet registered anything. +/// +/// This is a pure read helper and does not extend the TTL. Callers +/// (`require_cooldown`) handle the extension so the marker stays live. pub fn last_developer_action(env: &Env, developer: &Address) -> Option { env.storage() .persistent() @@ -35,11 +45,21 @@ pub fn last_developer_action(env: &Env, developer: &Address) -> Option { /// Assert that the cooldown window has elapsed since `developer`'s last /// registration. /// -/// Returns `Err(RegistryError::AdminCooldownActive)` if the cooldown has not +/// Returns `Err(RegistryError::AdminCooldownActive)if the cooldown has not /// expired for this developer. This is a no-op when the developer has no prior /// registration, so every developer's first action always succeeds. +/// +/// When a marker is present, its TT\ is extended on read so the cooldown +/// state cannot archive out from under an active window. pub fn require_cooldown(env: &Env, developer: &Address) -> Result<(), RegistryError> { - if let Some(last) = last_developer_action(env, developer) { + let key = StorageKey::DeveloperCooldown(developer.clone()); + if let Some(last) = env.storage().persistent().get::(&key) { + // Extend on read so a live marker never archives between registrations. + env.storage().persistent().extend_ttl( + &key, + COOLDOWN_TTL_THRESHOLD, + COOLDOWN_TTL_EXTENSION, + ); let now = env.ledger().timestamp(); let elapsed = now.saturating_sub(last); if elapsed < COOLDOWN_SECONDS { @@ -54,9 +74,16 @@ pub fn require_cooldown(env: &Env, developer: &Address) -> Result<(), RegistryEr /// Must be called after every successful registration so that subsequent /// registrations by the same developer are subject to the cooldown window. /// Registrations by other developers are not affected. +/// +/// The marker TTL is extended on write using a lifetime at least as long as +/// the cooldown window so the entry cannot archive while it is still relevant. pub fn update_cooldown(env: &Env, developer: &Address) { let now = env.ledger().timestamp(); - env.storage() - .persistent() - .set(&StorageKey::DeveloperCooldown(developer.clone()), &now); + let key = StorageKey::DeveloperCooldown(developer.clone()); + env.storage().persistent().set(&key, &ow); + env.storage().persistent().extend_ttl( + @key, + COOLDOWN_TTL_THRESHOLD, + COOLDOWN_TTL_EXTENSION, + ); } diff --git a/contracts/registry/src/lib.rs b/contracts/registry/src/lib.rs index 412919df..e398082d 100644 --- a/contracts/registry/src/lib.rs +++ b/contracts/registry/src/lib.rs @@ -144,22 +144,20 @@ impl CalloraRegistry { /// Shared registration core used by both public entrypoints. /// - /// Validates inputs, checks for duplicates, publishes to the catalog, then - /// atomically writes the offering record, increments the registered count, - /// emits the registration event, and records the developer cooldown timestamp. + /// Checks for duplicates, publishes to the catalog, then atomically writes + /// the offering record, increments the registered count, emits the + /// registration event, and records the developer cooldown timestamp. /// - /// Callers are responsible for authentication, admin-equality checks, the - /// cooldown gate, and any pre-conditions specific to their variant (e.g. - /// the developer balance gate in `register_offering_with_gate`). + /// Callers are responsible for authentication, admin-equality checks, input + /// validation, the cooldown gate, and any pre-conditions specific to their + /// variant (e.g. the developer balance gate in + /// `register_offering_with_gate`). fn do_register( env: &Env, developer: Address, offering_id: String, metadata: String, ) -> Result<(), RegistryError> { - Self::validate_offering_id(&offering_id)?; - Self::validate_metadata(&metadata)?; - let key = StorageKey::Offering(offering_id.clone()); if env.storage().persistent().has(&key) { return Err(RegistryError::OfferingAlreadyRegistered); @@ -202,6 +200,11 @@ impl CalloraRegistry { /// /// Cross-contract interactions happen before any registry persistent write, /// so a reverting or panicking catalog leaves registry storage unchanged. + /// + /// Error precedence: authentication, then admin equality, then input + /// validation (`offering_id` and `metadata`), then the per-developer + /// cooldown, then the duplicate check. No state is written on any failure + /// path. pub fn register_offering( env: Env, caller: Address, @@ -215,6 +218,8 @@ impl CalloraRegistry { if caller != admin { return Err(RegistryError::Unauthorized); } + Self::validate_offering_id(&offering_id)?; + Self::validate_metadata(&metadata)?; admin::require_cooldown(&env, &developer)?; Self::do_register(&env, developer, offering_id, metadata) } @@ -224,6 +229,11 @@ impl CalloraRegistry { /// /// The balance gate runs before the shared registration logic so that an /// insufficient balance is rejected before any catalog call or state write. + /// + /// Error precedence: authentication, then admin equality, then input + /// validation (`offering_id` and `metadata`), then the per-developer + /// cooldown, then the balance gate, then the duplicate check. No state is + /// written on any failure path. pub fn register_offering_with_gate( env: Env, caller: Address, @@ -239,6 +249,8 @@ impl CalloraRegistry { if caller != admin { return Err(RegistryError::Unauthorized); } + Self::validate_offering_id(&offering_id)?; + Self::validate_metadata(&metadata)?; admin::require_cooldown(&env, &developer)?; // Balance gate: reject before any catalog interaction or state write. @@ -252,9 +264,15 @@ impl CalloraRegistry { } /// Return whether `offering_id` has been registered. + /// + /// This is a total predicate: a malformed `offering_id` can never be + /// registered, so it returns `Ok(false)` rather than an error. Use + /// [`Self::get_offering`] when the caller needs `InvalidOfferingId`. pub fn is_offering_registered(env: Env, offering_id: String) -> Result { Self::extend_instance_ttl(&env); - Self::validate_offering_id(&offering_id)?; + if Self::validate_offering_id(&offering_id).is_err() { + return Ok(false); + } if Self::admin(&env).is_err() { return Err(RegistryError::NotInitialized); } diff --git a/contracts/registry/tests/metadata_validation.rs b/contracts/registry/tests/metadata_validation.rs index 5c910e67..b67512ea 100644 --- a/contracts/registry/tests/metadata_validation.rs +++ b/contracts/registry/tests/metadata_validation.rs @@ -458,7 +458,6 @@ fn rejected_metadata_leaves_no_partial_state_gated() { // --------------------------------------------------------------------------- #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_control_character_in_offering_id() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); @@ -476,7 +475,6 @@ fn rejects_control_character_in_offering_id() { } #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_line_break_in_offering_id() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); @@ -489,7 +487,6 @@ fn rejects_line_break_in_offering_id() { } #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_non_ascii_multibyte_offering_id() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); @@ -502,7 +499,6 @@ fn rejects_non_ascii_multibyte_offering_id() { } #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_leading_whitespace_in_offering_id() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); @@ -515,7 +511,6 @@ fn rejects_leading_whitespace_in_offering_id() { } #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_trailing_whitespace_in_offering_id() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); @@ -528,7 +523,6 @@ fn rejects_trailing_whitespace_in_offering_id() { } #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_whitespace_only_offering_id() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); @@ -541,7 +535,6 @@ fn rejects_whitespace_only_offering_id() { } #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_empty_offering_id() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); @@ -554,7 +547,6 @@ fn rejects_empty_offering_id() { } #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_over_length_offering_id() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); @@ -568,7 +560,6 @@ fn rejects_over_length_offering_id() { } #[test] -#[ignore = "stale test: fails against current contract; see docs/KNOWN_ISSUES.md"] fn rejects_invalid_offering_id_in_with_gate_variant() { let env = Env::default(); let (admin, client, developer) = setup_registry(&env); diff --git a/contracts/stake/tests/auth_snap.rs b/contracts/stake/tests/auth_snap.rs index 99a3ad6e..9aae265b 100644 --- a/contracts/stake/tests/auth_snap.rs +++ b/contracts/stake/tests/auth_snap.rs @@ -1,5 +1,4 @@ #![cfg(test)] - extern crate std; use callora_stake::{CalloraStake, CalloraStakeClient, SUPPORTED_CAPABILITIES}; diff --git a/public/fonts/README.md b/public/fonts/README.md new file mode 100644 index 00000000..1e4f9ba1 --- /dev/null +++ b/public/fonts/README.md @@ -0,0 +1,24 @@ +# Fonts Directory + +This directory contains custom fonts for the Blockchain Explorer application. + +## Required Font Files + +The application expects the following font files: + +1. **BlockchainFont-Regular.woff2** and **BlockchainFont-Regular.woff** + - Regular weight font for the main UI + +2. **BlockchainFont-Bold.woff2** and **BlockchainFont-Bold.woff** + - Bold weight font for headings + +3. **TechMono-Regular.woff2** and **TechMono-Regular.woff** + - Monospace font for code and hash displays + +## Note + +If you don't have custom fonts, the application will fall back to system fonts: +- BlockchainFont → system sans-serif fonts +- TechMono → system monospace fonts (Courier New, etc.) + +The fonts are referenced in `public/index.html` and will be loaded automatically when available. diff --git a/public/fonts/fa-brands-400.eot b/public/fonts/fa-brands-400.eot new file mode 100644 index 00000000..a1bc094a Binary files /dev/null and b/public/fonts/fa-brands-400.eot differ diff --git a/public/fonts/fa-brands-400.svg b/public/fonts/fa-brands-400.svg new file mode 100644 index 00000000..46ad237a --- /dev/null +++ b/public/fonts/fa-brands-400.svg @@ -0,0 +1,3570 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-brands-400.ttf b/public/fonts/fa-brands-400.ttf new file mode 100644 index 00000000..948a2a6c Binary files /dev/null and b/public/fonts/fa-brands-400.ttf differ diff --git a/public/fonts/fa-brands-400.woff b/public/fonts/fa-brands-400.woff new file mode 100644 index 00000000..2a89d521 Binary files /dev/null and b/public/fonts/fa-brands-400.woff differ diff --git a/public/fonts/fa-brands-400.woff2 b/public/fonts/fa-brands-400.woff2 new file mode 100644 index 00000000..141a90a9 Binary files /dev/null and b/public/fonts/fa-brands-400.woff2 differ diff --git a/public/fonts/fa-regular-400.eot b/public/fonts/fa-regular-400.eot new file mode 100644 index 00000000..38cf2517 Binary files /dev/null and b/public/fonts/fa-regular-400.eot differ diff --git a/public/fonts/fa-regular-400.svg b/public/fonts/fa-regular-400.svg new file mode 100644 index 00000000..48634a9a --- /dev/null +++ b/public/fonts/fa-regular-400.svg @@ -0,0 +1,803 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-regular-400.ttf b/public/fonts/fa-regular-400.ttf new file mode 100644 index 00000000..abe99e20 Binary files /dev/null and b/public/fonts/fa-regular-400.ttf differ diff --git a/public/fonts/fa-regular-400.woff b/public/fonts/fa-regular-400.woff new file mode 100644 index 00000000..24de566a Binary files /dev/null and b/public/fonts/fa-regular-400.woff differ diff --git a/public/fonts/fa-regular-400.woff2 b/public/fonts/fa-regular-400.woff2 new file mode 100644 index 00000000..7e0118e5 Binary files /dev/null and b/public/fonts/fa-regular-400.woff2 differ diff --git a/public/fonts/fa-solid-900.eot b/public/fonts/fa-solid-900.eot new file mode 100644 index 00000000..d3b77c22 Binary files /dev/null and b/public/fonts/fa-solid-900.eot differ diff --git a/public/fonts/fa-solid-900.svg b/public/fonts/fa-solid-900.svg new file mode 100644 index 00000000..7742838b --- /dev/null +++ b/public/fonts/fa-solid-900.svg @@ -0,0 +1,4938 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-solid-900.ttf b/public/fonts/fa-solid-900.ttf new file mode 100644 index 00000000..5b979039 Binary files /dev/null and b/public/fonts/fa-solid-900.ttf differ diff --git a/public/fonts/fa-solid-900.woff b/public/fonts/fa-solid-900.woff new file mode 100644 index 00000000..beec7917 Binary files /dev/null and b/public/fonts/fa-solid-900.woff differ diff --git a/public/fonts/fa-solid-900.woff2 b/public/fonts/fa-solid-900.woff2 new file mode 100644 index 00000000..978a681a Binary files /dev/null and b/public/fonts/fa-solid-900.woff2 differ