Skip to content
55 changes: 39 additions & 16 deletions contracts/account/src/account.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use crate::errors::ContractError;
use crate::events::{
publish_account_initialized_event,
publish_account_restricted_event,
publish_account_verified_event,
publish_refund_processed_event,
publish_token_added_event,
publish_account_initialized_event, publish_account_restricted_event,
publish_account_verified_event, publish_refund_processed_event, publish_token_added_event,
publish_withdrawal_to_event,
};
use crate::interface::MerchantAccountTrait;
Expand All @@ -31,7 +28,10 @@ fn get_tracked_tokens(env: &Env) -> Vec<Address> {
}

fn is_restricted_account(env: &Env) -> bool {
env.storage().persistent().get(&DataKey::Restricted).unwrap_or(false)
env.storage()
.persistent()
.get(&DataKey::Restricted)
.unwrap_or(false)
}

fn token_exists(tracked_tokens: &Vec<Address>, token: &Address) -> bool {
Expand Down Expand Up @@ -67,14 +67,18 @@ impl MerchantAccountTrait for MerchantAccount {
merchant_id,
date_created: env.ledger().timestamp(),
};
env.storage().persistent().set(&DataKey::AccountInfo, &account_info);
env.storage().persistent().set(&DataKey::Merchant, &merchant);
env.storage()
.persistent()
.set(&DataKey::AccountInfo, &account_info);
env.storage()
.persistent()
.set(&DataKey::Merchant, &merchant);
env.storage().persistent().set(&DataKey::Manager, &manager);
publish_account_initialized_event(
&env,
merchant.clone(),
merchant_id,
env.ledger().timestamp()
env.ledger().timestamp(),
);
}
fn get_merchant(env: Env) -> Address {
Expand All @@ -94,7 +98,9 @@ impl MerchantAccountTrait for MerchantAccount {
}

tracked_tokens.push_back(token.clone());
env.storage().persistent().set(&DataKey::TrackedTokens, &tracked_tokens);
env.storage()
.persistent()
.set(&DataKey::TrackedTokens, &tracked_tokens);
publish_token_added_event(&env, token, env.ledger().timestamp());
}

Expand Down Expand Up @@ -152,14 +158,19 @@ impl MerchantAccountTrait for MerchantAccount {
}

fn is_verified_account(env: Env) -> bool {
env.storage().persistent().get(&DataKey::Verified).unwrap_or(false)
env.storage()
.persistent()
.get(&DataKey::Verified)
.unwrap_or(false)
}

fn restrict_account(env: Env, status: bool) {
let manager = get_manager(&env);
manager.require_auth();

env.storage().persistent().set(&DataKey::Restricted, &status);
env.storage()
.persistent()
.set(&DataKey::Restricted, &status);
publish_account_restricted_event(&env, status, env.ledger().timestamp());
}

Expand Down Expand Up @@ -199,7 +210,9 @@ impl MerchantAccountTrait for MerchantAccount {
env.storage()
.persistent()
.set(&DataKey::WithdrawalRequest(id), &request);
env.storage().persistent().set(&DataKey::WithdrawalCount, &id);
env.storage()
.persistent()
.set(&DataKey::WithdrawalCount, &id);
return;
}

Expand All @@ -209,11 +222,16 @@ impl MerchantAccountTrait for MerchantAccount {
fn set_withdrawal_threshold(env: Env, threshold: i128) {
let manager = get_manager(&env);
manager.require_auth();
env.storage().persistent().set(&DataKey::Threshold, &threshold);
env.storage()
.persistent()
.set(&DataKey::Threshold, &threshold);
}

fn get_withdrawal_threshold(env: Env) -> i128 {
env.storage().persistent().get(&DataKey::Threshold).unwrap_or(0)
env.storage()
.persistent()
.get(&DataKey::Threshold)
.unwrap_or(0)
}

fn approve_withdrawal(env: Env, request_id: u64) {
Expand Down Expand Up @@ -246,7 +264,12 @@ impl MerchantAccountTrait for MerchantAccount {
// If we have 2 approvals (merchant initiated + manager approved), execute
if request.approvals.len() >= 2 {
request.status = WithdrawalStatus::Executed;
Self::execute_withdrawal_internal(&env, &request.token, request.amount, &request.recipient);
Self::execute_withdrawal_internal(
&env,
&request.token,
request.amount,
&request.recipient,
);
}

env.storage()
Expand Down
25 changes: 15 additions & 10 deletions contracts/account/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use soroban_sdk::{ contractevent, Address, Env };
use soroban_sdk::{contractevent, Address, Env};

#[contractevent]
pub struct AccountInitializedEvent {
Expand All @@ -11,13 +11,14 @@ pub fn publish_account_initialized_event(
env: &Env,
merchant: Address,
merchant_id: u64,
timestamp: u64
timestamp: u64,
) {
(AccountInitializedEvent {
merchant,
merchant_id,
timestamp,
}).publish(env);
})
.publish(env);
}

#[contractevent(topics = ["account_restricted"])]
Expand Down Expand Up @@ -55,14 +56,15 @@ pub fn publish_refund_processed_event(
token: Address,
amount: i128,
to: Address,
timestamp: u64
timestamp: u64,
) {
(RefundProcessedEvent {
token,
amount,
recipient: to,
timestamp,
}).publish(env);
})
.publish(env);
}

