Skip to content

feat(core): contract root anchoring#134

Open
Obiajulu-gif wants to merge 11 commits intoAlien-Protocol:mainfrom
Obiajulu-gif:feat/contract-root-anchoring
Open

feat(core): contract root anchoring#134
Obiajulu-gif wants to merge 11 commits intoAlien-Protocol:mainfrom
Obiajulu-gif:feat/contract-root-anchoring

Conversation

@Obiajulu-gif
Copy link
Copy Markdown

@Obiajulu-gif Obiajulu-gif commented Mar 24, 2026

This pull request refactors and extends the core_contract for the gateway system to implement a Merkle root–based registration flow with proof verification, event emission, and robust storage management. The contract now supports zero-knowledge proof–based username registration, tracks commitments, manages Merkle roots, and emits events for state changes. Comprehensive tests are added to ensure correct behavior and error handling.

Key changes:

Core Contract Redesign and Features

  • Completely rewrote the main contract logic in lib.rs to support initialization, proof submission with Merkle root and commitment checks, integration with an external verifier contract, and event emission on state changes. The contract now exposes methods for initialization, proof submission, root and verifier retrieval, and commitment existence checks.
  • Defined a new error enum ContractError for robust error handling (e.g., already initialized, not initialized, root mismatch, invalid proof, duplicate commitment).
  • Introduced new event types UsernameRegistered and MerkleRootUpdated for better observability of contract state changes.

Storage Layer Refactor

  • Added a new storage.rs module to encapsulate all persistent state management, including Merkle root, verifier address, and commitment tracking, using a typed key system.

Type Definitions

  • Introduced new types Proof and PublicSignals to model the data required for proof verification and registration.

Comprehensive Testing

  • Added a new test.rs module with extensive test coverage for initialization, proof submission, error conditions (invalid proof, stale root, duplicate commitment), and event emission, using a mock verifier contract for simulation.

Closes #56

Summary by CodeRabbit

  • New Features

    • Added contract initialization and proof-submission flow, verifier configuration, and merkle-root management
    • New public queries to read verifier, merkle root, and commitment presence
    • Emits events for username registration and merkle-root updates
  • Bug Fixes / Error Handling

    • Explicit error cases for already-initialized, not-initialized, stale root, invalid proof, and duplicate submission
  • Removed

    • Removed previous resolver registration and lookup behavior
  • Tests

    • Added comprehensive tests covering init, proof submission, state updates, events, and error paths

@drips-wave
Copy link
Copy Markdown

drips-wave bot commented Mar 24, 2026

@Obiajulu-gif Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a CoreContract with init and submit_proof flows: persistent Merkle root and verifier storage, ZK-proof verification via a VerifierContract client, commitment deduplication, atomic root updates, contract events, new error enum, storage/type helpers, and an expanded test harness.

Changes

Cohort / File(s) Summary
Core contract entry & API
gateway-contract/contracts/core_contract/src/lib.rs
Introduced CoreContract (renamed from Contract), added entrypoints: init, submit_proof, get_merkle_root, get_verifier, has_commitment; added VerifierContract client trait; re-exported new errors/events/types; new internal helpers for root update and event emission.
Errors
gateway-contract/contracts/core_contract/src/errors.rs
Added #[contracterror] pub enum ContractError with explicit discriminants: AlreadyInitialized = 1, NotInitialized = 2, RootMismatch = 3, InvalidProof = 4, DuplicateCommitment = 5.
Events
gateway-contract/contracts/core_contract/src/events.rs
Added #[contractevent] structs: UsernameRegistered { commitment: BytesN<32> } and MerkleRootUpdated { old_root: BytesN<32>, new_root: BytesN<32> }; adjusted dead-code allowances on existing Symbol constants.
Storage helpers & keys
gateway-contract/contracts/core_contract/src/storage.rs
Added #[contracttype] pub enum DataKey (CurrentMerkleRoot, Verifier, Commitment(BytesN<32>)) and storage helpers: is_initialized, get_merkle_root/set_merkle_root, get_verifier/set_verifier, has_commitment, store_commitment. (Note: duplicate DataKey declarations appear in-file.)
Types
gateway-contract/contracts/core_contract/src/types.rs
Removed AddressMetadata/ResolveData. Added Proof { a,b,c: BytesN<32> } and expanded PublicSignals { old_root, new_root, commitment: BytesN<32> } with Clone, Debug, Eq, PartialEq. (Note: duplicate PublicSignals appears later in file.)
Tests & mock verifier
gateway-contract/contracts/core_contract/src/test.rs
Added MockVerifierContract (test-only) with set_should_verify and verify_proof; comprehensive tests covering init, successful/failed submit_proof flows, root progression, duplicate/stale checks, re-init guard, and rejection of direct root override.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Core as CoreContract
  participant Storage as Storage
  participant Verifier as VerifierContract
  participant Events as Events

  Client->>Core: submit_proof(proof, public_signals)
  Core->>Storage: get_verifier()
  Core->>Storage: get_merkle_root()
  Core->>Storage: has_commitment(public_signals.commitment)
  alt verifier not set
    Core-->>Client: Err(NotInitialized)
  else commitment exists
    Core-->>Client: Err(DuplicateCommitment)
  else old_root mismatch
    Core-->>Client: Err(RootMismatch)
  else
    Core->>Verifier: verify_proof(proof, public_signals)
    alt verifier returns false
      Core-->>Client: Err(InvalidProof)
    else verifier returns true
      Core->>Storage: store_commitment(public_signals.commitment)
      Core->>Storage: set_merkle_root(public_signals.new_root)
      Core->>Events: emit MerkleRootUpdated(old_root,new_root)
      Core->>Events: emit UsernameRegistered(commitment)
      Core-->>Client: Success
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

Approved

Suggested reviewers

  • ryzen-xp

Poem

