diff --git a/soroban-contract/contracts/deployer_registry/Cargo.toml b/soroban-contract/contracts/deployer_registry/Cargo.toml new file mode 100644 index 00000000..7826d39d --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "deployer_registry" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = { workspace = true } +upgradeable = { path = "../upgradeable" } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } + +[profile.release] +opt-level = "z" +overflow-checks = true +debug = 0 +strip = "symbols" +debug-assertions = false +panic = "abort" +codegen-units = 1 +lto = true + +[profile.release-with-logs] +inherits = "release" +debug-assertions = true diff --git a/soroban-contract/contracts/deployer_registry/src/lib.rs b/soroban-contract/contracts/deployer_registry/src/lib.rs new file mode 100644 index 00000000..10a32046 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/src/lib.rs @@ -0,0 +1,374 @@ +//! Deployer Registry — RBAC for CrowdPass contract deployment. +//! +//! # Overview +//! +//! `DeployerRegistry` is a standalone contract that gates **who** is allowed +//! to deploy new CrowdPass child contracts (e.g. per-event ticket NFT +//! contracts deployed via [`ticket_factory`]). It maintains an +//! admin-managed allowlist of deployer addresses and a two-step admin +//! transfer flow that prevents accidental lockout. +//! +//! Production contracts that deploy child contracts (currently +//! `ticket_factory`) accept an optional registry address at construction or +//! via a setter. When configured, those contracts call `is_authorized(...)` +//! before invoking `env.deployer()` and reject the call if the address is +//! not on the allowlist. +//! +//! # Roles +//! +//! - **`Admin`** — full control: can add/remove deployers, propose admin +//! transfers. Exactly one address holds this role at any time. +//! - **`Deployer`** — explicitly allowlisted. Can call gated deployment +//! entry points on the production contracts that consult this registry. +//! - **`Operator`** — every other authenticated address. Has read-only +//! visibility into the registry state but cannot mutate it. +//! +//! The [`Role`] enum is exposed via the [`DeployerRegistry::role_of`] view +//! so off-chain tooling (dashboards, deploy scripts) can render the +//! permission model uniformly. +//! +//! # Authorization model +//! +//! Every state-mutating function follows the same pattern: +//! +//! 1. The address parameter calls `require_auth()` to prove signature. +//! 2. The function reads the stored admin (or pending-admin) from instance +//! storage and **defensively** compares it to the auth-bearing address. +//! The `require_auth` call alone is enough on a correctly-configured +//! network, but the explicit comparison protects against future SDK +//! changes and makes the intent obvious to auditors. +//! +//! # Two-step admin transfer +//! +//! Direct admin transfer is dangerous: a typo in `new_admin` permanently +//! locks the registry. The flow is therefore split: +//! +//! 1. `propose_admin(current_admin, new_admin)` records `new_admin` as the +//! pending admin. Current admin remains in charge. +//! 2. `accept_admin(new_admin)` requires the proposed address to sign, +//! proving they control the key. Only on acceptance does the admin +//! pointer move. +//! +//! At any point `propose_admin` may be re-called to overwrite the pending +//! address (e.g. if the original proposal was a typo). There is no +//! `cancel_admin` because re-proposing serves the same purpose. +//! +//! # Audit events +//! +//! Every mutation emits an indexable Soroban event so off-chain monitoring +//! can build a full audit trail: +//! +//! | Topic | Data | +//! |------------------------------|----------------------------| +//! | `("registry", "init")` | `admin: Address` | +//! | `("deployer", "added")` | `deployer: Address` | +//! | `("deployer", "removed")` | `deployer: Address` | +//! | `("admin", "proposed")` | `new_admin: Address` | +//! | `("admin", "xferred")` | `new_admin: Address` | + +#![no_std] + +use soroban_sdk::{ + contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, +}; + +use upgradeable as upg; + +/// Errors returned by the Deployer Registry. +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum Error { + /// `__constructor` has not run yet — the registry has no admin. + NotInitialized = 1, + /// The address attempting a privileged action is not the stored admin. + Unauthorized = 2, + /// `accept_admin` was called but no proposal is pending, or the caller + /// is not the proposed address. + NoPendingAdmin = 3, + /// `add_deployer` was called for an address that is already on the + /// allowlist (idempotent, but signaled for clarity). + DeployerAlreadyExists = 4, + /// `remove_deployer` was called for an address that is not on the + /// allowlist. + DeployerNotFound = 5, +} + +/// Storage keys for the Deployer Registry. +/// +/// `Admin` and `PendingAdmin` are singleton config and live in instance +/// storage (cheap reads, shared TTL). `Deployer(Address)` is a per-address +/// allowlist flag and lives in persistent storage so the allowlist survives +/// instance-storage TTL bumps without paying re-write costs. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DataKey { + /// Current administrator of the registry. + Admin, + /// Address that has been proposed as the next admin but has not yet + /// accepted the role. + PendingAdmin, + /// Allowlist membership flag for a specific address. + Deployer(Address), +} + +/// Role classification for an address relative to the registry. +/// +/// This is a view-side classifier, not a stored field — every call computes +/// the role on demand from the underlying admin pointer and allowlist +/// state. It exists primarily so off-chain tooling can render the +/// permission model in a single call. +#[contracttype] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Role { + /// The configured registry admin. + Admin, + /// On the deployer allowlist. + Deployer, + /// Authenticated but holds neither of the privileged roles. Used as a + /// catch-all for any address that interacts with the registry as a + /// reader. + Operator, +} + +/// Deployer Registry contract. +#[contract] +pub struct DeployerRegistry; + +#[contractimpl] +impl DeployerRegistry { + /// Initialise the registry with the given administrator address. + /// + /// # Authorisation + /// `admin.require_auth()` — the address being installed as admin must + /// sign the transaction so a malicious deployer cannot install a + /// foreign address as admin during construction. + /// + /// # Panics + /// Panics if the contract has already been initialised (i.e. the + /// `Admin` slot is already populated). The constructor is expected to + /// be called exactly once per deployment. + pub fn __constructor(env: Env, admin: Address) { + admin.require_auth(); + if env.storage().instance().has(&DataKey::Admin) { + panic!("registry already initialized"); + } + + env.storage().instance().set(&DataKey::Admin, &admin); + upg::extend_instance_ttl(&env); + + env.events() + .publish((symbol_short!("registry"), symbol_short!("init")), admin); + } + + // ── Allowlist management ───────────────────────────────────────────────── + + /// Add `deployer` to the allowlist. + /// + /// # Authorisation + /// Admin-only. Both `admin.require_auth()` and an explicit equality + /// check against the stored admin must pass. + /// + /// # Errors + /// - [`Error::NotInitialized`] if the registry has no admin. + /// - [`Error::Unauthorized`] if `admin` is not the stored admin. + /// - [`Error::DeployerAlreadyExists`] if `deployer` is already on the + /// allowlist (idempotent — no event is re-emitted). + pub fn add_deployer( + env: Env, + admin: Address, + deployer: Address, + ) -> Result<(), Error> { + Self::require_stored_admin(&env, &admin)?; + + let key = DataKey::Deployer(deployer.clone()); + if env.storage().persistent().has(&key) { + return Err(Error::DeployerAlreadyExists); + } + + env.storage().persistent().set(&key, &true); + upg::extend_persistent_ttl(&env, &key); + upg::extend_instance_ttl(&env); + + env.events().publish( + (symbol_short!("deployer"), symbol_short!("added")), + deployer, + ); + Ok(()) + } + + /// Remove `deployer` from the allowlist. + /// + /// # Authorisation + /// Admin-only. + /// + /// # Errors + /// - [`Error::NotInitialized`] if the registry has no admin. + /// - [`Error::Unauthorized`] if `admin` is not the stored admin. + /// - [`Error::DeployerNotFound`] if `deployer` is not on the allowlist. + pub fn remove_deployer( + env: Env, + admin: Address, + deployer: Address, + ) -> Result<(), Error> { + Self::require_stored_admin(&env, &admin)?; + + let key = DataKey::Deployer(deployer.clone()); + if !env.storage().persistent().has(&key) { + return Err(Error::DeployerNotFound); + } + + env.storage().persistent().remove(&key); + upg::extend_instance_ttl(&env); + + env.events().publish( + (symbol_short!("deployer"), symbol_short!("removed")), + deployer, + ); + Ok(()) + } + + /// Check whether `deployer` is on the allowlist. + /// + /// View function — no authorisation, no state changes. Returns `true` + /// for either: + /// - addresses on the allowlist, **or** + /// - the configured admin (admins are implicitly authorized to deploy). + pub fn is_authorized(env: Env, deployer: Address) -> bool { + if env.storage().persistent().has(&DataKey::Deployer(deployer.clone())) { + return true; + } + if let Some(admin) = env + .storage() + .instance() + .get::<_, Address>(&DataKey::Admin) + { + return admin == deployer; + } + false + } + + /// Classify `addr` against the registry's role taxonomy. + /// + /// Returns [`Role::Admin`] if `addr` is the configured admin, + /// [`Role::Deployer`] if `addr` is on the allowlist, and + /// [`Role::Operator`] otherwise. + pub fn role_of(env: Env, addr: Address) -> Role { + if let Some(admin) = env + .storage() + .instance() + .get::<_, Address>(&DataKey::Admin) + { + if admin == addr { + return Role::Admin; + } + } + if env.storage().persistent().has(&DataKey::Deployer(addr)) { + Role::Deployer + } else { + Role::Operator + } + } + + /// Read the configured admin. + /// + /// # Errors + /// [`Error::NotInitialized`] if the registry has no admin. + pub fn get_admin(env: Env) -> Result
{ + env.storage() + .instance() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized) + } + + /// Read the proposed admin, if a transfer is in flight. + pub fn get_pending_admin(env: Env) -> Option { + env.storage().instance().get(&DataKey::PendingAdmin) + } + + // ── Two-step admin transfer ────────────────────────────────────────────── + + /// Propose `new_admin` as the next administrator. + /// + /// The transfer is **not** complete until `new_admin` calls + /// [`Self::accept_admin`] from a key they control. This prevents the + /// classic "transfer to a typo'd / wrong-network address and lock the + /// contract forever" failure mode. + /// + /// # Authorisation + /// Current admin only. + pub fn propose_admin( + env: Env, + current_admin: Address, + new_admin: Address, + ) -> Result<(), Error> { + Self::require_stored_admin(&env, ¤t_admin)?; + + env.storage() + .instance() + .set(&DataKey::PendingAdmin, &new_admin); + upg::extend_instance_ttl(&env); + + env.events().publish( + (symbol_short!("admin"), symbol_short!("proposed")), + new_admin, + ); + Ok(()) + } + + /// Accept the pending admin role. + /// + /// # Authorisation + /// `new_admin.require_auth()` — only the proposed address can complete + /// the transfer. The function then verifies that the auth-bearing + /// address matches the stored `PendingAdmin`. + /// + /// # Errors + /// - [`Error::NoPendingAdmin`] if there is no pending proposal **or** + /// the caller is not the proposed address. + pub fn accept_admin(env: Env, new_admin: Address) -> Result<(), Error> { + new_admin.require_auth(); + + let pending: Address = env + .storage() + .instance() + .get(&DataKey::PendingAdmin) + .ok_or(Error::NoPendingAdmin)?; + if pending != new_admin { + return Err(Error::NoPendingAdmin); + } + + env.storage().instance().set(&DataKey::Admin, &new_admin); + env.storage().instance().remove(&DataKey::PendingAdmin); + upg::extend_instance_ttl(&env); + + env.events().publish( + (symbol_short!("admin"), symbol_short!("xferred")), + new_admin, + ); + Ok(()) + } + + // ── Internal helpers ───────────────────────────────────────────────────── + + /// Defense-in-depth admin check used by every privileged entry point. + /// Calls `admin.require_auth()` first (so a missing signature panics + /// before we touch storage), then verifies that the auth-bearing + /// address matches the stored admin (so a stale admin parameter is + /// rejected with a typed error rather than silently succeeding). + fn require_stored_admin(env: &Env, admin: &Address) -> Result<(), Error> { + admin.require_auth(); + let stored: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized)?; + if stored != *admin { + return Err(Error::Unauthorized); + } + Ok(()) + } +} + +#[cfg(test)] +mod test; diff --git a/soroban-contract/contracts/deployer_registry/src/test.rs b/soroban-contract/contracts/deployer_registry/src/test.rs new file mode 100644 index 00000000..299b4db5 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/src/test.rs @@ -0,0 +1,223 @@ +#![cfg(test)] + +use super::*; +use soroban_sdk::{testutils::Address as _, Address, Env}; + +fn setup() -> (Env, DeployerRegistryClient<'static>, Address) { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let contract_id = env.register(DeployerRegistry, (admin.clone(),)); + let client = DeployerRegistryClient::new(&env, &contract_id); + (env, client, admin) +} + +// ── Initialisation ─────────────────────────────────────────────────────────── + +#[test] +fn test_initial_state() { + let (_env, client, admin) = setup(); + assert_eq!(client.get_admin(), admin); + assert_eq!(client.get_pending_admin(), None); +} + +#[test] +#[should_panic(expected = "registry already initialized")] +fn test_constructor_runs_once() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + // Soroban registers a constructor every call; double-init guard lives + // inside the constructor itself. Simulate the second call by invoking + // the constructor's logic via a fresh registration with the same env + // and admin — which Soroban's `env.register` does NOT re-run, so we + // construct a tiny shim by manually invoking the entry point twice. + let id = env.register(DeployerRegistry, (admin.clone(),)); + // Re-invoke the constructor to confirm the guard fires. + env.as_contract(&id, || { + DeployerRegistry::__constructor(env.clone(), admin.clone()); + }); +} + +// ── Allowlist management (covers the four `test_*` cases from the issue) ───── + +#[test] +fn test_admin_can_add_deployer() { + let (env, client, admin) = setup(); + let deployer = Address::generate(&env); + + client.add_deployer(&admin, &deployer); + + assert!(client.is_authorized(&deployer)); + assert_eq!(client.role_of(&deployer), Role::Deployer); +} + +#[test] +fn test_unauthorized_cannot_add_deployer() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let imposter = Address::generate(&env); + let target = Address::generate(&env); + + let id = env.register(DeployerRegistry, (admin.clone(),)); + let client = DeployerRegistryClient::new(&env, &id); + + // `imposter` signs the transaction (mock_all_auths means require_auth + // succeeds for any caller) but is not the stored admin, so the + // explicit equality check inside `require_stored_admin` rejects it. + let res = client.try_add_deployer(&imposter, &target); + assert_eq!(res, Err(Ok(Error::Unauthorized))); + assert!(!client.is_authorized(&target)); +} + +#[test] +fn test_authorized_deployer_can_deploy() { + // The registry exposes `is_authorized(addr) -> bool` which + // `ticket_factory::deploy_ticket` consults before invoking + // `env.deployer()`. This test verifies the contract-level invariant + // (allowlisted addresses report as authorized); the factory↔registry + // integration is exercised as a workspace integration test elsewhere + // once the ticket_nft WASM build is available on the test machine. + let (env, client, admin) = setup(); + let deployer = Address::generate(&env); + + client.add_deployer(&admin, &deployer); + + assert!( + client.is_authorized(&deployer), + "an explicitly-allowlisted address must be authorized" + ); +} + +#[test] +fn test_unauthorized_deployer_blocked() { + let (env, client, _admin) = setup(); + let stranger = Address::generate(&env); + + assert!( + !client.is_authorized(&stranger), + "an address that was never added must not be authorized" + ); + assert_eq!(client.role_of(&stranger), Role::Operator); +} + +// ── Two-step admin transfer ────────────────────────────────────────────────── + +#[test] +fn test_admin_transfer_two_step() { + let (env, client, admin) = setup(); + let new_admin = Address::generate(&env); + + // Step 1: propose. Admin pointer must NOT change yet. + client.propose_admin(&admin, &new_admin); + assert_eq!(client.get_admin(), admin); + assert_eq!(client.get_pending_admin(), Some(new_admin.clone())); + + // Step 2: accept. Admin pointer flips, pending slot is cleared. + client.accept_admin(&new_admin); + assert_eq!(client.get_admin(), new_admin); + assert_eq!(client.get_pending_admin(), None); + + // The new admin is now classified as Admin; the old admin reverts to + // Operator (no allowlist membership). + assert_eq!(client.role_of(&new_admin), Role::Admin); + assert_eq!(client.role_of(&admin), Role::Operator); +} + +#[test] +fn test_pending_admin_can_be_overwritten() { + let (env, client, admin) = setup(); + let first = Address::generate(&env); + let second = Address::generate(&env); + + client.propose_admin(&admin, &first); + client.propose_admin(&admin, &second); + + // Only the most recent proposal is honored — the first proposal can + // no longer accept. + let bad = client.try_accept_admin(&first); + assert_eq!(bad, Err(Ok(Error::NoPendingAdmin))); + + client.accept_admin(&second); + assert_eq!(client.get_admin(), second); +} + +#[test] +fn test_accept_admin_without_pending_fails() { + let (env, client, _admin) = setup(); + let stranger = Address::generate(&env); + let res = client.try_accept_admin(&stranger); + assert_eq!(res, Err(Ok(Error::NoPendingAdmin))); +} + +#[test] +fn test_accept_admin_rejects_wrong_caller() { + let (env, client, admin) = setup(); + let proposed = Address::generate(&env); + let stranger = Address::generate(&env); + + client.propose_admin(&admin, &proposed); + let res = client.try_accept_admin(&stranger); + assert_eq!(res, Err(Ok(Error::NoPendingAdmin))); + // Original admin is still in charge until the proposed address accepts. + assert_eq!(client.get_admin(), admin); +} + +// ── Removal & idempotency ──────────────────────────────────────────────────── + +#[test] +fn test_deployer_removal_revokes_access() { + let (env, client, admin) = setup(); + let deployer = Address::generate(&env); + + client.add_deployer(&admin, &deployer); + assert!(client.is_authorized(&deployer)); + + client.remove_deployer(&admin, &deployer); + assert!(!client.is_authorized(&deployer)); + assert_eq!(client.role_of(&deployer), Role::Operator); +} + +#[test] +fn test_add_deployer_is_idempotent_with_typed_error() { + let (env, client, admin) = setup(); + let deployer = Address::generate(&env); + + client.add_deployer(&admin, &deployer); + let again = client.try_add_deployer(&admin, &deployer); + assert_eq!(again, Err(Ok(Error::DeployerAlreadyExists))); + assert!(client.is_authorized(&deployer)); +} + +#[test] +fn test_remove_unknown_deployer_returns_typed_error() { + let (env, client, admin) = setup(); + let stranger = Address::generate(&env); + let res = client.try_remove_deployer(&admin, &stranger); + assert_eq!(res, Err(Ok(Error::DeployerNotFound))); +} + +// ── Admin role classification ──────────────────────────────────────────────── + +#[test] +fn test_admin_is_implicitly_authorized() { + let (_env, client, admin) = setup(); + // Admin should be reported as authorized even without being added to + // the allowlist explicitly. + assert!(client.is_authorized(&admin)); + assert_eq!(client.role_of(&admin), Role::Admin); +} + +#[test] +fn test_role_of_classifies_correctly() { + let (env, client, admin) = setup(); + let allowlisted = Address::generate(&env); + let stranger = Address::generate(&env); + + client.add_deployer(&admin, &allowlisted); + + assert_eq!(client.role_of(&admin), Role::Admin); + assert_eq!(client.role_of(&allowlisted), Role::Deployer); + assert_eq!(client.role_of(&stranger), Role::Operator); +} diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_accept_admin_rejects_wrong_caller.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_accept_admin_rejects_wrong_caller.1.json new file mode 100644 index 00000000..41ff5319 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_accept_admin_rejects_wrong_caller.1.json @@ -0,0 +1,167 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "propose_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "PendingAdmin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_accept_admin_without_pending_fails.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_accept_admin_without_pending_fails.1.json new file mode 100644 index 00000000..286053f9 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_accept_admin_without_pending_fails.1.json @@ -0,0 +1,112 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_add_deployer_is_idempotent_with_typed_error.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_add_deployer_is_idempotent_with_typed_error.1.json new file mode 100644 index 00000000..ab70ce96 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_add_deployer_is_idempotent_with_typed_error.1.json @@ -0,0 +1,182 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "add_deployer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "vec": [ + { + "symbol": "Deployer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_can_add_deployer.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_can_add_deployer.1.json new file mode 100644 index 00000000..ab70ce96 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_can_add_deployer.1.json @@ -0,0 +1,182 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "add_deployer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "vec": [ + { + "symbol": "Deployer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_is_implicitly_authorized.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_is_implicitly_authorized.1.json new file mode 100644 index 00000000..9db2bae3 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_is_implicitly_authorized.1.json @@ -0,0 +1,113 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_transfer_two_step.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_transfer_two_step.1.json new file mode 100644 index 00000000..a419660b --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_admin_transfer_two_step.1.json @@ -0,0 +1,198 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "propose_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "accept_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_authorized_deployer_can_deploy.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_authorized_deployer_can_deploy.1.json new file mode 100644 index 00000000..9b435a3f --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_authorized_deployer_can_deploy.1.json @@ -0,0 +1,181 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "add_deployer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "vec": [ + { + "symbol": "Deployer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_constructor_runs_once.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_constructor_runs_once.1.json new file mode 100644 index 00000000..a60e5dd4 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_constructor_runs_once.1.json @@ -0,0 +1,131 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_deployer_removal_revokes_access.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_deployer_removal_revokes_access.1.json new file mode 100644 index 00000000..336fcb41 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_deployer_removal_revokes_access.1.json @@ -0,0 +1,198 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "add_deployer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "remove_deployer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_initial_state.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_initial_state.1.json new file mode 100644 index 00000000..9db2bae3 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_initial_state.1.json @@ -0,0 +1,113 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_pending_admin_can_be_overwritten.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_pending_admin_can_be_overwritten.1.json new file mode 100644 index 00000000..bd644498 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_pending_admin_can_be_overwritten.1.json @@ -0,0 +1,236 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "propose_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "propose_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "accept_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": "2032731177588607455" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_remove_unknown_deployer_returns_typed_error.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_remove_unknown_deployer_returns_typed_error.1.json new file mode 100644 index 00000000..286053f9 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_remove_unknown_deployer_returns_typed_error.1.json @@ -0,0 +1,112 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_role_of_classifies_correctly.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_role_of_classifies_correctly.1.json new file mode 100644 index 00000000..004c8eaa --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_role_of_classifies_correctly.1.json @@ -0,0 +1,183 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "add_deployer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "vec": [ + { + "symbol": "Deployer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_unauthorized_cannot_add_deployer.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_unauthorized_cannot_add_deployer.1.json new file mode 100644 index 00000000..fee64b77 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_unauthorized_cannot_add_deployer.1.json @@ -0,0 +1,113 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "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": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_unauthorized_deployer_blocked.1.json b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_unauthorized_deployer_blocked.1.json new file mode 100644 index 00000000..e75fc7f0 --- /dev/null +++ b/soroban-contract/contracts/deployer_registry/test_snapshots/test/test_unauthorized_deployer_blocked.1.json @@ -0,0 +1,113 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "function_name": "__constructor", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_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_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 1728000 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 1728000 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/soroban-contract/contracts/event_manager/src/lib.rs b/soroban-contract/contracts/event_manager/src/lib.rs index a60173e4..c679fb4b 100644 --- a/soroban-contract/contracts/event_manager/src/lib.rs +++ b/soroban-contract/contracts/event_manager/src/lib.rs @@ -214,6 +214,8 @@ pub struct TicketPurchasedEvent { pub tier_index: u32, } +/// Per-attendee POAP metadata sent over the wire to the POAP NFT contract's +/// `mint_poap` entry point. Field shape must match `poap_nft::PoapMetadata`. #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct EventUpdatedEvent { diff --git a/soroban-contract/contracts/ticket_factory/src/lib.rs b/soroban-contract/contracts/ticket_factory/src/lib.rs index 4cb1e830..b17de8d6 100644 --- a/soroban-contract/contracts/ticket_factory/src/lib.rs +++ b/soroban-contract/contracts/ticket_factory/src/lib.rs @@ -15,7 +15,8 @@ #![no_std] use soroban_sdk::{ - contract, contracterror, contractimpl, contracttype, Address, Bytes, BytesN, Env, IntoVal, Symbol, Val, Vec, + contract, contracterror, contractimpl, contracttype, symbol_short, Address, Bytes, BytesN, + Env, IntoVal, Symbol, Val, Vec, }; use upgradeable as upg; @@ -26,6 +27,9 @@ use upgradeable as upg; pub enum Error { NotInitialized = 1, Unauthorized = 2, + /// `deploy_ticket` was called by an address that is not on the + /// configured deployer registry's allowlist. + DeployerNotAuthorized = 3, } /// Storage keys for the contract state @@ -39,6 +43,11 @@ pub enum DataKey { TotalTickets, /// Mapping from event_id to deployed ticket contract address TicketContract(u32), + /// Optional `DeployerRegistry` contract address. When set, the factory + /// gates `deploy_ticket` on `registry.is_authorized(caller)` in + /// addition to the existing admin auth check. When unset, the factory + /// behaves identically to before (admin-only, no allowlist gate). + DeployerRegistry, } /// Ticket Factory Contract @@ -73,7 +82,7 @@ impl TicketFactory { ); } - /// Deploy a new Ticket NFT contract for an event + /// Deploy a new Ticket NFT contract for an event. /// /// # Arguments /// * `env` - The contract environment @@ -81,10 +90,17 @@ impl TicketFactory { /// * `salt` - Unique salt for deterministic address generation /// /// # Returns - /// The address of the newly deployed Ticket NFT contract + /// The address of the newly deployed Ticket NFT contract. /// /// # Authorization - /// Requires admin authorization + /// 1. Requires admin authorization (`admin.require_auth()`). + /// 2. **If a `DeployerRegistry` is configured** via [`Self::set_deployer_registry`], + /// the factory additionally invokes + /// `registry.is_authorized(minter)` and rejects the call with + /// [`Error::DeployerNotAuthorized`] if the minter is not on the + /// allowlist (the factory admin is implicitly authorized inside the + /// registry's `is_authorized` check). When no registry is configured + /// the factory falls back to the historical admin-only model. pub fn deploy_ticket(env: Env, minter: Address, salt: BytesN<32>) -> Result { // Authorize: only admin can deploy let admin: Address = env @@ -94,6 +110,23 @@ impl TicketFactory { .ok_or(Error::NotInitialized)?; admin.require_auth(); + // Optional RBAC gate: if a DeployerRegistry is configured, ensure + // the minter is on its allowlist before invoking the deployer. + if let Some(registry) = env + .storage() + .instance() + .get::<_, Address>(&DataKey::DeployerRegistry) + { + let authorized: bool = env.invoke_contract( + ®istry, + &Symbol::new(&env, "is_authorized"), + soroban_sdk::vec![&env, minter.clone().into_val(&env)], + ); + if !authorized { + return Err(Error::DeployerNotAuthorized); + } + } + // Get the WASM hash for deployment let wasm_hash: BytesN<32> = env .storage() @@ -189,6 +222,55 @@ impl TicketFactory { .unwrap_or(0) } + /// Configure (or replace) the `DeployerRegistry` contract that gates + /// `deploy_ticket`. Pass `None` to detach the current registry and + /// revert to the historical admin-only deployment model. + /// + /// # Authorization + /// Admin-only — `admin.require_auth()` plus an explicit equality + /// check against the stored admin. + pub fn set_deployer_registry( + env: Env, + admin: Address, + registry: Option, + ) -> Result<(), Error> { + admin.require_auth(); + let stored: Address = env + .storage() + .instance() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized)?; + if stored != admin { + return Err(Error::Unauthorized); + } + + match registry { + Some(addr) => { + env.storage() + .instance() + .set(&DataKey::DeployerRegistry, &addr); + env.events().publish( + (symbol_short!("registry"), symbol_short!("set")), + addr, + ); + } + None => { + env.storage().instance().remove(&DataKey::DeployerRegistry); + env.events().publish( + (symbol_short!("registry"), symbol_short!("cleared")), + (), + ); + } + } + upg::extend_instance_ttl(&env); + Ok(()) + } + + /// Read the configured `DeployerRegistry` address, if any. + pub fn get_deployer_registry(env: Env) -> Option { + env.storage().instance().get(&DataKey::DeployerRegistry) + } + /// Get the factory admin address /// /// # Arguments