Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
14 changes: 7 additions & 7 deletions contracts/contracts/user_registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub enum DataKey {
PrivyDid(String), // Maps Privy DID -> wallet Address
WalletDid(Address), // Maps wallet Address -> Privy DID (reverse index)
Admin, // Stores the contract admin Address
PendingAdmin, // Stores the proposed successor admin (2-step transfer, issue #776)
PendingAdmin, // Stores the proposed successor admin (2-step transfer, issue #989)
PrivyVerifierKey, // Ed25519 public key trusted to attest DID <-> wallet links
ReservationToken, // Stores Naira token contract Address
ReservationAmount, // Stores required reservation amount (i128)
Expand Down Expand Up @@ -123,10 +123,10 @@ impl UserRegistryContract {
}

/// Admin-only: propose a successor admin as part of a 2-step ownership
/// transfer (issue #776). The current admin keeps full privileges until
/// transfer (issue #989). The current admin keeps full privileges until
/// the proposed address claims ownership via `claim_admin`. Calling this
/// only updates the pending admin; it does not change `DataKey::Admin`.
pub fn propose_admin(env: Env, caller: Address, new_admin: Address) {
pub fn propose_new_admin(env: Env, caller: Address, new_admin: Address) {
caller.require_auth();
let admin = Self::require_admin(&env);
assert!(caller == admin, "only admin can propose new admin");
Expand All @@ -142,7 +142,7 @@ impl UserRegistryContract {
}

/// Complete a 2-step ownership transfer. Only the address previously
/// proposed via `propose_admin` may call this; once called, ownership
/// proposed via `propose_new_admin` may call this; once called, ownership
/// (`DataKey::Admin`) moves to the caller and the pending proposal is
/// cleared.
pub fn claim_admin(env: Env, caller: Address) {
Expand Down Expand Up @@ -828,7 +828,7 @@ mod test;
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::testutils::Address as _;
use soroban_sdk::testutils::{Address as _, Events as _};

// ── Issue #772: reservation lock-fee release on profile completion ──────

Expand Down Expand Up @@ -1612,7 +1612,7 @@ mod tests {
let has_created = events.iter().any(|e| {
e.topics
.iter()
.any(|t| t == soroban_sdk::Val::from(Symbol::new(&env, "PrivyLinkCreated")))
.any(|t| t == Symbol::new(&env, "PrivyLinkCreated").to_val())
});
assert!(has_created, "PrivyLinkCreated event must be emitted on DID registration");
}
Expand Down Expand Up @@ -1658,7 +1658,7 @@ mod tests {
let has_removed = events.iter().any(|e| {
e.topics
.iter()
.any(|t| t == soroban_sdk::Val::from(Symbol::new(&env, "PrivyLinkRemoved")))
.any(|t| t == Symbol::new(&env, "PrivyLinkRemoved").to_val())
});
assert!(has_removed, "PrivyLinkRemoved event must be emitted on DID unlink");
}
Expand Down
16 changes: 10 additions & 6 deletions contracts/contracts/user_registry/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

use super::*;
use ed25519_dalek::{Signer, SigningKey};
use soroban_sdk::{testutils::Address as _, xdr::ToXdr, BytesN, Env, String};
use soroban_sdk::{
testutils::{Address as _, Events as _},
xdr::ToXdr,
BytesN, Env, String,
};

/// Deterministic test-only signing key standing in for Privy's verifier key.
fn verifier_key() -> SigningKey {
Expand Down Expand Up @@ -393,7 +397,7 @@ fn test_get_did_for_wallet_unlinked_fails() {
);
}

// ── Issue #776: 2-step admin ownership transfer ─────────────────────────────
// ── Issue #989: 2-step admin ownership transfer ─────────────────────────────

/// Build a freshly initialized contract along with the old (current) admin and
/// a distinct proposed successor admin.
Expand Down Expand Up @@ -428,7 +432,7 @@ fn stored_admin(env: &Env, client: &UserRegistryContractClient<'static>) -> Addr
fn test_2step_transfer_old_admin_keeps_ownership_until_claim() {
let (env, client, old_admin, new_admin) = admin_setup();

client.propose_admin(&old_admin, &new_admin);
client.propose_new_admin(&old_admin, &new_admin);

// Ownership has not moved yet.
assert_eq!(stored_admin(&env, &client), old_admin);
Expand All @@ -443,7 +447,7 @@ fn test_2step_transfer_old_admin_keeps_ownership_until_claim() {
fn test_2step_transfer_claim_admin_moves_ownership() {
let (env, client, old_admin, new_admin) = admin_setup();

client.propose_admin(&old_admin, &new_admin);
client.propose_new_admin(&old_admin, &new_admin);
let claimed = client.try_claim_admin(&new_admin);
assert!(claimed.is_ok(), "proposed admin must be able to claim: {claimed:?}");

Expand All @@ -467,7 +471,7 @@ fn test_claim_admin_rejects_unauthorized_caller() {
let (env, client, old_admin, new_admin) = admin_setup();
let evil = Address::generate(&env);

client.propose_admin(&old_admin, &new_admin);
client.propose_new_admin(&old_admin, &new_admin);
// Neither an unrelated address nor the old admin can claim.
client.claim_admin(&evil);
}
Expand All @@ -481,7 +485,7 @@ fn test_propose_admin_rejects_non_admin() {
let imposter = Address::generate(&env);
let target = Address::generate(&env);

client.propose_admin(&imposter, &target);
client.propose_new_admin(&imposter, &target);
}

/// Claiming with no active proposal panics.
Expand Down
Loading