"I hopped through bytes and roots so deep, 🐇
I guarded proofs while others sleep,
I nudged the root with careful art,
I stored a name and played my part,
A tiny hop — a registry keeps."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed All coding requirements from issue #56 are met: Merkle root persistence [storage.rs], get_merkle_root() entry point [lib.rs], atomic updates after verified proof [lib.rs], MerkleRootUpdated event emission [events.rs, lib.rs], protection against unauthorized overrides [lib.rs, test.rs], and comprehensive test coverage [test.rs].
Out of Scope Changes check ✅ Passed Changes to types.rs (Proof/PublicSignals instead of AddressMetadata/ResolveData) and comprehensive errors enum are necessary supporting infrastructure for the Merkle root anchoring feature and proof verification flow.
Title check ✅ Passed The title 'feat(core): contract root anchoring' accurately describes the main change: implementing Merkle root anchoring in the core contract with initialization, verification, and storage management.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gateway-contract/contracts/core_contract/src/lib.rs`:
- Around line 47-67: submit_proof performs root and commitment checks before
calling out to the external verifier (VerifierContractClient::verify_proof)
which allows reentrancy and also doesn't map verifier errors; fix by either
adding a reentrancy guard around the verify_proof call or by re-reading and
re-checking current_merkle_root(&env) and storage::has_commitment(&env,
&public_signals.commitment) immediately after verify_proof returns, and ensure
the verifier invocation uses the fallible/try-style API (map the Result/err to a
ContractError like ContractError::VerifierError instead of letting raw panics
propagate). Update relevant symbols: submit_proof,
VerifierContractClient::verify_proof, storage::get_verifier,
current_merkle_root, storage::has_commitment, storage::store_commitment,
update_merkle_root.
- Around line 38-45: The init() entrypoint must require authorization before
mutating state: call env.require_auth(...) at the start of init to restrict who
can initialize (either require_auth(&env, &verifier) if the verifier is the
trusted deployer, or add an explicit deployer Address parameter and call
env.require_auth(&deployer)); keep the existing storage::is_initialized check
and ContractError::AlreadyInitialized panic, and only call storage::set_verifier
and storage::set_merkle_root after the require_auth check succeeds.

In `@gateway-contract/contracts/core_contract/src/storage.rs`:
- Around line 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 495b0612-1475-4948-a0bf-1748e1c9f050

📥 Commits

Reviewing files that changed from the base of the PR and between da709e0 and 675bb76.

📒 Files selected for processing (6)
  • gateway-contract/contracts/core_contract/src/errors.rs
  • gateway-contract/contracts/core_contract/src/events.rs
  • gateway-contract/contracts/core_contract/src/lib.rs
  • gateway-contract/contracts/core_contract/src/storage.rs
  • gateway-contract/contracts/core_contract/src/test.rs
  • gateway-contract/contracts/core_contract/src/types.rs

Comment on lines 38 to 45
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(&key, &data);
storage::set_verifier(&env, &verifier);
storage::set_merkle_root(&env, &root);
}
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

🧩 Analysis chain

🏁 Script executed:

cd gateway-contract && find . -type f -name "*.rs" | head -20

Repository: Alien-Protocol/Alien-Gateway

Length of output: 896


🏁 Script executed:

cd gateway-contract && rg "fn init" --type rust -B 2 -A 15

Repository: Alien-Protocol/Alien-Gateway

Length of output: 2467


🏁 Script executed:

cd gateway-contract && rg "invoke_contract_check_host_approval|__check_auth" --type rust

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && git log --oneline --all | head -20

Repository: Alien-Protocol/Alien-Gateway

Length of output: 1611


🏁 Script executed:

cd gateway-contract && find . -type f \( -name "*.toml" -o -name "*.md" \) | head -20

Repository: Alien-Protocol/Alien-Gateway

Length of output: 404


🏁 Script executed:

cd gateway-contract && sed -n '1,100p' contracts/core_contract/src/test.rs | grep -A 20 "fn setup"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 766


🏁 Script executed:

cd gateway-contract && rg "fn setup" contracts/core_contract/src/test.rs -A 25

Repository: Alien-Protocol/Alien-Gateway

Length of output: 884


🏁 Script executed:

cd gateway-contract && cat -n contracts/core_contract/src/storage.rs | head -80

Repository: Alien-Protocol/Alien-Gateway

Length of output: 1558


🏁 Script executed:

cd gateway-contract && rg "is_initialized\|initialization" contracts/core_contract/src/ -B 2 -A 3

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && cat contracts/core_contract/Cargo.toml | grep -A 10 "soroban"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 187


🏁 Script executed:

cd gateway-contract && rg "contractimpl|contract_fn|soroban_sdk" contracts/core_contract/src/lib.rs | head -20

Repository: Alien-Protocol/Alien-Gateway

Length of output: 186


🏁 Script executed:

cd gateway-contract && find . -type f -name "*.rs" -exec grep -l "deploy\|register" {} \; | head -10

Repository: Alien-Protocol/Alien-Gateway

Length of output: 195


🏁 Script executed:

cd gateway-contract && cat -n contracts/core_contract/src/lib.rs | sed -n '1,70p'

Repository: Alien-Protocol/Alien-Gateway

Length of output: 2839


🏁 Script executed:

cd gateway-contract && rg "require_auth\|require\|auth" contracts/core_contract/src/lib.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && find . -type f -name "*.rs" -path "*/factory*" -o -name "*deploy*" | xargs cat 2>/dev/null | head -100

Repository: Alien-Protocol/Alien-Gateway

Length of output: 207


🏁 Script executed:

cd gateway-contract && cat contracts/core_contract/core.md 2>/dev/null | head -150

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && cat readme.md 2>/dev/null | grep -A 20 -i "init\|deploy\|setup"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && rg "fn init" contracts/ -B 5 -A 20 --type rust | grep -E "(init|pub|auth|require)" | head -30

Repository: Alien-Protocol/Alien-Gateway

Length of output: 1143


🏁 Script executed:

cd gateway-contract && web_search "Soroban public contract function authorization require_auth"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 122


🏁 Script executed:

cd gateway-contract && find . -type f \( -name "*.md" -o -name "*.txt" \) -exec grep -l "init\|deploy" {} \;

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🌐 Web query:

Soroban contract require_auth authorization public functions

💡 Result:

In Soroban, any exported contract function (often called “public”) is callable by anyone unless your code explicitly enforces authorization. Soroban does not auto-restrict access based on “public/private”; you enforce permissions by calling require_auth (or require_auth_for_args) on the Address that must approve the action. [1][9]

How require_auth works (what it guarantees)

  • some_address.require_auth() tells the Soroban host: “this invocation must be authorized by some_address”.
  • The host then handles signature verification + replay prevention (you don’t manually verify signatures in the contract). [9]
  • If some_address is a contract address (account abstraction), the host calls that contract’s __check_auth hook to validate the authorization. [1]

Pattern for “public” functions that must be protected

Call require_auth() at the start of the function for the party whose rights are being exercised:

pub fn withdraw(env: Env, from: Address, amount: i128) {
    from.require_auth();          // must be authorized by `from`
    // ... mutate balances / transfer tokens ...
}

This is the core pattern used in Soroban auth examples. [2]

require_auth_for_args (more specific authorization)

Use require_auth_for_args(...) when you want the signature to be bound to specific arguments / intent, which enables safer delegation patterns (e.g., delegated signers / smart accounts authorizing particular calls). [3]

Cross-contract calls and “sub-invocation” authorization

If your contract calls another contract that does require_auth, Soroban’s auth framework supports authorizing those sub-contract calls as part of the same authorized invocation tree (you generally don’t need special wiring beyond using require_auth). [1]

Practical “when do I require auth?” rule of thumb

  • Read-only access to a user’s data: usually no require_auth needed.
  • Mutating a user’s state in a way that isn’t strictly beneficial (e.g., decreasing balance / transferring out): you should require_auth. [1]

Sources: Stellar authorization docs and examples. [1][2][3][9]


🏁 Script executed:

cd gateway-contract && cat -n contracts/core_contract/src/lib.rs | sed -n '30,80p'

Repository: Alien-Protocol/Alien-Gateway

Length of output: 2224


🏁 Script executed:

cd gateway-contract && rg "TODO\|FIXME\|XXX\|HACK\|NOTE" contracts/core_contract/src/lib.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && git log --all --oneline -- contracts/core_contract/src/lib.rs | head -10

Repository: Alien-Protocol/Alien-Gateway

Length of output: 474


🏁 Script executed:

cd gateway-contract && git show d864384 | grep -A 5 -B 5 "init\|auth" | head -40

Repository: Alien-Protocol/Alien-Gateway

Length of output: 2151


🏁 Script executed:

cd gateway-contract && git show d864384 | grep -A 50 "init\|Entry point" | head -80

Repository: Alien-Protocol/Alien-Gateway

Length of output: 4103


🏁 Script executed:

cd gateway-contract && git show d864384 | grep -B 5 -A 5 "^| \`init\`"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && git show d864384 | grep -C 10 "core_contract" | grep -A 30 "Entry point"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 2951


🏁 Script executed:

cd gateway-contract && git show d864384:contracts/core_contract/src/lib.rs 2>/dev/null | head -100

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && rg "fn init\|pub fn " contracts/core_contract/src/lib.rs | head -15

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

cd gateway-contract && git log --all --oneline --diff-filter=A -- contracts/core_contract/src/lib.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 183


🏁 Script executed:

cd gateway-contract && wc -l contracts/core_contract/src/lib.rs && grep -c "pub fn" contracts/core_contract/src/lib.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 111


🏁 Script executed:

cd gateway-contract && cat -n contracts/core_contract/src/lib.rs | grep "pub fn"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 430


Add require_auth() to gate initialization to a trusted deployer.

This entrypoint lacks authorization checks, so any caller can invoke init() first and permanently set both the verifier and initial merkle root. If deployment and initialization are separate transactions, a frontrunner can anchor arbitrary state before the intended owner. Call require_auth() on a trusted deployer address, or ensure initialization is atomic with contract deployment.

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

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 38 - 45,
The init() entrypoint must require authorization before mutating state: call
env.require_auth(...) at the start of init to restrict who can initialize
(either require_auth(&env, &verifier) if the verifier is the trusted deployer,
or add an explicit deployer Address parameter and call
env.require_auth(&deployer)); keep the existing storage::is_initialized check
and ContractError::AlreadyInitialized panic, and only call storage::set_verifier
and storage::set_merkle_root after the require_auth check succeeds.

Comment on lines +47 to +67
pub fn submit_proof(env: Env, proof: Proof, public_signals: PublicSignals) {
let current_root = current_merkle_root(&env);

match env.storage().persistent().get::<DataKey, ResolveData>(&key) {
Some(data) => data,
None => panic_with_error!(&env, ResolverError::NotFound),
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);
}

storage::store_commitment(&env, &public_signals.commitment);
update_merkle_root(&env, current_root, public_signals.new_root.clone());
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

🧩 Analysis chain

🏁 Script executed:

# First, let's explore the repository structure
find . -type f -name "*.rs" | grep -E "(lib|contract)" | head -20

Repository: Alien-Protocol/Alien-Gateway

Length of output: 1236


🏁 Script executed:

# Check the exact file and lines mentioned
if [ -f "gateway-contract/contracts/core_contract/src/lib.rs" ]; then
  wc -l gateway-contract/contracts/core_contract/src/lib.rs
fi

Repository: Alien-Protocol/Alien-Gateway

Length of output: 126


🏁 Script executed:

# Read the file around the specified lines
if [ -f "gateway-contract/contracts/core_contract/src/lib.rs" ]; then
  head -80 gateway-contract/contracts/core_contract/src/lib.rs | tail -40
fi

Repository: Alien-Protocol/Alien-Gateway

Length of output: 1463


🏁 Script executed:

# Search for reentrancy guard implementations in the codebase
rg "reentrancy|Reentrancy|re_entrant|ReEntrant" --type rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 98


🏁 Script executed:

# Look for any mutex or lock patterns that might be reentrancy guards
rg "Mutex|RwLock|atomic" gateway-contract/contracts/core_contract/src/ --type rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 98


🏁 Script executed:

# Check error handling in verifier calls - search for try_ patterns
rg "try_invoke|try_call|\.try_" gateway-contract/contracts/core_contract/src/ --type rs -A 3

Repository: Alien-Protocol/Alien-Gateway

Length of output: 98


🏁 Script executed:

# Search for reentrancy patterns without type restriction
rg "reentrancy|Reentrancy|REENTRANCY" gateway-contract/

Repository: Alien-Protocol/Alien-Gateway

Length of output: 206


🏁 Script executed:

# Look at the full submit_proof function and surrounding context
cat -n gateway-contract/contracts/core_contract/src/lib.rs | sed -n '40,75p'

Repository: Alien-Protocol/Alien-Gateway

Length of output: 1655


🏁 Script executed:

# Search for VerifierContractClient definition and available methods
rg "VerifierContractClient|verify_proof" gateway-contract/ -A 5 -B 2

Repository: Alien-Protocol/Alien-Gateway

Length of output: 4213


🏁 Script executed:

# Check if there's a try_verify_proof method
rg "try_verify_proof|try_" gateway-contract/contracts/core_contract/src/lib.rs -B 2 -A 2

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

# Search for reentrancy guard patterns more broadly
rg "Mutex|RwLock|guard|locked|re_enter" gateway-contract/contracts/core_contract/ -i

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

# Check the storage module for any reentrancy guards on state
cat -n gateway-contract/contracts/core_contract/src/storage.rs | head -50

Repository: Alien-Protocol/Alien-Gateway

Length of output: 1558


🏁 Script executed:

# Look for any comments or patterns related to reentrancy in the entire core_contract
grep -r "reenter\|re-enter\|reentr" gateway-contract/contracts/core_contract/ -i

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

# Check how panic_with_error is defined
rg "macro_rules! panic_with_error|fn panic_with_error" gateway-contract/ -A 5

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

# Look at the error types
cat -n gateway-contract/contracts/core_contract/src/errors.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 395


🏁 Script executed:

# Check if there are try_ methods in the contract
rg "try_submit_proof|pub fn" gateway-contract/contracts/core_contract/src/lib.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 395


🏁 Script executed:

# Look for try_ invocation patterns in Soroban
rg "try_invoke|try_call" gateway-contract/ -B 2 -A 2

Repository: Alien-Protocol/Alien-Gateway

Length of output: 515


🏁 Script executed:

# Check the full context of how verifier_client is used - look for all invocations
rg "verifier_client\." gateway-contract/contracts/core_contract/src/ -B 1 -A 1

Repository: Alien-Protocol/Alien-Gateway

Length of output: 980


🏁 Script executed:

# Let's check if there's a try_submit_proof method or any try_ variant methods
cat -n gateway-contract/contracts/core_contract/src/lib.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 3313


🏁 Script executed:

# Search for how other contracts handle verifier calls to see if there's a pattern
rg "verify_proof|verifier_client" gateway-contract/ -B 3 -A 3

Repository: Alien-Protocol/Alien-Gateway

Length of output: 3686


🏁 Script executed:

# Check if there are any contract test cases that demonstrate reentrancy behavior
cat -n gateway-contract/contracts/core_contract/src/test.rs | grep -A 20 "reenter\|reentr" -i

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


🏁 Script executed:

# Look for the complete lib.rs to see all public functions and their signatures
cat -n gateway-contract/contracts/core_contract/src/lib.rs | grep -E "pub fn|pub trait"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 466


🏁 Script executed:

# Check if there are any reentrancy guard patterns in other contracts
cat gateway-contract/contracts/escrow_contract/src/lib.rs | head -100

Repository: Alien-Protocol/Alien-Gateway

Length of output: 3159


🏁 Script executed:

# Let's check the test to understand the behavior better
cat -n gateway-contract/contracts/core_contract/src/test.rs | head -100

Repository: Alien-Protocol/Alien-Gateway

Length of output: 3389


🏁 Script executed:

# Search for any context/mutex/lock patterns in the Soroban SDK usage
rg "env\.|storage\(\)" gateway-contract/contracts/core_contract/src/lib.rs | head -20

Repository: Alien-Protocol/Alien-Gateway

Length of output: 54


Add reentrancy protection or re-validate state after the verifier call.

The root and duplicate-commitment checks (lines 50–56) execute before the external verifier call (line 61), then their results are trusted without re-validation. A malicious verifier that reenters submit_proof can invalidate those checks. Additionally, the direct verify_proof() invocation on line 61 does not use try_ error handling, so verifier panics or errors will leak raw invoke errors instead of mapping to ContractError.

Either (1) re-read current_root and recheck has_commitment after the verifier returns, or (2) add a reentrancy guard to prevent reentry during the verification call. Explicitly map any verifier invocation errors to ContractError variants.

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

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 47 - 67,
submit_proof performs root and commitment checks before calling out to the
external verifier (VerifierContractClient::verify_proof) which allows reentrancy
and also doesn't map verifier errors; fix by either adding a reentrancy guard
around the verify_proof call or by re-reading and re-checking
current_merkle_root(&env) and storage::has_commitment(&env,
&public_signals.commitment) immediately after verify_proof returns, and ensure
the verifier invocation uses the fallible/try-style API (map the Result/err to a
ContractError like ContractError::VerifierError instead of letting raw panics
propagate). Update relevant symbols: submit_proof,
VerifierContractClient::verify_proof, storage::get_verifier,
current_merkle_root, storage::has_commitment, storage::store_commitment,
update_merkle_root.

Comment on lines +11 to +13
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.

@Obiajulu-gif Obiajulu-gif force-pushed the feat/contract-root-anchoring branch from 675bb76 to 1583021 Compare March 24, 2026 16:44
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
gateway-contract/contracts/core_contract/src/storage.rs (1)

26-32: ⚠️ Potential issue | 🟠 Major

Verifier uses instance storage while root uses persistent storage.

Instance storage and persistent storage have different archival/TTL behaviors. If the contract instance is archived and restored, or if TTLs differ, the verifier entry could be lost while the Merkle root remains—breaking is_initialized() checks and potentially allowing init() to be called again. Consider using persistent storage for both to ensure consistent durability.

🛠️ Proposed fix to use persistent storage for verifier
 pub fn get_verifier(env: &Env) -> Option<Address> {
-    env.storage().instance().get(&DataKey::Verifier)
+    env.storage().persistent().get(&DataKey::Verifier)
 }

 pub fn set_verifier(env: &Env, verifier: &Address) {
-    env.storage().instance().set(&DataKey::Verifier, verifier);
+    env.storage().persistent().set(&DataKey::Verifier, verifier);
 }
🤖 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 26 -
32, The verifier is stored in instance storage while the Merkle root uses
persistent storage, causing durability mismatch; update get_verifier and
set_verifier to use persistent storage (replace env.storage().instance() with
env.storage().persistent()) when accessing DataKey::Verifier so the verifier
entry shares the same persistence/TTL as the Merkle root and cannot be lost
across archiving/restoration.
🧹 Nitpick comments (1)
gateway-contract/contracts/core_contract/src/storage.rs (1)

34-44: Consider extending TTL for persistent commitment entries.

Persistent storage entries can expire if their TTL is not extended. For long-lived commitment tracking, you may want to call extend_ttl() when storing or accessing commitments to prevent expiration of critical state.

🤖 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 34 -
44, When storing or checking commitments make sure to extend the persistent
entry TTL so commitments don't expire: in store_commitment (and in
has_commitment when a commitment is found) call the persistent storage's
extend_ttl for the same DataKey::Commitment(commitment.clone()) after
setting/reading it; add a clear TTL constant (e.g. COMMITMENT_TTL) and use that
value for extend_ttl so the lifetime is explicit and maintainable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gateway-contract/contracts/core_contract/src/lib.rs`:
- Around line 16-20: Remove the duplicate module and import declarations that
cause E0428/E0252: delete the extra `pub mod events;` (duplicate of `mod
events;`) and remove the redundant `use soroban_sdk::{contract, contracterror,
contractimpl, contracttype, panic_with_error, Address, BytesN, Env};` duplicate
import so only the intended single `mod events;` and one `use
soroban_sdk::{...}` remain in the file; ensure no other duplicate `mod events`
or identical `use soroban_sdk` blocks remain.

