Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gateway-contract/contracts/core_contract/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ use soroban_sdk::contracterror;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ContractError {
AlreadyInitialized = 1,
NotInitialized = 2,
RootMismatch = 3,
InvalidProof = 4,
DuplicateCommitment = 5,
#[repr(u32)]
pub enum CoreError {
/// The requested resource was not found.
Expand Down
25 changes: 24 additions & 1 deletion gateway-contract/contracts/core_contract/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
#![allow(dead_code)]
use soroban_sdk::{contractevent, BytesN};

#[contractevent]
pub struct UsernameRegistered {
pub commitment: BytesN<32>,
}

#[contractevent]
pub struct MerkleRootUpdated {
pub old_root: BytesN<32>,
pub new_root: BytesN<32>,
}

use soroban_sdk::{symbol_short, Symbol};

#[allow(dead_code)]
pub const INIT_EVENT: Symbol = symbol_short!("INIT");
#[allow(dead_code)]
pub const TRANSFER_EVENT: Symbol = symbol_short!("TRANSFER");
#[allow(dead_code)]
pub const REGISTER_EVENT: Symbol = symbol_short!("REGISTER");
#[allow(dead_code)]
pub const ROOT_UPDATED: Symbol = symbol_short!("ROOT_UPD");
#[allow(dead_code)]
pub const MASTER_SET: Symbol = symbol_short!("MSTR_SET");
#[allow(dead_code)]
pub const ADDR_ADDED: Symbol = symbol_short!("ADDR_ADD");
#[allow(dead_code)]
pub const CHAIN_ADD: Symbol = symbol_short!("CHAIN_ADD");
#[allow(dead_code)]
pub const CHAIN_REM: Symbol = symbol_short!("CHAIN_REM");
#[allow(dead_code)]
pub const VAULT_CREATE: Symbol = symbol_short!("VAULT_CRT");
#[allow(dead_code)]
pub const DEPOSIT: Symbol = symbol_short!("DEPOSIT");
#[allow(dead_code)]
pub const WITHDRAW: Symbol = symbol_short!("WITHDRAW");
#[allow(dead_code)]
pub const SCHED_PAY: Symbol = symbol_short!("SCHED_PAY");
73 changes: 72 additions & 1 deletion gateway-contract/contracts/core_contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#![no_std]

mod errors;
mod events;
mod storage;
mod types;
pub mod address_manager;
pub mod errors;
pub mod events;
Expand All @@ -12,6 +16,11 @@ pub mod zk_verifier;
#[cfg(test)]
mod test;

use soroban_sdk::{contract, contractclient, contractimpl, panic_with_error, Address, BytesN, Env};

pub use crate::errors::ContractError;
pub use crate::events::{MerkleRootUpdated, UsernameRegistered};
pub use crate::types::{Proof, PublicSignals};
use address_manager::AddressManager;
use errors::CoreError;
use events::REGISTER_EVENT;
Expand All @@ -20,8 +29,52 @@ use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Bytes, Byte
use types::{ChainType, PublicSignals, ResolveData};

#[contract]
pub struct Contract;
pub struct CoreContract;

#[contractclient(name = "VerifierContractClient")]
pub trait VerifierContract {
fn verify_proof(env: Env, proof: Proof, public_signals: PublicSignals) -> bool;
}

fn current_merkle_root(env: &Env) -> BytesN<32> {
storage::get_merkle_root(env)
.unwrap_or_else(|| panic_with_error!(env, ContractError::NotInitialized))
}

fn update_merkle_root(env: &Env, old_root: BytesN<32>, new_root: BytesN<32>) {
storage::set_merkle_root(env, &new_root);

MerkleRootUpdated { old_root, new_root }.publish(env);
}

#[contractimpl]
impl CoreContract {
pub fn init(env: Env, verifier: Address, root: BytesN<32>) {
if storage::is_initialized(&env) {
panic_with_error!(&env, ContractError::AlreadyInitialized);
}

storage::set_verifier(&env, &verifier);
storage::set_merkle_root(&env, &root);
}

pub fn submit_proof(env: Env, proof: Proof, public_signals: PublicSignals) {
let current_root = current_merkle_root(&env);

if current_root != public_signals.old_root.clone() {
panic_with_error!(&env, ContractError::RootMismatch);
}

if storage::has_commitment(&env, &public_signals.commitment) {
panic_with_error!(&env, ContractError::DuplicateCommitment);
}

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);
#[contractimpl]
impl Contract {
/// Get the current SMT root.
Expand Down Expand Up @@ -106,8 +159,26 @@ impl Contract {
Some(data) => (data.wallet, data.memo),
None => panic_with_error!(&env, CoreError::NotFound),
}

storage::store_commitment(&env, &public_signals.commitment);
update_merkle_root(&env, current_root, public_signals.new_root.clone());

UsernameRegistered {
commitment: public_signals.commitment,
}
.publish(&env);
}

pub fn get_merkle_root(env: Env) -> BytesN<32> {
current_merkle_root(&env)
}

pub fn get_verifier(env: Env) -> Option<Address> {
storage::get_verifier(&env)
}

pub fn has_commitment(env: Env, commitment: BytesN<32>) -> bool {
storage::has_commitment(&env, &commitment)
/// Register a username commitment, mapping it to the caller's address.
pub fn register(env: Env, caller: Address, commitment: BytesN<32>) {
Registration::register(env, caller, commitment);
Expand Down
43 changes: 43 additions & 0 deletions gateway-contract/contracts/core_contract/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
use soroban_sdk::{contracttype, Address, BytesN, Env};

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
CurrentMerkleRoot,
Verifier,
Commitment(BytesN<32>),
}

pub fn is_initialized(env: &Env) -> bool {
env.storage().persistent().has(&DataKey::CurrentMerkleRoot)
&& env.storage().instance().has(&DataKey::Verifier)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail closed on partial initialization state.

is_initialized() currently returns false unless both keys exist, but only one missing key is enough to make a second init() dangerous. If the verifier entry ever disappears while the persistent root remains, init() can overwrite the anchored root outside the proof flow. At minimum, treat either key as “already initialized”; ideally keep the verifier in the same durability class as the root.

🛠️ Minimal fail-closed fix
 pub fn is_initialized(env: &Env) -> bool {
     env.storage().persistent().has(&DataKey::CurrentMerkleRoot)
-        && env.storage().instance().has(&DataKey::Verifier)
+        || env.storage().instance().has(&DataKey::Verifier)
 }

Also applies to: 26-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/storage.rs` around lines 11 -
13, The current is_initialized function uses a logical AND so it returns false
if either DataKey::CurrentMerkleRoot or DataKey::Verifier is missing, which
allows init() to run when one of the two is present — change the check in
is_initialized (function name: is_initialized) to treat the presence of either
key as “already initialized” (use logical OR between
env.storage().persistent().has(&DataKey::CurrentMerkleRoot) and
env.storage().instance().has(&DataKey::Verifier)) so init() will fail-closed;
additionally consider moving the Verifier entry to persistent storage (make
DataKey::Verifier stored via env.storage().persistent()) so both items share
durability to avoid asymmetric deletion risks.

}

pub fn get_merkle_root(env: &Env) -> Option<BytesN<32>> {
env.storage().persistent().get(&DataKey::CurrentMerkleRoot)
}

pub fn set_merkle_root(env: &Env, root: &BytesN<32>) {
env.storage()
.persistent()
.set(&DataKey::CurrentMerkleRoot, root);
}

pub fn get_verifier(env: &Env) -> Option<Address> {
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);
use soroban_sdk::{contracttype, BytesN};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing closing brace for store_commitment function causes parse error.

The store_commitment function starting at line 40 is missing its closing }. Line 44 shows old code (use soroban_sdk::{contracttype, BytesN};) that was incorrectly merged into the function body.

🐛 Proposed fix to close function and remove duplicate code
 pub fn store_commitment(env: &Env, commitment: &BytesN<32>) {
     env.storage()
         .persistent()
         .set(&DataKey::Commitment(commitment.clone()), &true);
+}
-use soroban_sdk::{contracttype, BytesN};
-
-/// Storage keys for the Core contract's persistent and instance storage.
-#[contracttype]
-#[derive(Clone, Debug, Eq, PartialEq)]
-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>),
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/storage.rs` around lines 40 -
44, The function store_commitment is missing its closing brace and a stray
import line was accidentally merged into the function body; close the function
by adding the missing "}" after the env.storage()...set(...) statement and
remove the duplicated import line `use soroban_sdk::{contracttype, BytesN};`
from inside the function so the import remains at module scope and the function
ends cleanly (refer to function name store_commitment and the
DataKey::Commitment usage to locate the block).


/// Storage keys for the Core contract's persistent and instance storage.
Expand Down
Loading
Loading