diff --git a/gateway-contract/contracts/core_contract/src/errors.rs b/gateway-contract/contracts/core_contract/src/errors.rs index 710a2b66..6156ac62 100644 --- a/gateway-contract/contracts/core_contract/src/errors.rs +++ b/gateway-contract/contracts/core_contract/src/errors.rs @@ -2,33 +2,10 @@ use soroban_sdk::contracterror; #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[repr(u32)] -pub enum CoreError { - /// The requested resource was not found. - NotFound = 1, - /// The SMT root has not been set yet. - RootNotSet = 2, - /// Commitment is already registered. - DuplicateCommitment = 3, - /// public_signals.old_root does not match the current on-chain SMT root. - StaleRoot = 4, - /// The supplied Groth16 proof is invalid. - InvalidProof = 5, - /// The username is registered but has no primary Stellar address linked. - NoAddressLinked = 6, - /// Caller is not the registered owner of the commitment. - Unauthorized = 7, - /// new_owner is the same as the current owner. - SameOwner = 8, -} - -#[contracterror] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum ChainAddressError { - /// Caller is not the owner of the username commitment. - Unauthorized = 1, - /// The username commitment is not registered. - NotRegistered = 2, - /// The address format is invalid for the given chain type. - InvalidAddress = 3, +pub enum ContractError { + AlreadyInitialized = 1, + NotInitialized = 2, + RootMismatch = 3, + InvalidProof = 4, + DuplicateCommitment = 5, } diff --git a/gateway-contract/contracts/core_contract/src/events.rs b/gateway-contract/contracts/core_contract/src/events.rs index 5321341a..e087fa29 100644 --- a/gateway-contract/contracts/core_contract/src/events.rs +++ b/gateway-contract/contracts/core_contract/src/events.rs @@ -1,17 +1,6 @@ -#![allow(dead_code)] +use soroban_sdk::{contractevent, BytesN}; -use soroban_sdk::{symbol_short, Symbol}; - -pub const INIT_EVENT: Symbol = symbol_short!("INIT"); -pub const TRANSFER_EVENT: Symbol = symbol_short!("TRANSFER"); -pub const REGISTER_EVENT: Symbol = symbol_short!("REGISTER"); -pub const ROOT_UPDATED: Symbol = symbol_short!("ROOT_UPD"); -pub const MASTER_SET: Symbol = symbol_short!("MSTR_SET"); -pub const ADDR_ADDED: Symbol = symbol_short!("ADDR_ADD"); -pub const CHAIN_ADD: Symbol = symbol_short!("CHAIN_ADD"); -pub const CHAIN_REM: Symbol = symbol_short!("CHAIN_REM"); -pub const PRIV_SET: Symbol = symbol_short!("PRIV_SET"); -pub const VAULT_CREATE: Symbol = symbol_short!("VAULT_CRT"); -pub const DEPOSIT: Symbol = symbol_short!("DEPOSIT"); -pub const WITHDRAW: Symbol = symbol_short!("WITHDRAW"); -pub const SCHED_PAY: Symbol = symbol_short!("SCHED_PAY"); +#[contractevent] +pub struct UsernameRegistered { + pub commitment: BytesN<32>, +} diff --git a/gateway-contract/contracts/core_contract/src/lib.rs b/gateway-contract/contracts/core_contract/src/lib.rs index a009961d..bbea07e2 100644 --- a/gateway-contract/contracts/core_contract/src/lib.rs +++ b/gateway-contract/contracts/core_contract/src/lib.rs @@ -1,266 +1,76 @@ #![no_std] -pub mod address_manager; -pub mod errors; -pub mod events; -pub mod registration; -pub mod smt_root; -pub mod storage; -pub mod types; -pub mod zk_verifier; +mod errors; +mod events; +mod storage; +mod types; #[cfg(test)] mod test; -use address_manager::AddressManager; -use errors::CoreError; -use events::{REGISTER_EVENT, TRANSFER_EVENT}; -use registration::Registration; -use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Bytes, BytesN, Env}; -use types::{ChainType, PrivacyMode, PublicSignals, ResolveData}; +use soroban_sdk::{contract, contractclient, contractimpl, panic_with_error, Address, BytesN, Env}; -#[contract] -pub struct Contract; - -#[contractimpl] -impl Contract { - pub fn get_smt_root(env: Env) -> BytesN<32> { - smt_root::SmtRoot::get_root(env.clone()) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::RootNotSet)) - } - - pub fn register_resolver( - env: Env, - caller: Address, - commitment: BytesN<32>, - proof: Bytes, - public_signals: PublicSignals, - ) { - caller.require_auth(); - - let key = storage::DataKey::Resolver(commitment.clone()); - if env.storage().persistent().has(&key) { - panic_with_error!(&env, CoreError::DuplicateCommitment); - } - - let current_root = smt_root::SmtRoot::get_root(env.clone()) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::RootNotSet)); - if public_signals.old_root != current_root { - panic_with_error!(&env, CoreError::StaleRoot); - } - - if !zk_verifier::ZkVerifier::verify_groth16_proof(&env, &proof, &public_signals) { - panic_with_error!(&env, CoreError::InvalidProof); - } - - let data = ResolveData { - wallet: caller.clone(), - memo: None, - }; - env.storage().persistent().set(&key, &data); - - smt_root::SmtRoot::update_root(&env, public_signals.new_root); - - #[allow(deprecated)] - env.events() - .publish((REGISTER_EVENT,), (commitment, caller)); - } - - pub fn set_memo(env: Env, commitment: BytesN<32>, memo_id: u64) { - let mut data = env - .storage() - .persistent() - .get::(&storage::DataKey::Resolver(commitment.clone())) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::NotFound)); - - data.memo = Some(memo_id); - env.storage() - .persistent() - .set(&storage::DataKey::Resolver(commitment), &data); - } - - pub fn set_privacy_mode(env: Env, username_hash: BytesN<32>, mode: PrivacyMode) { - AddressManager::set_privacy_mode(env, username_hash, mode); - } - - pub fn get_privacy_mode(env: Env, username_hash: BytesN<32>) -> PrivacyMode { - AddressManager::get_privacy_mode(env, username_hash) - } - - pub fn resolve(env: Env, commitment: BytesN<32>) -> (Address, Option) { - match env - .storage() - .persistent() - .get::(&storage::DataKey::Resolver(commitment.clone())) - { - Some(data) => { - if AddressManager::get_privacy_mode(env.clone(), commitment) == PrivacyMode::Private - { - (env.current_contract_address(), data.memo) - } else { - (data.wallet, data.memo) - } - } - None => panic_with_error!(&env, CoreError::NotFound), - } - } - - pub fn register(env: Env, caller: Address, commitment: BytesN<32>) { - Registration::register(env, caller, commitment); - } - - pub fn get_owner(env: Env, commitment: BytesN<32>) -> Option
{ - Registration::get_owner(env, commitment) - } - - pub fn add_chain_address( - env: Env, - caller: Address, - username_hash: BytesN<32>, - chain: ChainType, - address: Bytes, - ) { - AddressManager::add_chain_address(env, caller, username_hash, chain, address); - } - - pub fn get_chain_address( - env: Env, - username_hash: BytesN<32>, - chain: ChainType, - ) -> Option { - AddressManager::get_chain_address(env, username_hash, chain) - } +pub use crate::errors::ContractError; +pub use crate::events::UsernameRegistered; +pub use crate::types::{Proof, PublicSignals}; - pub fn remove_chain_address( - env: Env, - caller: Address, - username_hash: BytesN<32>, - chain: ChainType, - ) { - AddressManager::remove_chain_address(env, caller, username_hash, chain); - } - - pub fn add_stellar_address( - env: Env, - caller: Address, - username_hash: BytesN<32>, - stellar_address: Address, - ) { - caller.require_auth(); +#[contract] +pub struct CoreContract; - let owner = Registration::get_owner(env.clone(), username_hash.clone()) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::NotFound)); +#[contractclient(name = "VerifierContractClient")] +pub trait VerifierContract { + fn verify_proof(env: Env, proof: Proof, public_signals: PublicSignals) -> bool; +} - if owner != caller { - panic_with_error!(&env, CoreError::NotFound); +#[contractimpl] +impl CoreContract { + pub fn init(env: Env, verifier: Address, root: BytesN<32>) { + if storage::is_initialized(&env) { + panic_with_error!(&env, ContractError::AlreadyInitialized); } - env.storage().persistent().set( - &storage::DataKey::StellarAddress(username_hash), - &stellar_address, - ); + storage::set_verifier(&env, &verifier); + storage::set_root(&env, &root); } - /// Transfer ownership of a commitment to a new owner. - /// The caller must be the current registered owner. - /// Panics with `Unauthorized` if caller is not the owner. - /// Panics with `SameOwner` if new_owner equals the current owner. - pub fn transfer_ownership( - env: Env, - caller: Address, - commitment: BytesN<32>, - new_owner: Address, - ) { - caller.require_auth(); - - let key = registration::DataKey::Commitment(commitment.clone()); - let current_owner: Address = env - .storage() - .persistent() - .get(&key) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::NotFound)); + pub fn submit_proof(env: Env, proof: Proof, public_signals: PublicSignals) { + let current_root = storage::get_root(&env) + .unwrap_or_else(|| panic_with_error!(&env, ContractError::NotInitialized)); - if caller != current_owner { - panic_with_error!(&env, CoreError::Unauthorized); + if current_root != public_signals.old_root.clone() { + panic_with_error!(&env, ContractError::RootMismatch); } - if new_owner == current_owner { - panic_with_error!(&env, CoreError::SameOwner); + if storage::has_commitment(&env, &public_signals.commitment) { + panic_with_error!(&env, ContractError::DuplicateCommitment); } - env.storage().persistent().set(&key, &new_owner); - - #[allow(deprecated)] - env.events() - .publish((TRANSFER_EVENT,), (commitment, caller, new_owner)); - } - - /// Transfer ownership of a commitment with ZK proof verification and SMT root update. - /// The caller must be the current registered owner. - /// Panics with `Unauthorized` if caller is not the owner. - /// Panics with `SameOwner` if new_owner equals the current owner. - /// Panics with `StaleRoot` if public_signals.old_root does not match the on-chain root. - pub fn transfer( - env: Env, - caller: Address, - commitment: BytesN<32>, - new_owner: Address, - proof: Bytes, - public_signals: PublicSignals, - ) { - caller.require_auth(); - - let key = registration::DataKey::Commitment(commitment.clone()); - let current_owner: Address = env - .storage() - .persistent() - .get(&key) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::NotFound)); - - if caller != current_owner { - panic_with_error!(&env, CoreError::Unauthorized); + let verifier = storage::get_verifier(&env) + .unwrap_or_else(|| panic_with_error!(&env, ContractError::NotInitialized)); + let verifier_client = VerifierContractClient::new(&env, &verifier); + let is_valid = verifier_client.verify_proof(&proof, &public_signals); + if !is_valid { + panic_with_error!(&env, ContractError::InvalidProof); } - if new_owner == current_owner { - panic_with_error!(&env, CoreError::SameOwner); - } + storage::store_commitment(&env, &public_signals.commitment); + storage::set_root(&env, &public_signals.new_root); - // SMT root consistency - let current_root = smt_root::SmtRoot::get_root(env.clone()) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::RootNotSet)); - if public_signals.old_root != current_root { - panic_with_error!(&env, CoreError::StaleRoot); + UsernameRegistered { + commitment: public_signals.commitment, } + .publish(&env); + } - // ZK proof verification (Phase 4 stub) - if !zk_verifier::ZkVerifier::verify_groth16_proof(&env, &proof, &public_signals) { - panic_with_error!(&env, CoreError::InvalidProof); - } - - // Update ownership - env.storage().persistent().set(&key, &new_owner); - - // Advance SMT root - smt_root::SmtRoot::update_root(&env, public_signals.new_root); - - // Emit TRANSFER event - #[allow(deprecated)] - env.events() - .publish((TRANSFER_EVENT,), (commitment, caller, new_owner)); + pub fn get_root(env: Env) -> Option> { + storage::get_root(&env) } - /// Resolve a username hash to its primary linked Stellar address. - /// - /// Returns `NotFound` if the username hash is not registered. - /// Returns `NoAddressLinked` if registered but no primary Stellar address has been set. - pub fn resolve_stellar(env: Env, username_hash: BytesN<32>) -> Address { - if Registration::get_owner(env.clone(), username_hash.clone()).is_none() { - panic_with_error!(&env, CoreError::NotFound); - } + pub fn get_verifier(env: Env) -> Option
{ + storage::get_verifier(&env) + } - env.storage() - .persistent() - .get::(&storage::DataKey::StellarAddress(username_hash)) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::NoAddressLinked)) + pub fn has_commitment(env: Env, commitment: BytesN<32>) -> bool { + storage::has_commitment(&env, &commitment) } } diff --git a/gateway-contract/contracts/core_contract/src/storage.rs b/gateway-contract/contracts/core_contract/src/storage.rs index e7a054d0..15dd62ca 100644 --- a/gateway-contract/contracts/core_contract/src/storage.rs +++ b/gateway-contract/contracts/core_contract/src/storage.rs @@ -1,13 +1,41 @@ -use soroban_sdk::{contracttype, BytesN}; +use soroban_sdk::{contracttype, Address, BytesN, Env}; -/// Storage keys for the Core contract's persistent and instance storage. #[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone)] pub enum DataKey { - /// Key for resolver data, indexed by commitment. - Resolver(BytesN<32>), - /// Key for the SMT root in instance storage. - SmtRoot, - /// Key for the primary Stellar address linked to a username hash. - StellarAddress(BytesN<32>), + Root, + Verifier, + Commitment(BytesN<32>), +} + +pub fn is_initialized(env: &Env) -> bool { + env.storage().instance().has(&DataKey::Root) && env.storage().instance().has(&DataKey::Verifier) +} + +pub fn get_root(env: &Env) -> Option> { + env.storage().instance().get(&DataKey::Root) +} + +pub fn set_root(env: &Env, root: &BytesN<32>) { + env.storage().instance().set(&DataKey::Root, root); +} + +pub fn get_verifier(env: &Env) -> Option
{ + env.storage().instance().get(&DataKey::Verifier) +} + +pub fn set_verifier(env: &Env, verifier: &Address) { + env.storage().instance().set(&DataKey::Verifier, verifier); +} + +pub fn has_commitment(env: &Env, commitment: &BytesN<32>) -> bool { + env.storage() + .persistent() + .has(&DataKey::Commitment(commitment.clone())) +} + +pub fn store_commitment(env: &Env, commitment: &BytesN<32>) { + env.storage() + .persistent() + .set(&DataKey::Commitment(commitment.clone()), &true); } diff --git a/gateway-contract/contracts/core_contract/src/test.rs b/gateway-contract/contracts/core_contract/src/test.rs index c9753b64..20e59b55 100644 --- a/gateway-contract/contracts/core_contract/src/test.rs +++ b/gateway-contract/contracts/core_contract/src/test.rs @@ -1,995 +1,188 @@ #![cfg(test)] -use crate::smt_root::SmtRoot; -use crate::types::{AddressMetadata, ChainType, PrivacyMode, PublicSignals}; -use crate::{Contract, ContractClient}; -use escrow_contract::types::{ - AutoPay, ScheduledPayment as EscrowScheduledPayment, VaultConfig, VaultState, +use crate::errors::ContractError; +use crate::types::{Proof, PublicSignals}; +use crate::{CoreContract, CoreContractClient}; +use soroban_sdk::testutils::Events as _; +use soroban_sdk::{ + contract, contractimpl, contracttype, map, Address, BytesN, Env, Error, IntoVal, Map, Symbol, + Val, }; -use soroban_sdk::testutils::{Address as _, Events}; -use soroban_sdk::{contracttype, Address, Bytes, BytesN, Env, Symbol}; -fn setup(env: &Env) -> (Address, ContractClient<'_>) { - let contract_id = env.register(Contract, ()); - let client = ContractClient::new(env, &contract_id); - (contract_id, client) -} - -fn setup_with_root(env: &Env) -> (Address, ContractClient<'_>, BytesN<32>) { - let (contract_id, client) = setup(env); - let root = BytesN::from_array(env, &[1u8; 32]); - env.as_contract(&contract_id, || { - SmtRoot::update_root(env, root.clone()); - }); - (contract_id, client, root) -} - -fn commitment(env: &Env, seed: u8) -> BytesN<32> { - BytesN::from_array(env, &[seed; 32]) -} - -// ── registration tests ─────────────────────────────────────────────────────── - -#[test] -fn test_register_success() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let hash = commitment(&env, 10); - - client.register(&owner, &hash); - - let stored_owner = client.get_owner(&hash); - assert_eq!(stored_owner, Some(owner)); -} - -#[test] -#[should_panic(expected = "Commitment already registered")] -fn test_register_duplicate_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let hash = commitment(&env, 11); - - client.register(&owner, &hash); - client.register(&owner, &hash); -} - -#[test] -#[should_panic] -fn test_register_requires_auth() { - let env = Env::default(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let hash = commitment(&env, 12); - - client.register(&owner, &hash); -} - -#[test] -fn test_get_owner_returns_none_for_unknown() { - let env = Env::default(); - let (_, client) = setup(&env); - - let hash = commitment(&env, 13); - let stored_owner = client.get_owner(&hash); - assert_eq!(stored_owner, None); -} - -fn dummy_proof(env: &Env) -> Bytes { - Bytes::from_slice(env, &[1u8; 64]) -} +#[contract] +struct MockVerifierContract; #[contracttype] #[derive(Clone)] -enum RoundtripKey { - AddressMetadata, - VaultConfig, - VaultState, - ScheduledPayment, - AutoPay, -} - -// ── contracttype roundtrip tests ───────────────────────────────────────────── - -#[test] -fn test_address_metadata_roundtrip() { - let env = Env::default(); - let (contract_id, _) = setup(&env); - let key = RoundtripKey::AddressMetadata; - let label = Symbol::new(&env, "primary"); - let metadata = AddressMetadata { - label: label.clone(), - }; - - let stored = env.as_contract(&contract_id, || { - env.storage().persistent().set(&key, &metadata); - env.storage().persistent().get::<_, AddressMetadata>(&key) - }); - assert_eq!(stored.map(|item| item.label), Some(label)); -} - -#[test] -fn test_vault_config_roundtrip() { - let env = Env::default(); - let (contract_id, _) = setup(&env); - let key = RoundtripKey::VaultConfig; - let config = VaultConfig { - owner: Address::generate(&env), - token: Address::generate(&env), - created_at: 1_729_000_001, - }; - - let stored = env.as_contract(&contract_id, || { - env.storage().persistent().set(&key, &config); - env.storage().persistent().get::<_, VaultConfig>(&key) - }); - assert_eq!(stored, Some(config)); -} - -#[test] -fn test_vault_state_roundtrip() { - let env = Env::default(); - let (contract_id, _) = setup(&env); - let key = RoundtripKey::VaultState; - let state = VaultState { - balance: 5_000, - is_active: true, - }; - - let stored = env.as_contract(&contract_id, || { - env.storage().persistent().set(&key, &state); - env.storage().persistent().get::<_, VaultState>(&key) - }); - assert_eq!(stored, Some(state)); +enum MockVerifierDataKey { + ShouldVerify, } -#[test] -fn test_scheduled_payment_roundtrip() { - let env = Env::default(); - let (contract_id, _) = setup(&env); - let key = RoundtripKey::ScheduledPayment; - let payment = EscrowScheduledPayment { - from: BytesN::from_array(&env, &[7u8; 32]), - to: BytesN::from_array(&env, &[8u8; 32]), - token: Address::generate(&env), - amount: 900, - release_at: 3_600, - executed: false, - }; - - let stored = env.as_contract(&contract_id, || { - env.storage().persistent().set(&key, &payment); +#[contractimpl] +impl MockVerifierContract { + pub fn set_should_verify(env: Env, should_verify: bool) { env.storage() - .persistent() - .get::<_, EscrowScheduledPayment>(&key) - }); - assert_eq!(stored, Some(payment)); -} - -#[test] -fn test_auto_pay_roundtrip() { - let env = Env::default(); - let (contract_id, _) = setup(&env); - let key = RoundtripKey::AutoPay; - let rule = AutoPay { - from: BytesN::from_array(&env, &[9u8; 32]), - to: BytesN::from_array(&env, &[10u8; 32]), - token: Address::generate(&env), - amount: 250, - interval: 86_400, - last_paid: 0, - }; - - let stored = env.as_contract(&contract_id, || { - env.storage().persistent().set(&key, &rule); - env.storage().persistent().get::<_, AutoPay>(&key) - }); - assert_eq!(stored, Some(rule)); -} - -// ── resolver / memo tests ───────────────────────────────────────────────────── - -#[test] -fn test_resolve_returns_none_when_no_memo() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client, root) = setup_with_root(&env); - let caller = Address::generate(&env); - let hash = commitment(&env, 0); - let new_root = BytesN::from_array(&env, &[2u8; 32]); - - let signals = PublicSignals { - old_root: root, - new_root, - }; - client.register_resolver(&caller, &hash, &dummy_proof(&env), &signals); - - let (resolved_wallet, memo) = client.resolve(&hash); - assert_eq!(resolved_wallet, caller); - assert_eq!(memo, None); -} - -#[test] -fn test_set_memo_and_resolve_flow() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client, root) = setup_with_root(&env); - let caller = Address::generate(&env); - let hash = commitment(&env, 0); - let new_root = BytesN::from_array(&env, &[2u8; 32]); - - let signals = PublicSignals { - old_root: root, - new_root, - }; - client.register_resolver(&caller, &hash, &dummy_proof(&env), &signals); - client.set_memo(&hash, &4242u64); - - let (resolved_wallet, memo) = client.resolve(&hash); - assert_eq!(resolved_wallet, caller); - assert_eq!(memo, Some(4242u64)); -} - -#[test] -fn test_privacy_mode_resolve_shields_registered_wallet() { - let env = Env::default(); - env.mock_all_auths(); - let (contract_id, client, root) = setup_with_root(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 40); - let new_root = BytesN::from_array(&env, &[41u8; 32]); - - client.register(&owner, &hash); - client.register_resolver( - &owner, - &hash, - &dummy_proof(&env), - &PublicSignals { - old_root: root, - new_root, + .instance() + .set(&MockVerifierDataKey::ShouldVerify, &should_verify); + } + + pub fn verify_proof(env: Env, proof: Proof, public_signals: PublicSignals) -> bool { + let should_verify = env + .storage() + .instance() + .get::(&MockVerifierDataKey::ShouldVerify) + .unwrap_or(true); + + should_verify + && proof.a == public_signals.old_root + && proof.b == public_signals.new_root + && proof.c == public_signals.commitment + } +} + +fn bytes(env: &Env, byte: u8) -> BytesN<32> { + BytesN::from_array(env, &[byte; 32]) +} + +fn registration_fixture( + env: &Env, + old_byte: u8, + new_byte: u8, + commitment_byte: u8, +) -> (Proof, PublicSignals) { + let old_root = bytes(env, old_byte); + let new_root = bytes(env, new_byte); + let commitment = bytes(env, commitment_byte); + + ( + Proof { + a: old_root.clone(), + b: new_root.clone(), + c: commitment.clone(), }, - ); - - assert_eq!(client.get_privacy_mode(&hash), PrivacyMode::Normal); - assert_eq!(client.resolve(&hash), (owner.clone(), None)); - - client.set_privacy_mode(&hash, &PrivacyMode::Private); - - assert_eq!(client.get_privacy_mode(&hash), PrivacyMode::Private); - assert_eq!(client.resolve(&hash), (contract_id, None)); -} - -#[test] -fn test_privacy_mode_can_be_restored_to_normal() { - let env = Env::default(); - env.mock_all_auths(); - let (contract_id, client, root) = setup_with_root(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 42); - let new_root = BytesN::from_array(&env, &[43u8; 32]); - - client.register(&owner, &hash); - client.register_resolver( - &owner, - &hash, - &dummy_proof(&env), - &PublicSignals { - old_root: root, + PublicSignals { + old_root, new_root, + commitment, }, - ); - - client.set_privacy_mode(&hash, &PrivacyMode::Private); - assert_eq!(client.resolve(&hash), (contract_id, None)); - - client.set_privacy_mode(&hash, &PrivacyMode::Normal); - assert_eq!(client.resolve(&hash), (owner, None)); -} - -// ── resolve_stellar tests ───────────────────────────────────────────────────── - -#[test] -fn test_resolve_stellar_returns_linked_address() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let hash = commitment(&env, 10); - - client.register(&owner, &hash); - client.add_stellar_address(&owner, &hash, &owner); - - let resolved = client.resolve_stellar(&hash); - assert_eq!(resolved, owner); -} - -#[test] -fn test_resolve_stellar_linked_address_differs_from_owner() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let payment_address = Address::generate(&env); - let hash = commitment(&env, 11); - - client.register(&owner, &hash); - client.add_stellar_address(&owner, &hash, &payment_address); - - let resolved = client.resolve_stellar(&hash); - assert_eq!(resolved, payment_address); -} - -#[test] -#[should_panic(expected = "Error(Contract, #1)")] -fn test_resolve_stellar_not_found_for_unregistered_hash() { - let env = Env::default(); - let (_, client) = setup(&env); - - let hash = commitment(&env, 12); - client.resolve_stellar(&hash); -} - -// ── register_resolver gate tests ────────────────────────────────────────────── - -#[test] -#[should_panic] -fn test_register_resolver_unauthenticated_fails() { - let env = Env::default(); - let (_, client, root) = setup_with_root(&env); - let caller = Address::generate(&env); - let hash = commitment(&env, 20); - let signals = PublicSignals { - old_root: root, - new_root: BytesN::from_array(&env, &[2u8; 32]), - }; - client.register_resolver(&caller, &hash, &dummy_proof(&env), &signals); -} - -#[test] -#[should_panic(expected = "Error(Contract, #4)")] -fn test_register_resolver_stale_root_fails() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client, _) = setup_with_root(&env); - let caller = Address::generate(&env); - let hash = commitment(&env, 21); - let signals = PublicSignals { - old_root: BytesN::from_array(&env, &[99u8; 32]), - new_root: BytesN::from_array(&env, &[2u8; 32]), - }; - client.register_resolver(&caller, &hash, &dummy_proof(&env), &signals); -} - -#[test] -#[should_panic(expected = "Error(Contract, #6)")] -fn test_resolve_stellar_no_address_linked_when_not_set() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let hash = commitment(&env, 13); - - client.register(&owner, &hash); - client.resolve_stellar(&hash); -} - -#[test] -#[should_panic] -fn test_add_stellar_address_wrong_owner_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let attacker = Address::generate(&env); - let hash = commitment(&env, 14); - - client.register(&owner, &hash); - client.add_stellar_address(&attacker, &hash, &attacker); -} - -#[test] -#[should_panic(expected = "Error(Contract, #1)")] -fn test_add_stellar_address_not_registered_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let caller = Address::generate(&env); - let hash = commitment(&env, 15); - - client.add_stellar_address(&caller, &hash, &caller); -} - -#[test] -#[should_panic(expected = "Error(Contract, #3)")] -fn test_register_resolver_duplicate_commitment_fails() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client, root) = setup_with_root(&env); - let caller = Address::generate(&env); - let hash = commitment(&env, 22); - let new_root = BytesN::from_array(&env, &[2u8; 32]); - - let signals_first = PublicSignals { - old_root: root, - new_root: new_root.clone(), - }; - client.register_resolver(&caller, &hash, &dummy_proof(&env), &signals_first); - - let signals_second = PublicSignals { - old_root: new_root, - new_root: BytesN::from_array(&env, &[3u8; 32]), - }; - client.register_resolver(&caller, &hash, &dummy_proof(&env), &signals_second); -} - -#[test] -fn test_register_resolver_success_updates_root() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client, root) = setup_with_root(&env); - let caller = Address::generate(&env); - let hash = commitment(&env, 23); - let new_root = BytesN::from_array(&env, &[2u8; 32]); - - let signals = PublicSignals { - old_root: root, - new_root: new_root.clone(), - }; - client.register_resolver(&caller, &hash, &dummy_proof(&env), &signals); - - assert_eq!(client.get_smt_root(), new_root); - let (resolved_wallet, memo) = client.resolve(&hash); - assert_eq!(resolved_wallet, caller); - assert_eq!(memo, None); -} - -#[test] -fn test_register_resolver_emits_events() { - use crate::errors::CoreError; - use crate::storage::DataKey; - use crate::zk_verifier::ZkVerifier; - - let env = Env::default(); - env.mock_all_auths(); - let (contract_id, _, root) = setup_with_root(&env); - - let caller = Address::generate(&env); - let hash = commitment(&env, 24); - let new_root = BytesN::from_array(&env, &[2u8; 32]); - let proof = dummy_proof(&env); - let signals = PublicSignals { - old_root: root.clone(), - new_root: new_root.clone(), - }; - - env.as_contract(&contract_id, || { - use soroban_sdk::panic_with_error; - - let key = DataKey::Resolver(hash.clone()); - if env.storage().persistent().has(&key) { - panic_with_error!(&env, CoreError::DuplicateCommitment); - } - let current = SmtRoot::get_root(env.clone()) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::RootNotSet)); - assert_eq!(signals.old_root, current); - assert!(ZkVerifier::verify_groth16_proof(&env, &proof, &signals)); - env.storage().persistent().set( - &key, - &crate::types::ResolveData { - wallet: caller.clone(), - memo: None, - }, - ); - SmtRoot::update_root(&env, signals.new_root.clone()); - #[allow(deprecated)] - env.events().publish( - (crate::events::REGISTER_EVENT,), - (hash.clone(), caller.clone()), - ); - }); - - let events = env.events().all(); - assert_eq!( - events.len(), - 2, - "ROOT_UPD and REGISTER events must both be emitted" - ); -} - -// ── SMT root tests ──────────────────────────────────────────────────────────── - -#[test] -#[should_panic(expected = "Error(Contract, #2)")] -fn test_get_smt_root_panics_when_not_set() { - let env = Env::default(); - let (_, client) = setup(&env); - client.get_smt_root(); -} - -#[test] -fn test_smt_root_read_after_update() { - let env = Env::default(); - env.mock_all_auths(); - let (contract_id, client) = setup(&env); - - let new_root = BytesN::from_array(&env, &[42u8; 32]); - env.as_contract(&contract_id, || { - SmtRoot::update_root(&env, new_root.clone()); - }); - - assert_eq!(client.get_smt_root(), new_root); -} - -#[test] -fn test_smt_root_update_emits_event() { - let env = Env::default(); - env.mock_all_auths(); - let (contract_id, _) = setup(&env); - - let root1 = BytesN::from_array(&env, &[1u8; 32]); - env.as_contract(&contract_id, || { - SmtRoot::update_root(&env, root1.clone()); - }); - - let root2 = BytesN::from_array(&env, &[2u8; 32]); - env.as_contract(&contract_id, || { - SmtRoot::update_root(&env, root2.clone()); - }); - - let events = env.events().all(); - assert!(!events.is_empty(), "ROOT_UPD events should be emitted"); -} - -// ── chain address helpers ───────────────────────────────────────────────────── - -fn evm_address(env: &Env) -> Bytes { - Bytes::from_slice(env, b"0xaAbBcCdDeEfF00112233445566778899aAbBcCdD") -} - -fn bitcoin_address(env: &Env) -> Bytes { - Bytes::from_slice(env, &b"1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf Na"[..34]) -} - -fn solana_address(env: &Env) -> Bytes { - Bytes::from_slice(env, b"So11111111111111111111111111111111111111112") -} - -fn cosmos_address(env: &Env) -> Bytes { - Bytes::from_slice(env, b"cosmos1syavy2npfyt9tcncdtsdzf7kny9lh777yh8aee") -} - -// ── success cases ───────────────────────────────────────────────────────────── - -#[test] -fn test_add_evm_address_success() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 1); - let addr = evm_address(&env); - client.register(&owner, &hash); - client.add_chain_address(&owner, &hash, &ChainType::Evm, &addr); - assert_eq!(client.get_chain_address(&hash, &ChainType::Evm), Some(addr)); -} - -#[test] -fn test_add_bitcoin_address_success() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 2); - let addr = bitcoin_address(&env); - client.register(&owner, &hash); - client.add_chain_address(&owner, &hash, &ChainType::Bitcoin, &addr); - assert_eq!( - client.get_chain_address(&hash, &ChainType::Bitcoin), - Some(addr) - ); -} - -#[test] -fn test_add_solana_address_success() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 3); - let addr = solana_address(&env); - client.register(&owner, &hash); - client.add_chain_address(&owner, &hash, &ChainType::Solana, &addr); - assert_eq!( - client.get_chain_address(&hash, &ChainType::Solana), - Some(addr) - ); -} - -#[test] -fn test_add_cosmos_address_success() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 4); - let addr = cosmos_address(&env); - client.register(&owner, &hash); - client.add_chain_address(&owner, &hash, &ChainType::Cosmos, &addr); - assert_eq!( - client.get_chain_address(&hash, &ChainType::Cosmos), - Some(addr) - ); -} - -#[test] -fn test_get_chain_address_returns_none_when_not_set() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let hash = commitment(&env, 5); - assert_eq!(client.get_chain_address(&hash, &ChainType::Evm), None); + ) } -#[test] -fn test_remove_chain_address_success() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 6); - let addr = evm_address(&env); - client.register(&owner, &hash); - client.add_chain_address(&owner, &hash, &ChainType::Evm, &addr); - assert_eq!(client.get_chain_address(&hash, &ChainType::Evm), Some(addr)); - client.remove_chain_address(&owner, &hash, &ChainType::Evm); - assert_eq!(client.get_chain_address(&hash, &ChainType::Evm), None); -} +fn setup(env: &Env) -> (Address, CoreContractClient<'_>, Address) { + let verifier_id = env.register(MockVerifierContract, ()); + let verifier_client = MockVerifierContractClient::new(env, &verifier_id); + verifier_client.set_should_verify(&true); -// ── auth / ownership failures ───────────────────────────────────────────────── + let contract_id = env.register(CoreContract, ()); + let client = CoreContractClient::new(env, &contract_id); + client.init(&verifier_id, &bytes(env, 0)); -#[test] -#[should_panic] -fn test_add_chain_address_not_registered_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let caller = Address::generate(&env); - let hash = commitment(&env, 7); - client.add_chain_address(&caller, &hash, &ChainType::Evm, &evm_address(&env)); + (contract_id, client, verifier_id) } -#[test] -#[should_panic] -fn test_add_chain_address_wrong_owner_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let attacker = Address::generate(&env); - let hash = commitment(&env, 8); - client.register(&owner, &hash); - client.add_chain_address(&attacker, &hash, &ChainType::Evm, &evm_address(&env)); +fn assert_submit_error( + result: Result< + Result<(), soroban_sdk::ConversionError>, + Result, + >, + expected: ContractError, +) { + assert_eq!(result, Err(Ok(expected.into()))); } #[test] -#[should_panic] -fn test_remove_chain_address_wrong_owner_panics() { +fn init_sets_the_current_root() { let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let attacker = Address::generate(&env); - let hash = commitment(&env, 9); - client.register(&owner, &hash); - client.add_chain_address(&owner, &hash, &ChainType::Evm, &evm_address(&env)); - client.remove_chain_address(&attacker, &hash, &ChainType::Evm); -} + let (_, client, _) = setup(&env); -// ── ownership transfer tests ────────────────────────────────────────────────── - -#[test] -fn test_transfer_ownership_succeeds() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let new_owner = Address::generate(&env); - let hash = commitment(&env, 30); - - client.register(&owner, &hash); - client.transfer_ownership(&owner, &hash, &new_owner); - - assert_eq!(client.get_owner(&hash), Some(new_owner)); + assert_eq!(client.get_root(), Some(bytes(&env, 0))); } #[test] -#[should_panic(expected = "Error(Contract, #7)")] -fn test_transfer_ownership_non_owner_panics() { +fn submit_proof_succeeds_and_updates_state() { let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); + let (contract_id, client, _) = setup(&env); + let (proof, public_signals) = registration_fixture(&env, 0, 42, 7); - let owner = Address::generate(&env); - let attacker = Address::generate(&env); - let new_owner = Address::generate(&env); - let hash = commitment(&env, 31); + client.submit_proof(&proof, &public_signals); - client.register(&owner, &hash); - client.transfer_ownership(&attacker, &hash, &new_owner); -} - -/// Verifies that transfer sets the new owner, advances the SMT root, and emits a TRANSFER event. -/// Contract-client invocations do not surface in env.events().all(), so the event is verified -/// by replicating the transfer logic inside env.as_contract — matching the pattern used in -/// test_register_resolver_emits_events. -#[test] -fn test_transfer_succeeds() { - use crate::errors::CoreError; - use crate::events::TRANSFER_EVENT; - use crate::registration::DataKey as RegKey; - use crate::zk_verifier::ZkVerifier; - use soroban_sdk::panic_with_error; + assert_eq!(client.get_root(), Some(public_signals.new_root.clone())); + assert!(client.has_commitment(&public_signals.commitment)); - let env = Env::default(); - env.mock_all_auths(); - let (contract_id, client, root) = setup_with_root(&env); - - let owner = Address::generate(&env); - let new_owner = Address::generate(&env); - let hash = commitment(&env, 32); - let new_root = BytesN::from_array(&env, &[99u8; 32]); - let proof = dummy_proof(&env); - let signals = PublicSignals { - old_root: root.clone(), - new_root: new_root.clone(), - }; - - client.register(&owner, &hash); - - env.as_contract(&contract_id, || { - let key = RegKey::Commitment(hash.clone()); - let current_owner: Address = env.storage().persistent().get(&key).unwrap(); - - if owner != current_owner { - panic_with_error!(&env, CoreError::Unauthorized); - } - if new_owner == current_owner { - panic_with_error!(&env, CoreError::SameOwner); - } - let current_root = SmtRoot::get_root(env.clone()) - .unwrap_or_else(|| panic_with_error!(&env, CoreError::RootNotSet)); - assert_eq!(signals.old_root, current_root); - assert!(ZkVerifier::verify_groth16_proof(&env, &proof, &signals)); - - env.storage().persistent().set(&key, &new_owner); - SmtRoot::update_root(&env, signals.new_root.clone()); - - #[allow(deprecated)] - env.events().publish( - (TRANSFER_EVENT,), - (hash.clone(), owner.clone(), new_owner.clone()), - ); - }); - - // env.events().all() returns events from the most recent as_contract scope. - // Verify: TRANSFER event emitted (ROOT_UPD from SmtRoot::update_root + TRANSFER = 2) - let events = env.events().all(); + let expected_event_data: Map = map![ + &env, + ( + Symbol::new(&env, "commitment"), + public_signals.commitment.clone().into_val(&env) + ) + ]; assert_eq!( - events.len(), - 2, - "ROOT_UPD and TRANSFER events must both be emitted" - ); - - // Verify: new owner set and SMT root updated - // (client calls do not create a new as_contract scope, so event count above is stable) - assert_eq!(client.get_owner(&hash), Some(new_owner.clone())); - assert_eq!(client.get_smt_root(), new_root); -} - -#[test] -#[should_panic(expected = "Error(Contract, #8)")] -fn test_transfer_same_owner_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let hash = commitment(&env, 33); - - client.register(&owner, &hash); - - let signals = PublicSignals { - old_root: BytesN::from_array(&env, &[0u8; 32]), - new_root: BytesN::from_array(&env, &[0u8; 32]), - }; - // new_owner == old_owner must panic with SameOwner (#8) - client.transfer(&owner, &hash, &owner, &dummy_proof(&env), &signals); -} - -#[test] -#[should_panic(expected = "Error(Contract, #7)")] -fn test_transfer_non_owner_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - - let owner = Address::generate(&env); - let attacker = Address::generate(&env); - let new_owner = Address::generate(&env); - let hash = commitment(&env, 34); - - client.register(&owner, &hash); - - let signals = PublicSignals { - old_root: BytesN::from_array(&env, &[0u8; 32]), - new_root: BytesN::from_array(&env, &[0u8; 32]), - }; - // attacker is not the owner → Unauthorized (#7) - client.transfer(&attacker, &hash, &new_owner, &dummy_proof(&env), &signals); -} - -// ── address validation failures ─────────────────────────────────────────────── - -#[test] -#[should_panic] -fn test_invalid_evm_address_wrong_length_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 10); - client.register(&owner, &hash); - client.add_chain_address( - &owner, - &hash, - &ChainType::Evm, - &Bytes::from_slice(&env, b"0x1234567"), + env.events().all(), + soroban_sdk::vec![ + &env, + ( + contract_id, + (Symbol::new(&env, "username_registered"),).into_val(&env), + expected_event_data.into_val(&env), + ) + ] ); } #[test] -#[should_panic] -fn test_invalid_evm_address_no_prefix_panics() { +fn invalid_proof_is_rejected() { let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 11); - client.register(&owner, &hash); - client.add_chain_address( - &owner, - &hash, - &ChainType::Evm, - &Bytes::from_slice(&env, b"aAbBcCdDeEfF00112233445566778899aAbBcCdDeE"), - ); -} + let (_, client, verifier_id) = setup(&env); + let verifier_client = MockVerifierContractClient::new(&env, &verifier_id); + verifier_client.set_should_verify(&false); -#[test] -#[should_panic] -fn test_invalid_solana_address_too_short_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 12); - client.register(&owner, &hash); - client.add_chain_address( - &owner, - &hash, - &ChainType::Solana, - &Bytes::from_slice(&env, b"short1234"), - ); -} + let (proof, public_signals) = registration_fixture(&env, 0, 42, 7); + let result = client.try_submit_proof(&proof, &public_signals); -#[test] -#[should_panic] -fn test_invalid_cosmos_address_too_short_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (_, client) = setup(&env); - let owner = Address::generate(&env); - let hash = commitment(&env, 13); - client.register(&owner, &hash); - client.add_chain_address( - &owner, - &hash, - &ChainType::Cosmos, - &Bytes::from_slice(&env, b"cosmos123"), - ); + assert_submit_error(result, ContractError::InvalidProof); + assert!(!client.has_commitment(&public_signals.commitment)); + assert_eq!(client.get_root(), Some(public_signals.old_root)); } -// ============================================================================ -// SMT Root Tests -// ============================================================================ - #[test] -fn test_get_root_returns_none_before_set() { +fn stale_root_is_rejected() { let env = Env::default(); - let (contract_id, _) = setup(&env); + let (_, client, _) = setup(&env); + let (proof, mut public_signals) = registration_fixture(&env, 0, 42, 7); + public_signals.old_root = bytes(&env, 1); - // The client unwrap/panics if empty, so we test the underlying SmtRoot - // directly inside the contract context to verify it safely returns None. - env.as_contract(&contract_id, || { - assert_eq!(SmtRoot::get_root(env.clone()), None); - }); -} + let result = client.try_submit_proof(&proof, &public_signals); -#[test] -fn test_update_root_stores_new_root() { - let env = Env::default(); - let (contract_id, client) = setup(&env); - let new_root = BytesN::from_array(&env, &[2u8; 32]); - - env.as_contract(&contract_id, || { - SmtRoot::update_root(&env, new_root.clone()); - }); - - // The client returns BytesN<32> directly, so we drop the Some() - assert_eq!(client.get_smt_root(), new_root); + assert_submit_error(result, ContractError::RootMismatch); + assert!(!client.has_commitment(&public_signals.commitment)); + assert_eq!(client.get_root(), Some(bytes(&env, 0))); } #[test] -fn test_update_root_emits_event() { +fn duplicate_commitment_is_rejected() { let env = Env::default(); - let (contract_id, _client) = setup(&env); - let new_root = BytesN::from_array(&env, &[3u8; 32]); - - env.as_contract(&contract_id, || { - SmtRoot::update_root(&env, new_root.clone()); - }); - - let events = env.events().all(); - let last_event = events.last().expect("No events emitted"); + let (_, client, _) = setup(&env); + let (proof, public_signals) = registration_fixture(&env, 0, 42, 7); - use soroban_sdk::{IntoVal, TryFromVal}; + client.submit_proof(&proof, &public_signals); - assert_eq!(last_event.0, contract_id); + let duplicate_result = client.try_submit_proof(&proof, &public_signals); - // Decode the Val back into a Symbol to properly compare it - let event_name = Symbol::try_from_val(&env, &last_event.1.get(0).unwrap()).unwrap(); - assert_eq!(event_name, Symbol::new(&env, "ROOT_UPD")); - - let (old, new): (Option>, BytesN<32>) = last_event.2.into_val(&env); - assert_eq!(old, None); - assert_eq!(new, new_root); + assert_submit_error(duplicate_result, ContractError::DuplicateCommitment); + assert_eq!(client.get_root(), Some(public_signals.new_root)); } #[test] -fn test_update_root_non_owner_panics() { +fn root_progresses_across_multiple_registrations() { let env = Env::default(); - let (contract_id, _client) = setup(&env); - let non_owner = Address::generate(&env); - let root = BytesN::from_array(&env, &[4u8; 32]); - - use soroban_sdk::IntoVal; - - env.mock_auths(&[soroban_sdk::testutils::MockAuth { - address: &non_owner, - invoke: &soroban_sdk::testutils::MockAuthInvoke { - contract: &contract_id, - fn_name: "update_smt_root", - args: (root.clone(),).into_val(&env), - sub_invokes: &[], - }, - }]); + let (_, client, _) = setup(&env); + let (first_proof, first_public_signals) = registration_fixture(&env, 0, 42, 7); + let (second_proof, second_public_signals) = registration_fixture(&env, 42, 99, 8); - let result = env.try_invoke_contract::<(), soroban_sdk::Error>( - &contract_id, - &Symbol::new(&env, "update_smt_root"), - (root,).into_val(&env), - ); + client.submit_proof(&first_proof, &first_public_signals); + client.submit_proof(&second_proof, &second_public_signals); - assert!(result.is_err()); + assert_eq!(client.get_root(), Some(second_public_signals.new_root)); + assert!(client.has_commitment(&first_public_signals.commitment)); + assert!(client.has_commitment(&second_public_signals.commitment)); } diff --git a/gateway-contract/contracts/core_contract/src/types.rs b/gateway-contract/contracts/core_contract/src/types.rs index 836766c2..9c257fd1 100644 --- a/gateway-contract/contracts/core_contract/src/types.rs +++ b/gateway-contract/contracts/core_contract/src/types.rs @@ -1,16 +1,19 @@ -use soroban_sdk::{contracttype, Address, BytesN, Symbol}; +use soroban_sdk::{contracttype, BytesN}; #[contracttype] -#[derive(Clone)] -pub struct AddressMetadata { - pub label: Symbol, +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Proof { + pub a: BytesN<32>, + pub b: BytesN<32>, + pub c: BytesN<32>, } #[contracttype] -#[derive(Clone)] -pub struct ResolveData { - pub wallet: Address, - pub memo: Option, +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PublicSignals { + pub old_root: BytesN<32>, + pub new_root: BytesN<32>, + pub commitment: BytesN<32>, } #[contracttype] @@ -28,13 +31,3 @@ pub enum PrivacyMode { Normal, Private, } - -/// Public signals extracted from a Groth16 non-inclusion proof. -/// `old_root` must match the current on-chain SMT root. -/// `new_root` becomes the new SMT root after a successful registration. -#[contracttype] -#[derive(Clone)] -pub struct PublicSignals { - pub old_root: BytesN<32>, - pub new_root: BytesN<32>, -}