---

Duplicate comments:
In `@gateway-contract/contracts/core_contract/src/storage.rs`:
- Around line 26-32: The verifier is stored in instance storage while the Merkle
root uses persistent storage, causing durability mismatch; update get_verifier
and set_verifier to use persistent storage (replace env.storage().instance()
with env.storage().persistent()) when accessing DataKey::Verifier so the
verifier entry shares the same persistence/TTL as the Merkle root and cannot be
lost across archiving/restoration.

---

Nitpick comments:
In `@gateway-contract/contracts/core_contract/src/storage.rs`:
- Around line 34-44: When storing or checking commitments make sure to extend
the persistent entry TTL so commitments don't expire: in store_commitment (and
in has_commitment when a commitment is found) call the persistent storage's
extend_ttl for the same DataKey::Commitment(commitment.clone()) after
setting/reading it; add a clear TTL constant (e.g. COMMITMENT_TTL) and use that
value for extend_ttl so the lifetime is explicit and maintainable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9f99da82-83a3-40b1-a936-bcbc850148f0

📥 Commits

Reviewing files that changed from the base of the PR and between 675bb76 and 682aae2.

📒 Files selected for processing (6)
  • gateway-contract/contracts/core_contract/src/errors.rs
  • gateway-contract/contracts/core_contract/src/events.rs
  • gateway-contract/contracts/core_contract/src/lib.rs
  • gateway-contract/contracts/core_contract/src/storage.rs
  • gateway-contract/contracts/core_contract/src/test.rs
  • gateway-contract/contracts/core_contract/src/types.rs
