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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
193 changes: 170 additions & 23 deletions risk_score/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,49 @@ pub struct RiskTierData {

#[contractimpl]
impl RiskTierContract {
/// Initialize the contract with a trusted admin address
/// Can only be called once during deployment
pub fn initialize(env: Env, admin: Address) {
// Check if admin is already set
let admin_key = Symbol::new(&env, "admin");
if env.storage().persistent().has(&admin_key) {
panic!("Contract already initialized");
}

// Set the admin address
env.storage().persistent().set(&admin_key, &admin);

// Emit initialization event
env.events().publish(
Symbol::new(&env, "initialized"),
admin,
);
}

/// Get the current admin address
pub fn get_admin(env: Env) -> Option<Address> {
let admin_key = Symbol::new(&env, "admin");
env.storage().persistent().get(&admin_key)
}

/// Check if the caller is authorized (admin or the user themselves)
fn is_authorized(env: Env, caller: Address, user: Address) -> bool {
let admin_key = Symbol::new(&env, "admin");
if let Some(admin) = env.storage().persistent().get::<_, Address>(&admin_key) {
caller == admin || caller == user
} else {
// If no admin is set, only allow users to set their own data
caller == user
}
}

/// Set risk score with tier classification and timestamp
/// Following Soroban persistent storage best practices with tuple keys
pub fn set_risk_tier(env: Env, user: Address, score: u32, tier: Symbol, chosen_tier: Symbol) {
/// Now requires authorization: caller must be admin or the user themselves
pub fn set_risk_tier(env: Env, caller: Address, user: Address, score: u32, tier: Symbol, chosen_tier: Symbol) {
// Authorization check: caller must be admin or the user themselves
assert!(Self::is_authorized(env.clone(), caller.clone(), user.clone()), "Unauthorized: caller must be admin or the user themselves");

// Validate inputs
assert!(score <= 100, "Score must be 0-100");
assert!(
Expand Down Expand Up @@ -194,7 +234,41 @@ impl RiskTierContract {
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{testutils::Address as _, Env};
use soroban_sdk::{Address, Env, Symbol};
use soroban_sdk::testutils::{Address as _};

#[test]
fn test_initialize_and_get_admin() {
let env = Env::default();
let contract_id = env.register_contract(None, RiskTierContract);
let client = RiskTierContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);

// Initialize with admin
client.initialize(&admin);

// Check admin is set correctly
let retrieved_admin = client.get_admin().unwrap();
assert_eq!(retrieved_admin, admin);
}

#[test]
#[should_panic(expected = "Contract already initialized")]
fn test_initialize_only_once() {
let env = Env::default();
let contract_id = env.register_contract(None, RiskTierContract);
let client = RiskTierContractClient::new(&env, &contract_id);

let admin1 = Address::generate(&env);
let admin2 = Address::generate(&env);

// Initialize first time
client.initialize(&admin1);

// Try to initialize again - should panic
client.initialize(&admin2);
}

#[test]
fn test_set_and_get_risk_tier() {
Expand All @@ -205,14 +279,87 @@ mod tests {
let user = Address::generate(&env);
let tier_1 = Symbol::new(&env, "TIER_1");

client.set_risk_tier(&user, &25, &tier_1, &tier_1);
// User can set their own risk tier
client.set_risk_tier(&user, &user, &25, &tier_1, &tier_1);

let risk_data = client.get_risk_tier(&user).unwrap();
assert_eq!(risk_data.score, 25);
assert_eq!(risk_data.tier, tier_1);
assert_eq!(risk_data.chosen_tier, tier_1);
}

#[test]
fn test_admin_can_set_any_user_risk_tier() {
let env = Env::default();
let contract_id = env.register_contract(None, RiskTierContract);
let client = RiskTierContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let user = Address::generate(&env);
let tier_2 = Symbol::new(&env, "TIER_2");

// Initialize admin
client.initialize(&admin);

// Admin can set any user's risk tier
client.set_risk_tier(&admin, &user, &50, &tier_2, &tier_2);

let risk_data = client.get_risk_tier(&user).unwrap();
assert_eq!(risk_data.score, 50);
assert_eq!(risk_data.tier, tier_2);
}

#[test]
#[should_panic(expected = "Unauthorized: caller must be admin or the user themselves")]
fn test_unauthorized_cannot_set_other_user_risk_tier() {
let env = Env::default();
let contract_id = env.register_contract(None, RiskTierContract);
let client = RiskTierContractClient::new(&env, &contract_id);

let user1 = Address::generate(&env);
let user2 = Address::generate(&env);
let tier_1 = Symbol::new(&env, "TIER_1");

// user1 tries to set user2's risk tier - should panic
client.set_risk_tier(&user1, &user2, &25, &tier_1, &tier_1);
}

#[test]
#[should_panic(expected = "Unauthorized: caller must be admin or the user themselves")]
fn test_unauthorized_cannot_set_other_user_risk_tier_with_admin() {
let env = Env::default();
let contract_id = env.register_contract(None, RiskTierContract);
let client = RiskTierContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let unauthorized_user = Address::generate(&env);
let target_user = Address::generate(&env);
let tier_3 = Symbol::new(&env, "TIER_3");

// Initialize admin
client.initialize(&admin);

// Unauthorized user tries to set another user's risk tier - should panic
client.set_risk_tier(&unauthorized_user, &target_user, &75, &tier_3, &tier_3);
}

#[test]
fn test_user_can_set_own_risk_tier_without_admin() {
let env = Env::default();
let contract_id = env.register_contract(None, RiskTierContract);
let client = RiskTierContractClient::new(&env, &contract_id);

let user = Address::generate(&env);
let tier_1 = Symbol::new(&env, "TIER_1");

// No admin initialized, but user can still set their own risk tier
client.set_risk_tier(&user, &user, &15, &tier_1, &tier_1);

let risk_data = client.get_risk_tier(&user).unwrap();
assert_eq!(risk_data.score, 15);
assert_eq!(risk_data.tier, tier_1);
}

#[test]
fn test_score_validation_upper_bound() {
let env = Env::default();
Expand All @@ -222,7 +369,7 @@ mod tests {
let user = Address::generate(&env);
let tier_3 = Symbol::new(&env, "TIER_3");

client.set_risk_tier(&user, &100, &tier_3, &tier_3);
client.set_risk_tier(&user, &user, &100, &tier_3, &tier_3);

let score = client.get_score(&user);
assert_eq!(score, 100);
Expand All @@ -238,7 +385,7 @@ mod tests {
let user = Address::generate(&env);
let tier_3 = Symbol::new(&env, "TIER_3");

client.set_risk_tier(&user, &101, &tier_3, &tier_3);
client.set_risk_tier(&user, &user, &101, &tier_3, &tier_3);
}

#[test]
Expand All @@ -251,7 +398,7 @@ mod tests {
let user = Address::generate(&env);
let invalid_tier = Symbol::new(&env, "TIER_4");

client.set_risk_tier(&user, &50, &invalid_tier, &invalid_tier);
client.set_risk_tier(&user, &user, &50, &invalid_tier, &invalid_tier);
}

#[test]
Expand All @@ -263,7 +410,7 @@ mod tests {
let user = Address::generate(&env);
let tier_1 = Symbol::new(&env, "TIER_1");

client.set_risk_tier(&user, &25, &tier_1, &tier_1);
client.set_risk_tier(&user, &user, &25, &tier_1, &tier_1);

assert!(client.can_access_tier(&user, &tier_1));
}
Expand All @@ -277,7 +424,7 @@ mod tests {
let user = Address::generate(&env);
let tier_1 = Symbol::new(&env, "TIER_1");

client.set_risk_tier(&user, &30, &tier_1, &tier_1);
client.set_risk_tier(&user, &user, &30, &tier_1, &tier_1);

assert!(client.can_access_tier(&user, &tier_1));
}
Expand All @@ -292,7 +439,7 @@ mod tests {
let tier_2 = Symbol::new(&env, "TIER_2");
let tier_1 = Symbol::new(&env, "TIER_1");

client.set_risk_tier(&user, &50, &tier_2, &tier_2);
client.set_risk_tier(&user, &user, &50, &tier_2, &tier_2);

assert!(!client.can_access_tier(&user, &tier_1));
}
Expand All @@ -306,7 +453,7 @@ mod tests {
let user = Address::generate(&env);
let tier_2 = Symbol::new(&env, "TIER_2");

client.set_risk_tier(&user, &50, &tier_2, &tier_2);
client.set_risk_tier(&user, &user, &50, &tier_2, &tier_2);

assert!(client.can_access_tier(&user, &tier_2));
}
Expand All @@ -320,7 +467,7 @@ mod tests {
let user = Address::generate(&env);
let tier_3 = Symbol::new(&env, "TIER_3");

client.set_risk_tier(&user, &85, &tier_3, &tier_3);
client.set_risk_tier(&user, &user, &85, &tier_3, &tier_3);

assert!(client.can_access_tier(&user, &tier_3));
}
Expand All @@ -335,8 +482,8 @@ mod tests {
let user2 = Address::generate(&env);
let tier_1 = Symbol::new(&env, "TIER_1");

client.set_risk_tier(&user1, &20, &tier_1, &tier_1);
client.set_risk_tier(&user2, &25, &tier_1, &tier_1);
client.set_risk_tier(&user1, &user1, &20, &tier_1, &tier_1);
client.set_risk_tier(&user2, &user2, &25, &tier_1, &tier_1);

let tier_users = client.get_tier_users(&tier_1);
assert_eq!(tier_users.len(), 2);
Expand All @@ -352,7 +499,7 @@ mod tests {
let tier_1 = Symbol::new(&env, "TIER_1");
let tier_2 = Symbol::new(&env, "TIER_2");

client.set_risk_tier(&user, &25, &tier_1, &tier_1);
client.set_risk_tier(&user, &user, &25, &tier_1, &tier_1);
client.update_chosen_tier(&user, &tier_2);

let chosen = client.get_chosen_tier(&user);
Expand All @@ -370,7 +517,7 @@ mod tests {
let tier_3 = Symbol::new(&env, "TIER_3");
let tier_1 = Symbol::new(&env, "TIER_1");

client.set_risk_tier(&user, &85, &tier_3, &tier_3);
client.set_risk_tier(&user, &user, &85, &tier_3, &tier_3);
client.update_chosen_tier(&user, &tier_1);
}

Expand All @@ -388,9 +535,9 @@ mod tests {
let tier_2 = Symbol::new(&env, "TIER_2");
let tier_3 = Symbol::new(&env, "TIER_3");

client.set_risk_tier(&user1, &20, &tier_1, &tier_1);
client.set_risk_tier(&user2, &50, &tier_2, &tier_2);
client.set_risk_tier(&user3, &80, &tier_3, &tier_3);
client.set_risk_tier(&user1, &user1, &20, &tier_1, &tier_1);
client.set_risk_tier(&user2, &user2, &50, &tier_2, &tier_2);
client.set_risk_tier(&user3, &user3, &80, &tier_3, &tier_3);

let stats = client.get_tier_stats();
assert_eq!(stats.get(tier_1).unwrap(), 1);
Expand All @@ -408,8 +555,8 @@ mod tests {
let tier_2 = Symbol::new(&env, "TIER_2");
let tier_1 = Symbol::new(&env, "TIER_1");

client.set_risk_tier(&user, &50, &tier_2, &tier_2);
client.set_risk_tier(&user, &25, &tier_1, &tier_1);
client.set_risk_tier(&user, &user, &50, &tier_2, &tier_2);
client.set_risk_tier(&user, &user, &25, &tier_1, &tier_1);

let risk_data = client.get_risk_tier(&user).unwrap();
assert_eq!(risk_data.score, 25);
Expand Down Expand Up @@ -454,9 +601,9 @@ mod tests {
let tier_2 = Symbol::new(&env, "TIER_2");
let tier_3 = Symbol::new(&env, "TIER_3");

client.set_risk_tier(&user1, &15, &tier_1, &tier_1);
client.set_risk_tier(&user2, &45, &tier_2, &tier_2);
client.set_risk_tier(&user3, &90, &tier_3, &tier_3);
client.set_risk_tier(&user1, &user1, &15, &tier_1, &tier_1);
client.set_risk_tier(&user2, &user2, &45, &tier_2, &tier_2);
client.set_risk_tier(&user3, &user3, &90, &tier_3, &tier_3);

assert!(client.can_access_tier(&user1, &tier_1));
assert!(!client.can_access_tier(&user2, &tier_1));
Expand Down
2 changes: 1 addition & 1 deletion risk_score/target/.rustc_info.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"rustc_fingerprint":16478150667837962261,"outputs":{"6432102384495711296":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/mericcintosun/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.87.0 (17067e9ac 2025-05-09)\nbinary: rustc\ncommit-hash: 17067e9ac6d7ecb70e50f92c1944e545188d2359\ncommit-date: 2025-05-09\nhost: aarch64-apple-darwin\nrelease: 1.87.0\nLLVM version: 20.1.1\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/mericcintosun/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":15653068345748453314,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\nlib___.a\n___.dll\nC:\\Users\\USER\\.rustup\\toolchains\\stable-x86_64-pc-windows-gnu\noff\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.95.0 (59807616e 2026-04-14)\nbinary: rustc\ncommit-hash: 59807616e1fa2540724bfbac14d7976d7e4a3860\ncommit-date: 2026-04-14\nhost: x86_64-pc-windows-gnu\nrelease: 1.95.0\nLLVM version: 22.1.2\n","stderr":""}},"successes":{}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add50dc2111f9d93
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"rustc":6313536168557467772,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[966925859616469517,"build_script_build",false,13063463846681121356]],"local":[{"RerunIfChanged":{"output":"debug\\build\\ahash-5595a0aa67cc1458\\output","paths":["build.rs"]}}],"rustflags":[],"config":0,"compile_kind":0}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4c1296c527bf4ab5
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"rustc":6313536168557467772,"features":"[]","declared_features":"[\"atomic-polyfill\", \"compile-time-rng\", \"const-random\", \"default\", \"getrandom\", \"nightly-arm-aes\", \"no-rng\", \"runtime-rng\", \"serde\", \"std\"]","target":17883862002600103897,"profile":2225463790103693989,"path":10640389185318050259,"deps":[[5398981501050481332,"version_check",false,10448460581644503153]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\ahash-b4f9f2fada182f16\\dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file has an mtime of when this was started.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file has an mtime of when this was started.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8777f260cbc77722
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"rustc":6313536168557467772,"features":"[]","declared_features":"[\"atomic-polyfill\", \"compile-time-rng\", \"const-random\", \"default\", \"getrandom\", \"nightly-arm-aes\", \"no-rng\", \"runtime-rng\", \"serde\", \"std\"]","target":8470944000320059508,"profile":15657897354478470176,"path":5775091198602256179,"deps":[[966925859616469517,"build_script_build",false,10636692056049571245],[2377604147989930065,"zerocopy",false,16907620962258622181],[3722963349756955755,"once_cell",false,9303175923353700557],[10411997081178400487,"cfg_if",false,12101758266265924634]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\ahash-d6e875a7356d7481\\dep-lib-ahash","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file has an mtime of when this was started.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
c8ee3675c4f10c8e
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"rustc":6313536168557467772,"features":"[\"curve\", \"default\", \"scalar_field\"]","declared_features":"[\"curve\", \"default\", \"scalar_field\", \"std\"]","target":5756399181311494987,"profile":15657897354478470176,"path":2486312074137155270,"deps":[[520424413174385823,"ark_ff",false,8224870178994912581],[10325592727886569959,"ark_ec",false,12962763712446827709],[15179503056858879355,"ark_std",false,8131744621584096543],[16925068697324277505,"ark_serialize",false,14631385335183464653]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\ark-bls12-381-b9350e1cec4ba808\\dep-lib-ark_bls12_381","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file has an mtime of when this was started.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bd8cb7b3eafce4b3
Loading