Skip to content
1 change: 1 addition & 0 deletions contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"./asset-maintenance",
"assetsup",
"opsce",
"contrib",
"multisig-wallet",
"multisig_transfer",
Expand Down
1 change: 1 addition & 0 deletions contracts/assetsup/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ doctest = false

[dependencies]
soroban-sdk = { workspace = true }
opsce = { path = "../opsce" }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
64 changes: 56 additions & 8 deletions contracts/assetsup/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,24 +758,72 @@ impl AssetUpContract {
)
}

/// Add address to whitelist
pub fn add_to_whitelist(env: Env, asset_id: u64, address: Address) -> Result<(), Error> {
transfer_restrictions::add_to_whitelist(&env, asset_id, address)
/// Add address to whitelist (admin only)
pub fn add_to_whitelist(
env: Env,
asset_id: u64,
admin: Address,
address: Address,
) -> Result<(), Error> {
admin.require_auth();
let token = tokenization::get_tokenized_asset(&env, asset_id)?;
if token.tokenizer != admin {
return Err(Error::Unauthorized);
}

opsce::whitelist::add_to_whitelist(&env, asset_id, address.clone());
env.events()
.publish(("transfer", "whitelist_added"), (asset_id, address));
Ok(())
}

/// Remove address from whitelist
pub fn remove_from_whitelist(env: Env, asset_id: u64, address: Address) -> Result<(), Error> {
transfer_restrictions::remove_from_whitelist(&env, asset_id, address)
/// Remove address from whitelist (admin only)
pub fn remove_from_whitelist(
env: Env,
asset_id: u64,
admin: Address,
address: Address,
) -> Result<(), Error> {
admin.require_auth();
let token = tokenization::get_tokenized_asset(&env, asset_id)?;
if token.tokenizer != admin {
return Err(Error::Unauthorized);
}

opsce::whitelist::remove_from_whitelist(&env, asset_id, address.clone());
env.events()
.publish(("transfer", "whitelist_removed"), (asset_id, address));
Ok(())
}

/// Check if address is whitelisted
pub fn is_whitelisted(env: Env, asset_id: u64, address: Address) -> Result<bool, Error> {
transfer_restrictions::is_whitelisted(&env, asset_id, address)
Ok(opsce::whitelist::is_whitelisted(&env, asset_id, address))
}

/// Get whitelist
pub fn get_whitelist(env: Env, asset_id: u64) -> Result<Vec<Address>, Error> {
transfer_restrictions::get_whitelist(&env, asset_id)
Ok(opsce::whitelist::get_whitelist(&env, asset_id))
}

/// Enable or disable whitelist enforcement for an asset (admin only)
pub fn set_whitelist_enabled(
env: Env,
asset_id: u64,
admin: Address,
enabled: bool,
) -> Result<(), Error> {
admin.require_auth();
let token = tokenization::get_tokenized_asset(&env, asset_id)?;
if token.tokenizer != admin {
return Err(Error::Unauthorized);
}

opsce::whitelist::set_whitelist_enabled(&env, asset_id, enabled);
env.events()
.publish(("transfer", "whitelist_enabled"), (asset_id, enabled));

Ok(())
}

// =====================
Expand Down
2 changes: 1 addition & 1 deletion contracts/assetsup/src/tests/detokenization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ fn test_detokenization_clears_all_data() {

// Set up some data
client.transfer_tokens(&1u64, &user1, &user2, &600000i128);
client.add_to_whitelist(&1u64, &user2);
client.add_to_whitelist(&1u64, &user1, &user2);
client.enable_revenue_sharing(&1u64);

// Propose and execute detokenization
Expand Down
4 changes: 2 additions & 2 deletions contracts/assetsup/src/tests/integration_full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ fn test_transfer_restrictions_workflow() {
// Set transfer restrictions
client.set_transfer_restriction(&asset_id, &true);

// Add investor1 to whitelist
client.add_to_whitelist(&asset_id, &investor1);
// Add investor1 to whitelist (tokenizer is `owner`)
client.add_to_whitelist(&asset_id, &owner, &investor1);

// Transfer to whitelisted address should succeed
client.transfer_tokens(&asset_id, &owner, &investor1, &100000i128);
Expand Down
68 changes: 54 additions & 14 deletions contracts/assetsup/src/tests/transfer_restrictions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ fn test_add_to_whitelist() {
// Initially not whitelisted
assert!(!client.is_whitelisted(&1u64, &user2));

// Add to whitelist
client.add_to_whitelist(&1u64, &user2);
// Add to whitelist (tokenizer is `user1`)
client.add_to_whitelist(&1u64, &user1, &user2);

assert!(client.is_whitelisted(&1u64, &user2));
}
Expand All @@ -51,12 +51,12 @@ fn test_remove_from_whitelist() {
&AssetType::Physical,
);

// Add to whitelist
client.add_to_whitelist(&1u64, &user2);
// Add to whitelist (tokenizer is `user1`)
client.add_to_whitelist(&1u64, &user1, &user2);
assert!(client.is_whitelisted(&1u64, &user2));

// Remove from whitelist
client.remove_from_whitelist(&1u64, &user2);
client.remove_from_whitelist(&1u64, &user1, &user2);
assert!(!client.is_whitelisted(&1u64, &user2));
}

Expand All @@ -80,9 +80,9 @@ fn test_get_whitelist() {
&AssetType::Physical,
);

// Add multiple addresses to whitelist
client.add_to_whitelist(&1u64, &user2);
client.add_to_whitelist(&1u64, &user3);
// Add multiple addresses to whitelist (tokenizer is `user1`)
client.add_to_whitelist(&1u64, &user1, &user2);
client.add_to_whitelist(&1u64, &user1, &user3);

let whitelist = client.get_whitelist(&1u64);
assert_eq!(whitelist.len(), 2);
Expand All @@ -109,8 +109,8 @@ fn test_add_duplicate_to_whitelist() {
);

// Add to whitelist twice
client.add_to_whitelist(&1u64, &user2);
client.add_to_whitelist(&1u64, &user2);
client.add_to_whitelist(&1u64, &user1, &user2);
client.add_to_whitelist(&1u64, &user1, &user2);

// Should still only have one entry
let whitelist = client.get_whitelist(&1u64);
Expand Down Expand Up @@ -163,8 +163,8 @@ fn test_transfer_with_whitelist() {
&AssetType::Physical,
);

// Add user2 to whitelist
client.add_to_whitelist(&1u64, &user2);
// Add user2 to whitelist (tokenizer is `user1`)
client.add_to_whitelist(&1u64, &user1, &user2);

// Transfer should succeed
client.transfer_tokens(&1u64, &user1, &user2, &100000i128);
Expand Down Expand Up @@ -220,8 +220,8 @@ fn test_transfer_to_non_whitelisted_fails() {
&AssetType::Physical,
);

// Only user2 is whitelisted
client.add_to_whitelist(&2u64, &user2);
// Only user2 is whitelisted (tokenizer is `user1`)
client.add_to_whitelist(&2u64, &user1, &user2);

// Transfer to user3 (not whitelisted) should panic with TransferRestricted
client.transfer_tokens(&2u64, &user1, &user3, &100000i128);
Expand Down Expand Up @@ -251,3 +251,43 @@ fn test_empty_whitelist_allows_transfer() {
client.transfer_tokens(&3u64, &user1, &user2, &100000i128);
assert_eq!(client.get_token_balance(&3u64, &user2), 100000);
}

#[test]
fn test_whitelist_enforcement_toggle() {
let env = create_env();
let (admin, user1, user2, _) = create_mock_addresses(&env);
let client = initialize_contract(&env, &admin);

env.mock_all_auths();

client.tokenize_asset(
&1u64,
&String::from_str(&env, "TST"),
&1000000i128,
&6u32,
&100i128,
&user1,
&String::from_str(&env, "Test Token"),
&String::from_str(&env, "A test tokenized asset"),
&AssetType::Physical,
);

// Enable whitelist enforcement
client.set_whitelist_enabled(&1u64, &user1, &true);

// Add only recipient
client.add_to_whitelist(&1u64, &user1, &user2);

// Transfer should fail because sender (user1) is not whitelisted
let res = std::panic::catch_unwind(|| {
client.transfer_tokens(&1u64, &user1, &user2, &100000i128);
});
assert!(res.is_err());

// Now whitelist sender as well
client.add_to_whitelist(&1u64, &user1, &user1);

// Transfer should now succeed
client.transfer_tokens(&1u64, &user1, &user2, &100000i128);
assert_eq!(client.get_token_balance(&1u64, &user2), 100000);
}
55 changes: 38 additions & 17 deletions contracts/assetsup/src/tests/transfer_restrictions_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use soroban_sdk::{Address, Env, String};

use crate::tokenization;
use crate::transfer_restrictions;
use crate::AssetUpContractClient;
use crate::types::{AssetType, TransferRestriction};
use crate::AssetUpContract;

Expand Down Expand Up @@ -68,20 +69,28 @@ fn test_whitelist_operations() {

let (is_wl_after_add, list_len, is_wl_after_remove) = env.as_contract(&contract_id, || {
setup_tokenized_asset(&env, asset_id, &tokenizer);

// Add to whitelist
transfer_restrictions::add_to_whitelist(&env, asset_id, whitelisted.clone()).unwrap();

let is_wl_add =
transfer_restrictions::is_whitelisted(&env, asset_id, whitelisted.clone()).unwrap();
let whitelist = transfer_restrictions::get_whitelist(&env, asset_id).unwrap();
env.mock_all_auths();
let client = AssetUpContractClient::new(&env, &contract_id);

// Add to whitelist (admin/tokenizer)
client
.add_to_whitelist(&asset_id, &tokenizer, &whitelisted)
.unwrap();

let is_wl_add = client
.is_whitelisted(&asset_id, &whitelisted)
.unwrap();
let whitelist = client.get_whitelist(&asset_id).unwrap();
let len = whitelist.len();

// Remove from whitelist
transfer_restrictions::remove_from_whitelist(&env, asset_id, whitelisted.clone()).unwrap();
client
.remove_from_whitelist(&asset_id, &tokenizer, &whitelisted)
.unwrap();

let is_wl_rem =
transfer_restrictions::is_whitelisted(&env, asset_id, whitelisted.clone()).unwrap();
let is_wl_rem = client
.is_whitelisted(&asset_id, &whitelisted)
.unwrap();
(is_wl_add, len, is_wl_rem)
});

Expand All @@ -100,15 +109,19 @@ fn test_whitelist_duplicate_prevention() {

let list_len = env.as_contract(&contract_id, || {
setup_tokenized_asset(&env, asset_id, &tokenizer);
env.mock_all_auths();
let client = AssetUpContractClient::new(&env, &contract_id);

// Add to whitelist twice
transfer_restrictions::add_to_whitelist(&env, asset_id, whitelisted.clone()).unwrap();
transfer_restrictions::add_to_whitelist(&env, asset_id, whitelisted.clone()).unwrap();
client
.add_to_whitelist(&asset_id, &tokenizer, &whitelisted)
.unwrap();
client
.add_to_whitelist(&asset_id, &tokenizer, &whitelisted)
.unwrap();

// Should still have only 1 entry
transfer_restrictions::get_whitelist(&env, asset_id)
.unwrap()
.len()
client.get_whitelist(&asset_id).unwrap().len()
});

assert_eq!(list_len, 1);
Expand Down Expand Up @@ -178,9 +191,13 @@ fn test_validate_transfer_blocked_when_not_whitelisted() {

let (allowed_result, blocked_result) = env.as_contract(&contract_id, || {
setup_tokenized_asset(&env, asset_id, &tokenizer);
env.mock_all_auths();
let client = AssetUpContractClient::new(&env, &contract_id);

// Add only `whitelisted` to the whitelist
transfer_restrictions::add_to_whitelist(&env, asset_id, whitelisted.clone()).unwrap();
client
.add_to_whitelist(&asset_id, &tokenizer, &whitelisted)
.unwrap();

// Transfer to whitelisted address should be allowed
let allowed = transfer_restrictions::validate_transfer(
Expand Down Expand Up @@ -247,7 +264,11 @@ fn test_validate_transfer_accredited_required_uses_whitelist() {
geographic_allowed: soroban_sdk::Vec::new(&env),
};
transfer_restrictions::set_transfer_restriction(&env, asset_id, restriction).unwrap();
transfer_restrictions::add_to_whitelist(&env, asset_id, accredited.clone()).unwrap();
env.mock_all_auths();
let client = AssetUpContractClient::new(&env, &contract_id);
client
.add_to_whitelist(&asset_id, &tokenizer, &accredited)
.unwrap();

let ok = transfer_restrictions::validate_transfer(
&env,
Expand Down
Loading
Loading