✅ Files skipped from review due to trivial changes (1)
  • gateway-contract/contracts/core_contract/src/errors.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • gateway-contract/contracts/core_contract/src/events.rs
  • gateway-contract/contracts/core_contract/src/types.rs
  • gateway-contract/contracts/core_contract/src/test.rs

@ryzen-xp ryzen-xp self-requested a review March 24, 2026 17:05
@ryzen-xp ryzen-xp added the CI Failed Please check why you CI is faileing fix your code label Mar 24, 2026
@ryzen-xp ryzen-xp moved this from Todo to In Progress in Drip Wave Mar 24, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
gateway-contract/contracts/core_contract/src/lib.rs (2)

117-118: ⚠️ Potential issue | 🔴 Critical

Remove duplicate test module declaration.

The test module is already declared at lines 8-9. This duplicate declaration at lines 117-118 will cause compilation error E0428 (module defined multiple times).

🐛 Proposed fix
     }
 }
-
-#[cfg(test)]
-mod test;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 117 - 118,
There is a duplicate test module declaration `mod test;` in lib.rs causing
duplicate definition error E0428; remove the redundant `mod test;` at the later
location (the one around lines 117-118) so only the original `#[cfg(test)] mod
test;` declaration remains (the earlier declaration at lines 8-9), ensuring the
`test` module is defined only once.