#[contractevent]
Expand Down Expand Up @@ -90,15 +92,16 @@ pub fn publish_withdrawal_to_event(
merchant: Address,
recipient: Address,
amount: i128,
timestamp: u64
timestamp: u64,
) {
(WithdrawalToEvent {
token,
merchant,
recipient,
amount,
timestamp,
}).publish(env);
})
.publish(env);
}

#[contractevent]
Expand All @@ -118,7 +121,7 @@ pub fn publish_pending_withdrawal_created_event(
amount: i128,
recipient: Address,
initiator: Address,
timestamp: u64
timestamp: u64,
) {
(PendingWithdrawalCreatedEvent {
id,
Expand All @@ -127,7 +130,8 @@ pub fn publish_pending_withdrawal_created_event(
recipient,
initiator,
timestamp,
}).publish(env);
})
.publish(env);
}

#[contractevent]
Expand All @@ -142,7 +146,8 @@ pub fn publish_withdrawal_approved_event(env: &Env, id: u64, approver: Address,
id,
approver,
timestamp,
}).publish(env);
})
.publish(env);
}

#[contractevent]
Expand Down
33 changes: 26 additions & 7 deletions contracts/account/src/tests/test_withdrawal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ fn setup_initialized_account(env: &Env) -> (Address, MerchantAccountClient<'_>,

fn create_test_token(env: &Env) -> Address {
let token_admin = Address::generate(env);
env.register_stellar_asset_contract_v2(token_admin).address()
env.register_stellar_asset_contract_v2(token_admin)
.address()
}

/// Test Case 1: Successful Withdrawal
Expand Down Expand Up @@ -47,11 +48,17 @@ fn test_withdraw_to_success_with_balance() {

// Verify balance after withdrawal
let balance_after = client.get_balance(&token);
assert_eq!(balance_after, 2000, "Balance after withdrawal should be 2000");
assert_eq!(
balance_after, 2000,
"Balance after withdrawal should be 2000"
);

// Verify recipient received funds
let recipient_balance = token_client.balance(&recipient);
assert_eq!(recipient_balance, 3000, "Recipient should have received 3000");
assert_eq!(
recipient_balance, 3000,
"Recipient should have received 3000"
);
}

/// Test Case 2: Requires Merchant Authentication
Expand Down Expand Up @@ -116,11 +123,17 @@ fn test_withdraw_to_exact_balance() {

// Verify balance is now zero
let balance_after = client.get_balance(&token);
assert_eq!(balance_after, 0, "Balance after exact withdrawal should be 0");
assert_eq!(
balance_after, 0,
"Balance after exact withdrawal should be 0"
);

// Verify recipient received all funds
let recipient_balance = token_client.balance(&recipient);
assert_eq!(recipient_balance, 1000, "Recipient should have received 1000");
assert_eq!(
recipient_balance, 1000,
"Recipient should have received 1000"
);
}

/// Test Case 5: Multiple Withdrawals
Expand Down Expand Up @@ -182,11 +195,17 @@ fn test_withdraw_to_zero_amount() {

// Verify balance unchanged
let balance = client.get_balance(&token);
assert_eq!(balance, 5000, "Balance should remain unchanged after zero withdrawal");
assert_eq!(
balance, 5000,
"Balance should remain unchanged after zero withdrawal"
);

// Verify recipient received nothing
let recipient_balance = token_client.balance(&recipient);
assert_eq!(recipient_balance, 0, "Recipient should have received nothing");
assert_eq!(
recipient_balance, 0,
"Recipient should have received nothing"
);
}

/// Test Case 7: Withdrawal to Same Address
Expand Down
8 changes: 8 additions & 0 deletions contracts/crowdfund/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,12 @@ pub enum CrowdfundError {
InsufficientMatchingPool = 26,
// Pledge comment exceeds the configured maximum length.
CommentTooLong = 27,
// Caller is not authorized for this privileged view (organizer only).
NotAuthorized = 28,
// The backer already holds this badge.
BadgeAlreadyAwarded = 29,
// The backer does not meet this badge's on-chain eligibility rules.
BadgeNotEligible = 30,
// Badge eligibility thresholds have not been configured by the organizer.
BadgeConfigNotSet = 31,
}
Loading
Loading