Skip to content
Merged
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
103 changes: 77 additions & 26 deletions packages/tokens/src/rwa/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,35 +593,41 @@ impl RWA {
emit_identity_verifier_set(e, identity_verifier);
}

// ################## OVERRIDDEN FUNCTIONS ##################

/// This is a wrapper around [`Base::update()`] to enable
/// the compatibility across [`crate::fungible::FungibleToken`]
/// with [`crate::rwa::RWAToken`]
/// This function performs all the checks that are required
/// for a transfer but does not require authorization. It is used by
/// [`Self::transfer`] and [`Self::transfer_from`] overrides.
///
/// The main differences are:
/// - checks for if the contract is paused
/// - checks for if the addresses are frozen
/// - checks for if the from address have enough free tokens (unfrozen
/// tokens)
/// - enforces identity verification for both addresses
/// - enforces compliance rules for the transfer
/// - triggers `transferred` hook call from the compliance contract
/// # Arguments
///
/// Please refer to [`Base::update`] for the inline documentation.
pub fn transfer(e: &Env, from: &Address, to: &Address, amount: i128) {
from.require_auth();

/// * `e` - Access to the Soroban environment.
/// * `from` - The address of the sender.
/// * `to` - The address of the receiver.
/// * `amount` - The amount of tokens to transfer.
///
/// # Errors
///
/// * [`PausableError::EnforcedPause`] - If the contract is paused.
/// * [`RWAError::AddressFrozen`] - If either the sender or receiver is
/// frozen.
/// * [`RWAError::InsufficientFreeTokens`] - If the sender does not have
/// enough free tokens.
/// * refer to [`Self::identity_verifier`] errors.
/// * refer to [`Self::compliance`] errors.
/// * refer to [`IdentityVerifierClient::verify_identity`] errors.
/// * refer to [`Base::update`] errors.
///
/// # Events
///
/// * topics - `["transfer", from: Address, to: Address]`
/// * data - `["to_muxed_id: Option<u64>, amount: i128"]`
pub fn validate_transfer(e: &Env, from: &Address, to: &Address, amount: i128) {
// Check if contract is paused
if paused(e) {
panic_with_error!(e, PausableError::EnforcedPause);
}

// Check if addresses are frozen
if Self::is_frozen(e, from) {
panic_with_error!(e, RWAError::AddressFrozen);
}
if Self::is_frozen(e, to) {
if Self::is_frozen(e, from) || Self::is_frozen(e, to) {
panic_with_error!(e, RWAError::AddressFrozen);
}

Expand All @@ -645,22 +651,67 @@ impl RWA {
if !can_transfer {
panic_with_error!(e, RWAError::TransferNotCompliant);
}
}

// ################## OVERRIDDEN FUNCTIONS ##################

/// `transfer` override with added compliance and identity verification
/// checks.
///
/// This is ultimately a wrapper around [`Base::update()`] to enable
/// the compatibility across [`crate::fungible::FungibleToken`]
/// with [`crate::rwa::RWAToken`]
///
/// The main differences are:
/// - checks for if the contract is paused
/// - checks for if the addresses are frozen
/// - checks for if the from address have enough free tokens (unfrozen
/// tokens)
/// - enforces identity verification for both addresses
/// - enforces compliance rules for the transfer
/// - triggers `transferred` hook call from the compliance contract
///
/// Please refer to [`Base::update`] and [`Self::validate_transfer`] for the
/// inline documentation.
pub fn transfer(e: &Env, from: &Address, to: &Address, amount: i128) {
from.require_auth();

Self::validate_transfer(e, from, to, amount);

Base::update(e, Some(from), Some(to), amount);

let compliance_client = ComplianceClient::new(e, &Self::compliance(e));
compliance_client.transferred(from, to, &amount, &e.current_contract_address());

emit_transfer(e, from, to, None, amount);
}

/// This is a wrapper around [`Base::update()`] to enable
/// `transfer_from` override with added compliance and identity verification
/// checks.
///
/// This is ultimately a wrapper around [`Base::update()`] to enable
/// the compatibility across [`crate::fungible::FungibleToken`]
/// with [`crate::rwa::RWAToken`]
///
/// Please refer to [`Base::update`] and [`Self::transfer`] for the inline
/// documentation.
/// The main differences are:
/// - checks for if the contract is paused
/// - checks for if the addresses are frozen
/// - checks for if the from address have enough free tokens (unfrozen
/// tokens)
/// - enforces identity verification for both addresses
/// - enforces compliance rules for the transfer
/// - triggers `transferred` hook call from the compliance contract
///
/// Please refer to [`Base::update`] and [`Self::validate_transfer`] for the
/// inline documentation.
pub fn transfer_from(e: &Env, spender: &Address, from: &Address, to: &Address, amount: i128) {
spender.require_auth();

Base::spend_allowance(e, from, spender, amount);
Self::transfer(e, from, to, amount);

Base::update(e, Some(from), Some(to), amount);

let compliance_client = ComplianceClient::new(e, &Self::compliance(e));
compliance_client.transferred(from, to, &amount, &e.current_contract_address());
emit_transfer(e, from, to, None, amount);
}
}
48 changes: 48 additions & 0 deletions packages/tokens/src/rwa/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use soroban_sdk::{
contract, contractimpl, panic_with_error, symbol_short, testutils::Address as _, Address, Env,
String,
};
use stellar_contract_utils::pausable;

use crate::{
fungible::ContractOverrides,
Expand Down Expand Up @@ -854,3 +855,50 @@ fn transfer_fails_when_insufficient_free_tokens() {
RWA::transfer(&e, &from, &to, 50);
});
}

#[test]
#[should_panic(expected = "Error(Contract, #1000)")]
fn transfer_fails_when_contract_paused() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockRWAContract, ());
let from = Address::generate(&e);
let to = Address::generate(&e);

e.as_contract(&address, || {
setup_all_contracts(&e);

RWA::mint(&e, &from, 100);

// Pause the contract
pausable::pause(&e);

// Try to transfer - should fail with EnforcedPause error
RWA::transfer(&e, &from, &to, 50);
});
}

#[test]
#[should_panic(expected = "Error(Contract, #305)")]
fn transfer_fails_when_not_compliant() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockRWAContract, ());
let from = Address::generate(&e);
let to = Address::generate(&e);

e.as_contract(&address, || {
let _ = set_and_return_identity_verifier(&e);
let compliance = set_and_return_compliance(&e);

RWA::mint(&e, &from, 100);

// Set compliance to reject transfers
e.as_contract(&compliance, || {
e.storage().persistent().set(&symbol_short!("tx_ok"), &false);
});

// Try to transfer - should fail with TransferNotCompliant error
RWA::transfer(&e, &from, &to, 50);
});
}
Loading