80-102: ⚠️ Potential issue | 🔴 Critical

Critical: Remove old resolver functions and fix UsernameRegistered struct initialization.

The submit_proof function is broken by old code inserted between the UsernameRegistered struct initialization (lines 80-81) and its .publish(&env) call (line 101). Lines 82-100 contain old set_memo and resolve functions that must be removed.

🐛 Proposed fix to complete struct and remove old code
         UsernameRegistered {
             commitment: public_signals.commitment,
-    pub fn set_memo(env: Env, commitment: BytesN<32>, memo_id: u64) {
-        let key = DataKey::Resolver(commitment);
-        let mut data = env
-            .storage()
-            .persistent()
-            .get::<DataKey, ResolveData>(&key)
-            .unwrap_or_else(|| panic_with_error!(&env, ResolverError::NotFound));
-
-        data.memo = Some(memo_id);
-        env.storage().persistent().set(&key, &data);
-    }
-
-    pub fn resolve(env: Env, commitment: BytesN<32>) -> (Address, Option<u64>) {
-        let key = DataKey::Resolver(commitment);
-
-        match env.storage().persistent().get::<DataKey, ResolveData>(&key) {
-            Some(data) => (data.wallet, data.memo),
-            None => panic_with_error!(&env, ResolverError::NotFound),
-        }
+        }
         .publish(&env);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 80 - 102,
The UsernameRegistered struct initialization is being interrupted by two stale
functions (set_memo and resolve) which were accidentally inserted between the
struct literal that starts with UsernameRegistered { commitment:
public_signals.commitment, and its .publish(&env) call; remove the entire
set_memo and resolve function blocks (references: set_memo, resolve,
DataKey::Resolver, ResolveData) so the struct literal is completed properly and
the subsequent .publish(&env) is called on the UsernameRegistered instance,
ensuring submit_proof compiles and behaves as intended.
♻️ Duplicate comments (3)
gateway-contract/contracts/core_contract/src/lib.rs (3)

49-56: ⚠️ Potential issue | 🟠 Major

Add require_auth() to gate initialization to a trusted deployer.

The init() function lacks authorization checks, allowing any caller to invoke it first and permanently set both the verifier and initial Merkle root. If deployment and initialization are separate transactions, a frontrunner can anchor arbitrary state before the intended owner.

Consider adding a deployer/admin parameter and requiring authorization:

pub fn init(env: Env, admin: Address, verifier: Address, root: BytesN<32>) {
    admin.require_auth();
    // ... rest of initialization
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 49 - 56,
The init function (init) currently has no authorization and can be called by
anyone; update its signature to accept an admin/deployer Address, call
admin.require_auth() at the start of init, and persist that admin (e.g.,
storage::set_admin) alongside verifier and merkle root so future protected ops
can check authorized caller; ensure the existing storage helpers
(storage::set_verifier, storage::set_merkle_root) remain but add
storage::set_admin (and corresponding storage::is_admin or require_admin checks
elsewhere) and return/handle ContractError::AlreadyInitialized as before.

16-22: ⚠️ Potential issue | 🔴 Critical

Critical: Remove duplicate module declarations and imports blocking compilation.

Lines 16-22 contain old code that conflicts with the new declarations on lines 3-15:

  • pub mod events; and pub mod types; duplicate modules already declared at lines 4 and 6
  • The use soroban_sdk::{...} block duplicates the import on line 11
  • use types::ResolveData; references a type that no longer exists in the new implementation

These cause E0428 (duplicate module) and E0252 (duplicate import) compilation errors.

🐛 Proposed fix to remove duplicate definitions
 pub use crate::types::{Proof, PublicSignals};
-pub mod events;
-pub mod types;
-
-use soroban_sdk::{
-    contract, contracterror, contractimpl, contracttype, panic_with_error, Address, BytesN, Env,
-};
-use types::ResolveData;

 #[contract]
 pub struct CoreContract;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 16 - 22,
Remove the duplicate module and import declarations that cause compilation
errors: delete the redundant "pub mod events;" and "pub mod types;" lines that
duplicate earlier declarations, remove the duplicated "use
soroban_sdk::{contract, contracterror, contractimpl, contracttype,
panic_with_error, Address, BytesN, Env};" import block, and remove the obsolete
"use types::ResolveData;" reference (or replace it with the correct type/import
used by the new implementation) so only the single, intended module and
soroban_sdk imports remain.

69-78: ⚠️ Potential issue | 🟠 Major

Consider reentrancy protection for external verifier call.

The root and duplicate-commitment checks (lines 61-67) execute before the external verifier call (line 72), then state is mutated after. A malicious verifier that reenters submit_proof could potentially bypass these checks since state updates haven't occurred yet.

Consider either:

  1. Adding a reentrancy guard (lock flag in storage)
  2. Storing the commitment before the external call (checks-effects-interactions pattern)
  3. Re-validating state after the verifier returns
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 69 - 78,
The external verifier call via VerifierContractClient::verify_proof is invoked
before state changes, which permits reentrancy; fix by applying one of the
suggested patterns: (A) implement a reentrancy guard in storage (e.g., a boolean
lock flag set at start of submit_proof and cleared at the end) and check it in
entry (update storage::get_verifier usage to set/clear the lock around the
external call), or (B) follow checks-effects-interactions by calling
storage::store_commitment and update_merkle_root before invoking
VerifierContractClient::verify_proof, or (C) re-validate the critical invariants
(current_root and duplicate-commitment checks) immediately after verify_proof
returns before mutating state; reference the symbols storage::store_commitment,
update_merkle_root, VerifierContractClient::verify_proof, and
storage::get_verifier when applying the change.
🧹 Nitpick comments (1)
gateway-contract/contracts/core_contract/src/lib.rs (1)

104-110: Consider consistent error handling for getter functions.

get_merkle_root panics with ContractError::NotInitialized when uninitialized, while get_verifier returns Option<Address>. This inconsistency could confuse callers expecting uniform behavior. Consider either having both return Option or both panic when uninitialized.

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

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 104 - 110,
The two getters are inconsistent: get_merkle_root currently panics via
current_merkle_root (raising ContractError::NotInitialized) while get_verifier
returns Option; make their behavior consistent by choosing one approach — e.g.,
have get_verifier also panic when uninitialized: change its signature to return
Address and replace storage::get_verifier(&env) with a call that
unwraps/expect() to raise ContractError::NotInitialized (or convert the Option
into a Result and propagate the same ContractError), so both get_merkle_root
(which calls current_merkle_root) and get_verifier exhibit the same error
behavior; alternatively, if you prefer optional returns, change get_merkle_root
to return Option<BytesN<32>> and delegate to a storage accessor that returns
Option instead of current_merkle_root.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gateway-contract/contracts/core_contract/src/lib.rs`:
- Around line 37-45: The function update_merkle_root is missing its closing
brace and an old ResolverError enum was accidentally merged into its body; close
the update_merkle_root function after the MerkleRootUpdated.publish(env) call
(add the missing '}') and remove the outdated ResolverError enum block (the pub
enum ResolverError { ... }) so the function contains only the call to
storage::set_merkle_root and MerkleRootUpdated::publish; confirm symbols
referenced are update_merkle_root, storage::set_merkle_root, and
MerkleRootUpdated.

---

Outside diff comments:
In `@gateway-contract/contracts/core_contract/src/lib.rs`:
- Around line 117-118: There is a duplicate test module declaration `mod test;`
in lib.rs causing duplicate definition error E0428; remove the redundant `mod
test;` at the later location (the one around lines 117-118) so only the original
`#[cfg(test)] mod test;` declaration remains (the earlier declaration at lines
8-9), ensuring the `test` module is defined only once.
- Around line 80-102: The UsernameRegistered struct initialization is being
interrupted by two stale functions (set_memo and resolve) which were
accidentally inserted between the struct literal that starts with
UsernameRegistered { commitment: public_signals.commitment, and its
.publish(&env) call; remove the entire set_memo and resolve function blocks
(references: set_memo, resolve, DataKey::Resolver, ResolveData) so the struct
literal is completed properly and the subsequent .publish(&env) is called on the
UsernameRegistered instance, ensuring submit_proof compiles and behaves as
intended.

---

Duplicate comments:
In `@gateway-contract/contracts/core_contract/src/lib.rs`:
- Around line 49-56: The init function (init) currently has no authorization and
can be called by anyone; update its signature to accept an admin/deployer
Address, call admin.require_auth() at the start of init, and persist that admin
(e.g., storage::set_admin) alongside verifier and merkle root so future
protected ops can check authorized caller; ensure the existing storage helpers
(storage::set_verifier, storage::set_merkle_root) remain but add
storage::set_admin (and corresponding storage::is_admin or require_admin checks
elsewhere) and return/handle ContractError::AlreadyInitialized as before.
- Around line 16-22: Remove the duplicate module and import declarations that
cause compilation errors: delete the redundant "pub mod events;" and "pub mod
types;" lines that duplicate earlier declarations, remove the duplicated "use
soroban_sdk::{contract, contracterror, contractimpl, contracttype,
panic_with_error, Address, BytesN, Env};" import block, and remove the obsolete
"use types::ResolveData;" reference (or replace it with the correct type/import
used by the new implementation) so only the single, intended module and
soroban_sdk imports remain.
- Around line 69-78: The external verifier call via
VerifierContractClient::verify_proof is invoked before state changes, which
permits reentrancy; fix by applying one of the suggested patterns: (A) implement
a reentrancy guard in storage (e.g., a boolean lock flag set at start of
submit_proof and cleared at the end) and check it in entry (update
storage::get_verifier usage to set/clear the lock around the external call), or
(B) follow checks-effects-interactions by calling storage::store_commitment and
update_merkle_root before invoking VerifierContractClient::verify_proof, or (C)
re-validate the critical invariants (current_root and duplicate-commitment
checks) immediately after verify_proof returns before mutating state; reference
the symbols storage::store_commitment, update_merkle_root,
VerifierContractClient::verify_proof, and storage::get_verifier when applying
the change.

---

Nitpick comments:
In `@gateway-contract/contracts/core_contract/src/lib.rs`:
- Around line 104-110: The two getters are inconsistent: get_merkle_root
currently panics via current_merkle_root (raising ContractError::NotInitialized)
while get_verifier returns Option; make their behavior consistent by choosing
one approach — e.g., have get_verifier also panic when uninitialized: change its
signature to return Address and replace storage::get_verifier(&env) with a call
that unwraps/expect() to raise ContractError::NotInitialized (or convert the
Option into a Result and propagate the same ContractError), so both
get_merkle_root (which calls current_merkle_root) and get_verifier exhibit the
same error behavior; alternatively, if you prefer optional returns, change
get_merkle_root to return Option<BytesN<32>> and delegate to a storage accessor
that returns Option instead of current_merkle_root.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 806ed1ff-9765-4c89-98a6-269b8b697adb

📥 Commits

Reviewing files that changed from the base of the PR and between 682aae2 and 0522913.

📒 Files selected for processing (3)
  • gateway-contract/contracts/core_contract/src/lib.rs
  • gateway-contract/contracts/core_contract/src/test.rs
  • gateway-contract/contracts/core_contract/src/types.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • gateway-contract/contracts/core_contract/src/types.rs
  • gateway-contract/contracts/core_contract/src/test.rs

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
gateway-contract/contracts/core_contract/src/lib.rs (2)

3-14: ⚠️ Potential issue | 🔴 Critical

Duplicate module declarations cause compilation errors E0428.

Private module declarations (lines 3-6) conflict with public module declarations (lines 7-14):

  • mod errors vs pub mod errors
  • mod events vs pub mod events
  • mod storage vs pub mod storage
  • mod types vs pub mod types

This is merge conflict remnants. Decide on visibility (likely pub mod for external access) and remove duplicates.

🐛 Proposed fix to remove duplicate module declarations
 #![no_std]

-mod errors;
-mod events;
-mod storage;
-mod types;
 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;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 3 - 14, The
file contains duplicate private and public module declarations (mod
errors/events/storage/types vs pub mod errors/events/storage/types) causing
E0428; remove the private declarations and keep the public ones (retain pub mod
address_manager, errors, events, registration, smt_root, storage, types,
zk_verifier) so each module is declared only once and exported for external use.

19-29: ⚠️ Potential issue | 🔴 Critical

Duplicate imports cause compilation errors E0252.

Line 19 and lines 28-29 both import from soroban_sdk with overlapping items. The types module is also imported twice with different items.

🐛 Proposed fix to consolidate imports
-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;
 use registration::Registration;
-use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Bytes, BytesN, Env};
-use types::{ChainType, PublicSignals, ResolveData};
+use soroban_sdk::{contract, contractclient, contractimpl, panic_with_error, Address, Bytes, BytesN, Env};
+use types::{ChainType, Proof, PublicSignals, ResolveData};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/lib.rs` around lines 19 - 29,
There are duplicate imports from soroban_sdk and the types module causing E0252;
consolidate them into a single use declaration (e.g., merge the two soroban_sdk
lines into one that includes contract, contractclient (if needed), contractimpl,
panic_with_error, Address, Bytes, BytesN, Env) and remove the redundant import;
likewise unify the types imports so PublicSignals/Proof/ResolveData/ChainType
come from the correct module (crate::types or types) in one use statement and
remove the other conflicting use. Update the top-level imports (the duplicate
soroban_sdk lines and duplicate types imports) so each symbol appears only once.
gateway-contract/contracts/core_contract/src/test.rs (3)

263-291: ⚠️ Potential issue | 🔴 Critical

Test direct_root_override_entrypoint_is_rejected has merge conflict.

Lines 263-291 show merge conflict remnants where the new test body (lines 263-274) is incorrectly merged with old test code (lines 275-291).

🐛 Proposed fix
 #[test]
 fn direct_root_override_entrypoint_is_rejected() {
     let env = Env::default();
     let (contract_id, client, _) = setup(&env);

     let result = env.try_invoke_contract::<(), InvokeError>(
         &contract_id,
         &Symbol::new(&env, "set_merkle_root"),
         vec![&env, bytes(&env, 9).into_val(&env)],
     );

     assert_eq!(result, Err(Ok(InvokeError::Abort)));
     assert_eq!(client.get_merkle_root(), bytes(&env, 0));
-    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));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/test.rs` around lines 263 - 291,
The test direct_root_override_entrypoint_is_rejected contains leftover
merge-conflict/duplicated code: remove the duplicated old test block and keep
the intended single coherent test flow (the initial failed
env.try_invoke_contract assertion, the check client.get_merkle_root, then
env.mock_all_auths followed by the setup_with_root path). Edit the function body
to eliminate the merged-old lines so only one sequence using setup,
setup_with_root, caller, hash, new_root, PublicSignals,
client.register_resolver, client.set_memo and client.resolve remains; ensure the
assertions (Err(Ok(InvokeError::Abort)), root equality, resolved_wallet and memo
checks) remain and the test compiles.

191-214: ⚠️ Potential issue | 🔴 Critical

Missing closing brace and duplicate test code cause parse errors.

The test root_progresses_across_multiple_registrations (lines 191-203) is missing its closing }. Lines 204-214 contain old imports and a duplicate setup function that conflict with the new test infrastructure (lines 71-81).

🐛 Proposed fix to close test and remove duplicate code
     assert_eq!(client.get_merkle_root(), second_public_signals.new_root);
     assert!(client.has_commitment(&first_public_signals.commitment));
     assert!(client.has_commitment(&second_public_signals.commitment));
+}
-use crate::smt_root::SmtRoot;
-use crate::types::{ChainType, PublicSignals};
-use crate::{Contract, ContractClient};
-use soroban_sdk::testutils::{Address as _, Events};
-use soroban_sdk::{Address, Bytes, BytesN, Env};
-
-fn setup(env: &Env) -> (Address, ContractClient<'_>) {
-    let contract_id = env.register(Contract, ());
-    let client = ContractClient::new(env, &contract_id);
-    (contract_id, client)
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/test.rs` around lines 191 - 214,
The test function root_progresses_across_multiple_registrations is missing its
closing brace and a duplicated block (imports and a second setup function) was
accidentally inserted; close the test by adding the missing `}` immediately
after the assertions in root_progresses_across_multiple_registrations, and
remove the duplicated import block and duplicate setup function (the extra use
crate::smt_root::SmtRoot / use crate::types::{ChainType, PublicSignals} / use
crate::{Contract, ContractClient} / fn setup(...) block) so the file uses the
original setup() and imports defined earlier and no duplicate symbols remain.

237-260: ⚠️ Potential issue | 🔴 Critical

Test root_cannot_be_overridden_by_reinitializing has merge conflict.

Lines 237-260 show merge conflict remnants where the new test body (lines 237-244) is incorrectly merged with old test code (lines 245-260). The old code references setup_with_root, register_resolver, resolve, PublicSignals (old version), and dummy_proof which are incompatible with the new test structure.

🐛 Proposed fix
 #[test]
 fn root_cannot_be_overridden_by_reinitializing() {
     let env = Env::default();
     let (_, client, verifier_id) = setup(&env);

     let result = client.try_init(&verifier_id, &bytes(&env, 9));

     assert_eq!(result, Err(Ok(ContractError::AlreadyInitialized.into())));
     assert_eq!(client.get_merkle_root(), bytes(&env, 0));
-    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);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/test.rs` around lines 237 - 260,
The test contains leftover merge-conflict/old-code fragments: remove the block
that references setup_with_root, register_resolver, resolve, PublicSignals, and
dummy_proof and keep only the new test flow that verifies reinitialization is
rejected; specifically, in the function
root_cannot_be_overridden_by_reinitializing() delete the old code after
env.mock_all_auths() (all uses of setup_with_root, caller, hash, new_root,
PublicSignals, register_resolver, resolve, and related assertions) so the test
only uses setup(), client.try_init(), client.get_merkle_root(), and the existing
assertions that assert AlreadyInitialized and the root remains bytes(&env, 0).
gateway-contract/contracts/core_contract/src/types.rs (2)

