Skip to content
Open
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
36 changes: 26 additions & 10 deletions contracts/async-vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ impl AsyncVault {
oracle: Address,
roles: VaultRoles,
) {
// The vault kit white-label topology specifies five distinct authorities:
// governance, manager, treasury, guardian, compliance, and attester.
// Although compliance is enforced on the identity verifier and attestation
// on the NAV oracle, the vault checks pairwise distinctness at construction
// time to prevent role misconfiguration across the kit ecosystem. Neither
// compliance nor attester is granted a direct role on the vault contract itself.
if roles.treasury == roles.guardian
|| roles.treasury == roles.governance
|| roles.compliance == roles.governance
Expand Down Expand Up @@ -159,12 +165,14 @@ impl AsyncVault {
}

#[only_admin]
pub fn set_custodian(e: &Env, custodian: Address, _caller: Address) {
pub fn set_custodian(e: &Env, custodian: Address, caller: Address) {
caller.require_auth();
treasury::set_custodian(e, &custodian);
}

#[only_admin]
pub fn set_wind_down_delay(e: &Env, secs: u64, _caller: Address) {
pub fn set_wind_down_delay(e: &Env, secs: u64, caller: Address) {
caller.require_auth();
wind_down::set_delay(e, secs);
}

Expand All @@ -177,12 +185,14 @@ impl AsyncVault {
}

#[only_admin]
pub fn propose_wind_down(e: &Env, _caller: Address) {
pub fn propose_wind_down(e: &Env, caller: Address) {
caller.require_auth();
wind_down::propose(e);
}

#[only_admin]
pub fn cancel_wind_down_proposal(e: &Env, _caller: Address) {
pub fn cancel_wind_down_proposal(e: &Env, caller: Address) {
caller.require_auth();
wind_down::cancel_proposal(e);
}

Expand Down Expand Up @@ -221,7 +231,8 @@ impl AsyncVault {
}

#[only_admin]
pub fn set_notice(e: &Env, secs: u64, _caller: Address) {
pub fn set_notice(e: &Env, secs: u64, caller: Address) {
caller.require_auth();
if secs > MAX_NOTICE_SECS {
panic_with_error!(e, VaultError::NoticeTooLong);
}
Expand All @@ -233,23 +244,27 @@ impl AsyncVault {
}

#[only_admin]
pub fn propose_upgrade(e: &Env, wasm_hash: BytesN<32>, _caller: Address) {
pub fn propose_upgrade(e: &Env, wasm_hash: BytesN<32>, caller: Address) {
caller.require_auth();
upgrade::propose_wasm(e, wasm_hash);
}

#[only_admin]
pub fn propose_upgrade_delay(e: &Env, secs: u64, _caller: Address) {
pub fn propose_upgrade_delay(e: &Env, secs: u64, caller: Address) {
caller.require_auth();
upgrade::propose_delay(e, secs);
}

#[only_admin]
pub fn cancel_upgrade(e: &Env, _caller: Address) {
pub fn cancel_upgrade(e: &Env, caller: Address) {
caller.require_auth();
upgrade::cancel(e);
}

#[only_admin]
#[when_not_paused]
pub fn apply_upgrade(e: &Env, _caller: Address) {
pub fn apply_upgrade(e: &Env, caller: Address) {
caller.require_auth();
upgrade::apply(e);
}

Expand Down Expand Up @@ -360,7 +375,8 @@ impl Pausable for AsyncVault {
}

#[only_admin]
fn unpause(e: &Env, _caller: Address) {
fn unpause(e: &Env, caller: Address) {
caller.require_auth();
pausable::unpause(e);
upgrade::on_unpause(e);
}
Expand Down
7 changes: 7 additions & 0 deletions contracts/async-vault/src/test/wind_down.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ fn governance_cancels_a_proposal_before_activation() {
f.vault.set_wind_down_delay(&WEEK, &f.admin);
f.vault.propose_wind_down(&f.admin);

let stranger = Address::generate(&f.e);
f.e.set_auths(&[]);
assert!(f.vault.try_cancel_wind_down_proposal(&stranger).is_err());
assert!(f.vault.try_cancel_wind_down_proposal(&f.manager).is_err());
assert!(f.vault.try_cancel_wind_down_proposal(&f.guardian).is_err());

f.e.mock_all_auths();
f.vault.cancel_wind_down_proposal(&f.admin);
assert_eq!(f.vault.wind_down(), None);

Expand Down
3 changes: 3 additions & 0 deletions contracts/identity-verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ soroban-sdk = { workspace = true }
stellar-access = { workspace = true }
stellar-macros = { workspace = true }
stellar-tokens = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
6 changes: 5 additions & 1 deletion contracts/identity-verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ impl IdentityVerifier {

/// Adds or removes an account from the allowlist.
#[only_admin]
pub fn allow(e: &Env, account: Address, allowed: bool, _caller: Address) {
pub fn allow(e: &Env, account: Address, allowed: bool, caller: Address) {
caller.require_auth();
e.storage()
.persistent()
.set(&DataKey::Allowed(account), &allowed);
Expand Down Expand Up @@ -69,3 +70,6 @@ impl identity_verification::IdentityVerifier for IdentityVerifier {
) {
}
}

#[cfg(test)]
mod test;
55 changes: 55 additions & 0 deletions contracts/identity-verifier/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use super::*;
use soroban_sdk::{testutils::Address as _, Address, Env};
use stellar_tokens::rwa::identity_verification::IdentityVerifierClient as IdClient;

#[test]
fn only_admin_can_allow() {
let e = Env::default();
let admin = Address::generate(&e);
let stranger = Address::generate(&e);
let investor = Address::generate(&e);

let id = e.register(IdentityVerifier, (admin.clone(),));
let client = IdentityVerifierClient::new(&e, &id);

// Initial state: not allowed.
assert!(!client.is_allowed(&investor));

// Stranger cannot allow.
assert!(client.try_allow(&investor, &true, &stranger).is_err());
assert!(!client.is_allowed(&investor));

// Admin allows.
e.mock_all_auths();
client.allow(&investor, &true, &admin);
assert!(client.is_allowed(&investor));

// Stranger cannot disallow.
e.set_auths(&[]);
assert!(client.try_allow(&investor, &false, &stranger).is_err());
assert!(client.is_allowed(&investor));

// Admin disallows.
e.mock_all_auths();
client.allow(&investor, &false, &admin);
assert!(!client.is_allowed(&investor));
}

#[test]
fn verify_identity_checks_allowlist() {
let e = Env::default();
e.mock_all_auths();
let admin = Address::generate(&e);
let investor = Address::generate(&e);

let id = e.register(IdentityVerifier, (admin.clone(),));
let client = IdentityVerifierClient::new(&e, &id);
let id_client = IdClient::new(&e, &id);

// Unallowed investor fails identity verification.
assert!(id_client.try_verify_identity(&investor).is_err());

// Once allowed, verification succeeds.
client.allow(&investor, &true, &admin);
id_client.verify_identity(&investor);
}
6 changes: 4 additions & 2 deletions contracts/nav-oracle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ impl NavOracleContract {
}

#[only_admin]
pub fn set_ripcord(e: &Env, paused: bool, _caller: Address) {
pub fn set_ripcord(e: &Env, paused: bool, caller: Address) {
caller.require_auth();
state::set_ripcord(e, paused);
RipcordSet { paused }.publish(e);
}
Expand All @@ -223,7 +224,8 @@ impl NavOracleContract {
/// its distance from the last. Only while the ripcord is raised, so
/// resuming is always a deliberate second act.
#[only_admin]
pub fn clear_latest(e: &Env, _caller: Address) {
pub fn clear_latest(e: &Env, caller: Address) {
caller.require_auth();
if !ripcord_raised(e) {
panic_with_error!(e, OracleError::RipcordNotRaised);
}
Expand Down
26 changes: 26 additions & 0 deletions contracts/nav-oracle/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ fn set_config_rejects_a_zero_freshness_duration() {
assert!(f.oracle.try_set_config(&cfg).is_err());
}

#[test]
fn only_admin_can_set_config() {
let f = setup();
let cfg = config();

f.e.set_auths(&[]);
assert!(f.oracle.try_set_config(&cfg).is_err());

f.e.mock_all_auths();
f.oracle.set_config(&cfg);
}

#[test]
fn the_guardian_raises_the_ripcord_but_lowering_needs_governance() {
let f = setup();
Expand Down Expand Up @@ -379,6 +391,20 @@ fn the_record_clears_only_while_the_ripcord_is_raised() {
assert_eq!(f.oracle.nav_per_share(), SCALE * 50);
}

#[test]
fn only_admin_can_clear_latest() {
let f = setup();
let stranger = Address::generate(&f.e);
let r = report(&f.e, SCALE, 1, 1_000_000);
f.oracle.attest(&r, &f.attester);
f.oracle.raise_ripcord(&f.guardian);

f.e.set_auths(&[]);
assert!(f.oracle.try_clear_latest(&stranger).is_err());
assert!(f.oracle.try_clear_latest(&f.attester).is_err());
assert!(f.oracle.try_clear_latest(&f.guardian).is_err());
}

#[test]
fn every_oracle_authority_is_readable_and_rotatable() {
let f = setup();
Expand Down
6 changes: 4 additions & 2 deletions contracts/share-token/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ impl ShareToken {
#[contractimpl(contracttrait)]
impl Pausable for ShareToken {
#[only_admin]
fn pause(e: &Env, _caller: Address) {
fn pause(e: &Env, caller: Address) {
caller.require_auth();
pausable::pause(e);
}
#[only_admin]
fn unpause(e: &Env, _caller: Address) {
fn unpause(e: &Env, caller: Address) {
caller.require_auth();
pausable::unpause(e);
}
Comment thread
luchobonatti marked this conversation as resolved.
}
Expand Down
Loading
Loading