Skip to content

Commit f2b3337

Browse files
authored
Merge pull request #456 from aliybabsi/feat/226-on-chain-asset-whitelist
Implemented On-chain Asset Whitelisting
2 parents 45dfe72 + a97066e commit f2b3337

16 files changed

Lines changed: 7380 additions & 153 deletions
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! Asset whitelist for protected assets.
2+
//!
3+
//! Trust model: only admin-verified callers may mutate the whitelist; all external callers are untrusted.
4+
//! Enforcement invariant: no contribution to a whitelisted asset may proceed unless the contributor's
5+
//! KYC tier meets or exceeds the asset's required tier at the time of the transaction.
6+
//! Atomicity invariant: any failed whitelist write leaves on-chain state unchanged.
7+
8+
use soroban_sdk::{Address, Env};
9+
10+
use shared::errors::Error;
11+
use shared::events::{ASSET_WHITELIST_REMOVED, ASSET_WHITELIST_SET};
12+
use shared::types::KycTier;
13+
14+
pub struct AssetWhitelist;
15+
16+
impl AssetWhitelist {
17+
fn storage_key(asset: &Address) -> (crate::DataKey, Address) {
18+
(crate::DataKey::AssetWhitelist, asset.clone())
19+
}
20+
21+
/// Set or update the required KYC tier for a protected asset.
22+
///
23+
/// # Arguments
24+
///
25+
/// * `env` - Contract environment.
26+
/// * `admin` - Admin address signing the transaction.
27+
/// * `asset` - Asset address being protected.
28+
/// * `required_tier` - Required KYC tier for this asset.
29+
///
30+
/// # Errors
31+
///
32+
/// * `Error::NotInit` if the contract has not been initialized.
33+
/// * `Error::Unauthorized` if the caller is not the stored admin.
34+
/// * `Error::InvInput` if `required_tier` is zero.
35+
pub fn set(env: Env, admin: Address, asset: Address, required_tier: KycTier) -> Result<(), Error> {
36+
if required_tier == 0 {
37+
return Err(Error::InvInput);
38+
}
39+
40+
let stored_admin: Address = env
41+
.storage()
42+
.instance()
43+
.get(&crate::DataKey::Admin)
44+
.ok_or(Error::NotInit)?;
45+
46+
if stored_admin != admin {
47+
return Err(Error::Unauthorized);
48+
}
49+
50+
admin.require_auth();
51+
env.storage()
52+
.instance()
53+
.set(&Self::storage_key(&asset), &required_tier);
54+
env.events()
55+
.publish((ASSET_WHITELIST_SET,), (asset, required_tier));
56+
57+
Ok(())
58+
}
59+
60+
/// Remove an asset from the protected asset whitelist.
61+
///
62+
/// # Arguments
63+
///
64+
/// * `env` - Contract environment.
65+
/// * `admin` - Admin address signing the transaction.
66+
/// * `asset` - Asset address to remove.
67+
///
68+
/// # Errors
69+
///
70+
/// * `Error::NotInit` if the contract has not been initialized.
71+
/// * `Error::Unauthorized` if the caller is not the stored admin.
72+
/// * `Error::NotFound` if the asset is not currently whitelisted.
73+
pub fn remove(env: Env, admin: Address, asset: Address) -> Result<(), Error> {
74+
let stored_admin: Address = env
75+
.storage()
76+
.instance()
77+
.get(&crate::DataKey::Admin)
78+
.ok_or(Error::NotInit)?;
79+
80+
if stored_admin != admin {
81+
return Err(Error::Unauthorized);
82+
}
83+
84+
admin.require_auth();
85+
let key = Self::storage_key(&asset);
86+
if !env.storage().instance().has(&key) {
87+
return Err(Error::NotFound);
88+
}
89+
90+
env.storage().instance().remove(&key);
91+
env.events().publish((ASSET_WHITELIST_REMOVED,), asset);
92+
Ok(())
93+
}
94+
95+
/// Get the required KYC tier for a protected asset.
96+
///
97+
/// Returns `None` when the asset is not whitelisted.
98+
pub fn get_required_tier(env: &Env, asset: &Address) -> Option<KycTier> {
99+
env.storage().instance().get(&Self::storage_key(asset))
100+
}
101+
102+
/// Validate a contributor's KYC tier against the asset's whitelist requirement.
103+
///
104+
/// # Arguments
105+
///
106+
/// * `env` - Contract environment.
107+
/// * `asset` - Asset address being contributed.
108+
/// * `user_tier` - Contributor's current KYC tier.
109+
///
110+
/// # Errors
111+
///
112+
/// * `Error::InvInput` if the stored asset requirement is invalid.
113+
/// * `Error::Unauthorized` if the contributor's tier is below the asset requirement.
114+
pub fn validate_asset_kyc(env: &Env, asset: &Address, user_tier: KycTier) -> Result<(), Error> {
115+
if let Some(required_tier) = Self::get_required_tier(env, asset) {
116+
if required_tier == 0 {
117+
return Err(Error::InvInput);
118+
}
119+
if user_tier < required_tier {
120+
return Err(Error::Unauthorized);
121+
}
122+
}
123+
Ok(())
124+
}
125+
}

0 commit comments

Comments
 (0)