12-37: ⚠️ Potential issue | 🔴 Critical

Duplicate PublicSignals struct definitions will cause compilation error E0428.

Two PublicSignals structs are defined:

  • Lines 14-18: New version with old_root, new_root, commitment fields
  • Lines 34-37: Old version with only old_root, new_root fields

The old definition (lines 29-37) must be removed. Based on the PR objectives and test code, the new version with the commitment field is the correct one.

🐛 Proposed fix to remove duplicate definition
 }

-/// 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>,
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/core_contract/src/types.rs` around lines 12 - 37,
Remove the duplicate PublicSignals struct declaration and keep the updated
version that includes the commitment field; specifically, delete the second
definition of PublicSignals (the one with only old_root and new_root) so there
is a single #[contracttype] #[derive(...)] pub struct PublicSignals { pub
old_root: BytesN<32>, pub new_root: BytesN<32>, pub commitment: BytesN<32>, }
and ensure any associated doc comment about Groth16 non-inclusion is attached to
that remaining struct and no conflicting derives/attributes (e.g.,
#[contracttype], #[derive(Clone)]) are duplicated elsewhere.

1-37: ⚠️ Potential issue | 🔴 Critical

Restore missing ResolveData type or remove all usages.

ResolveData is imported and used in lib.rs (lines 29, 125, 144, 157) and test.rs (line 502), but the type definition is absent from types.rs. This will cause compilation failures.

Restore the definition:

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolveData {
    pub wallet: Address,
    pub memo: Option<u64>,
}

Or remove all usages in lib.rs and test.rs.

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

In `@gateway-contract/contracts/core_contract/src/types.rs` around lines 1 - 37,
The codebase references a missing contracttype struct ResolveData (used by
lib.rs functions and tests), so restore its definition in types.rs by adding a
#[contracttype] struct named ResolveData with fields wallet of type Address and
memo of type Option<u64> (derive Clone, Debug, Eq, PartialEq), or alternatively
remove all usages of ResolveData from lib.rs and test.rs (the symbols calling it
are the ResolveData type in lib.rs and tests referenced around the earlier
compile errors).
gateway-contract/contracts/core_contract/src/storage.rs (1)

44-56: ⚠️ Potential issue | 🔴 Critical

Remove duplicate DataKey enum definition and complete store_commitment function.

The file has two conflicting DataKey enum definitions (lines 5-9 and lines 49-56) causing compilation error E0428. Additionally, the store_commitment function at line 40 is incomplete and not properly closed.

The old DataKey variants (Resolver, SmtRoot, StellarAddress) are actively used in lib.rs, smt_root.rs, and test.rs. Either:

  1. Merge both definitions into a single DataKey enum that includes all variants needed, or
  2. Refactor all usage sites to use only the new variants (CurrentMerkleRoot, Verifier, Commitment)

Fix the store_commitment function closure before proceeding.

🤖 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 44 -
56, Merge the duplicate DataKey enums into one consolidated enum named DataKey
that contains all required variants used across the codebase (include
Resolver(BytesN<32>), SmtRoot, StellarAddress(BytesN<32>) as well as any new
variants like CurrentMerkleRoot, Verifier, Commitment if needed) so there is
only a single definition; then fix and properly close the store_commitment
function (ensure its braces complete and it returns/sets storage as expected) so
compilation succeeds and usages in lib.rs, smt_root.rs, and test.rs resolve to
the unified DataKey variants (refer to DataKey and store_commitment to locate
the changes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gateway-contract/contracts/core_contract/src/lib.rs`:
- Around line 31-32: There are duplicate/merged contract definitions: reconcile
and remove the orphan/old struct impl so only one contract type remains—ensure
all methods belong to the new CoreContract; specifically move the submit_proof
function into an impl CoreContract block (or rename CoreContract back to
Contract everywhere) and delete the extraneous impl Contract block and any
duplicate struct declaration so symbols CoreContract, Contract, submit_proof,
and impl Contract are consistent and compile.
- Around line 50-78: The impl CoreContract block is not closed because
submit_proof ends with an unfinished if !is_valid { branch; complete
submit_proof by handling the invalid-proof branch (e.g., call
panic_with_error!(&env, ContractError::InvalidProof); or any intended recovery),
perform the remaining valid-proof logic (store commitment via
storage::set_commitment, update merkle root with storage::set_merkle_root, emit
events, etc.), and then close the impl block with a matching } for impl
CoreContract; afterwards remove or reconcile the stray #[contractimpl] that
begins the old impl Contract so there are no overlapping/duplicate impl blocks.

In `@gateway-contract/contracts/core_contract/src/storage.rs`:
- Around line 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).

In `@gateway-contract/contracts/core_contract/src/types.rs`:
- Around line 1-2: Remove the duplicate use lines importing from soroban_sdk and
consolidate into a single use statement that lists all required symbols (e.g.,
contracttype, Address, BytesN, Symbol) so there is no overlapping import of
contracttype or BytesN; update the existing declarations (types.rs) to reference
that single use line and delete the redundant import to resolve compilation
error E0252.

---

Outside diff comments:
In `@gateway-contract/contracts/core_contract/src/lib.rs`:
- Around line 3-14: The file contains duplicate private and public module
declarations (mod errors/events/storage/types vs pub mod
errors/events/storage/types) causing E0428; remove the private declarations and
keep the public ones (retain pub mod address_manager, errors, events,
registration, smt_root, storage, types, zk_verifier) so each module is declared
only once and exported for external use.
- Around line 19-29: There are duplicate imports from soroban_sdk and the types
module causing E0252; consolidate them into a single use declaration (e.g.,
merge the two soroban_sdk lines into one that includes contract, contractclient
(if needed), contractimpl, panic_with_error, Address, Bytes, BytesN, Env) and
remove the redundant import; likewise unify the types imports so
PublicSignals/Proof/ResolveData/ChainType come from the correct module
(crate::types or types) in one use statement and remove the other conflicting
use. Update the top-level imports (the duplicate soroban_sdk lines and duplicate
types imports) so each symbol appears only once.

In `@gateway-contract/contracts/core_contract/src/storage.rs`:
- Around line 44-56: Merge the duplicate DataKey enums into one consolidated
enum named DataKey that contains all required variants used across the codebase
(include Resolver(BytesN<32>), SmtRoot, StellarAddress(BytesN<32>) as well as
any new variants like CurrentMerkleRoot, Verifier, Commitment if needed) so
there is only a single definition; then fix and properly close the
store_commitment function (ensure its braces complete and it returns/sets
storage as expected) so compilation succeeds and usages in lib.rs, smt_root.rs,
and test.rs resolve to the unified DataKey variants (refer to DataKey and
store_commitment to locate the changes).

In `@gateway-contract/contracts/core_contract/src/test.rs`:
- Around line 263-291: The test direct_root_override_entrypoint_is_rejected
contains leftover merge-conflict/duplicated code: remove the duplicated old test
block and keep the intended single coherent test flow (the initial failed
env.try_invoke_contract assertion, the check client.get_merkle_root, then
env.mock_all_auths followed by the setup_with_root path). Edit the function body
to eliminate the merged-old lines so only one sequence using setup,
setup_with_root, caller, hash, new_root, PublicSignals,
client.register_resolver, client.set_memo and client.resolve remains; ensure the
assertions (Err(Ok(InvokeError::Abort)), root equality, resolved_wallet and memo
checks) remain and the test compiles.
- Around line 191-214: The test function
root_progresses_across_multiple_registrations is missing its closing brace and a
duplicated block (imports and a second setup function) was accidentally
inserted; close the test by adding the missing `}` immediately after the
assertions in root_progresses_across_multiple_registrations, and remove the
duplicated import block and duplicate setup function (the extra use
crate::smt_root::SmtRoot / use crate::types::{ChainType, PublicSignals} / use
crate::{Contract, ContractClient} / fn setup(...) block) so the file uses the
original setup() and imports defined earlier and no duplicate symbols remain.
- Around line 237-260: The test contains leftover merge-conflict/old-code
fragments: remove the block that references setup_with_root, register_resolver,
resolve, PublicSignals, and dummy_proof and keep only the new test flow that
verifies reinitialization is rejected; specifically, in the function
root_cannot_be_overridden_by_reinitializing() delete the old code after
env.mock_all_auths() (all uses of setup_with_root, caller, hash, new_root,
PublicSignals, register_resolver, resolve, and related assertions) so the test
only uses setup(), client.try_init(), client.get_merkle_root(), and the existing
assertions that assert AlreadyInitialized and the root remains bytes(&env, 0).

In `@gateway-contract/contracts/core_contract/src/types.rs`:
- Around line 12-37: Remove the duplicate PublicSignals struct declaration and
keep the updated version that includes the commitment field; specifically,
delete the second definition of PublicSignals (the one with only old_root and
new_root) so there is a single #[contracttype] #[derive(...)] pub struct
PublicSignals { pub old_root: BytesN<32>, pub new_root: BytesN<32>, pub
commitment: BytesN<32>, } and ensure any associated doc comment about Groth16
non-inclusion is attached to that remaining struct and no conflicting
derives/attributes (e.g., #[contracttype], #[derive(Clone)]) are duplicated
elsewhere.
- Around line 1-37: The codebase references a missing contracttype struct
ResolveData (used by lib.rs functions and tests), so restore its definition in
types.rs by adding a #[contracttype] struct named ResolveData with fields wallet
of type Address and memo of type Option<u64> (derive Clone, Debug, Eq,
PartialEq), or alternatively remove all usages of ResolveData from lib.rs and
test.rs (the symbols calling it are the ResolveData type in lib.rs and tests
referenced around the earlier compile errors).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b95cd7b-2ca8-4486-952d-005ba55170ca

📥 Commits

Reviewing files that changed from the base of the PR and between 462b347 and ecff67d.

📒 Files selected for processing (5)
  • gateway-contract/contracts/core_contract/src/errors.rs
  • gateway-contract/contracts/core_contract/src/lib.rs
  • gateway-contract/contracts/core_contract/src/storage.rs
  • gateway-contract/contracts/core_contract/src/test.rs
  • gateway-contract/contracts/core_contract/src/types.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • gateway-contract/contracts/core_contract/src/errors.rs

Comment on lines 40 to 44
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).

@ryzen-xp ryzen-xp added Blocked Code Conflict This PR have merge conflicts, Please Resolve it !!! and removed Code Conflict This PR have merge conflicts, Please Resolve it !!! labels Mar 28, 2026
@ryzen-xp ryzen-xp changed the title Feat/contract root anchoring feat(core): contract root anchoring Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Blocked CI Failed Please check why you CI is faileing fix your code

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

[Contract] On-chain Merkle Root Anchoring

2 participants