diff --git a/contracts/account/src/tests/test_invoice_signed.rs b/contracts/account/src/tests/test_invoice_signed.rs
index 2c1415e..3b67f73 100644
--- a/contracts/account/src/tests/test_invoice_signed.rs
+++ b/contracts/account/src/tests/test_invoice_signed.rs
@@ -13,7 +13,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
@@ -341,4 +342,4 @@ fn test_create_invoice_signed_when_paused_fails() {
&generate_nonce(&env),
&generate_signature(&env),
);
-}
\ No newline at end of file
+}
diff --git a/contracts/shade/src/components/account_factory.rs b/contracts/shade/src/components/account_factory.rs
index 71b36ae..d3b53df 100644
--- a/contracts/shade/src/components/account_factory.rs
+++ b/contracts/shade/src/components/account_factory.rs
@@ -8,6 +8,22 @@ pub fn deploy_account(
merchant_id: u64,
wasm_hash: BytesN<32>,
) -> Address {
+ #[cfg(test)]
+ {
+ let deployed_contract = env.register(account::account::MerchantAccount, ());
+ let client = account::account::MerchantAccountClient::new(env, &deployed_contract);
+ client.initialize(&merchant, &manager, &merchant_id);
+
+ events::publish_merchant_account_deployed_event(
+ env,
+ merchant,
+ deployed_contract.clone(),
+ env.ledger().timestamp(),
+ );
+
+ return deployed_contract;
+ }
+
// Generate a random salt for deployment.
let random_bytes_n: BytesN<32> = env.prng().gen();
let random_bytes = Bytes::from_slice(env, &random_bytes_n.to_array());
diff --git a/contracts/shade/src/components/admin.rs b/contracts/shade/src/components/admin.rs
index 07ac3b7..a3d7a93 100644
--- a/contracts/shade/src/components/admin.rs
+++ b/contracts/shade/src/components/admin.rs
@@ -4,6 +4,8 @@ use crate::events;
use crate::types::DataKey;
use soroban_sdk::{panic_with_error, token, Address, Env, Vec};
+const FEE_UPDATE_DELAY_SECS: u64 = 3600;
+
pub fn add_accepted_token(env: &Env, admin: &Address, token: &Address) {
reentrancy::enter(env);
core::assert_admin(env, admin);
@@ -80,7 +82,7 @@ pub fn set_account_wasm_hash(env: &Env, admin: &Address, wasm_hash: &soroban_sdk
core::assert_admin(env, admin);
env.storage()
.persistent()
- .set(&DataKey::AccountWasmHash, wasm_hash);
+ .set(&DataKey::MerchantAccountWasmHash, wasm_hash);
reentrancy::exit(env);
}
@@ -92,15 +94,54 @@ pub fn set_fee(env: &Env, admin: &Address, token: &Address, fee: i128) {
panic_with_error!(env, ContractError::TokenNotAccepted);
}
- env.storage()
+ let has_active_fee = env
+ .storage()
.persistent()
- .set(&DataKey::TokenFee(token.clone()), &fee);
+ .has(&DataKey::TokenFee(token.clone()));
+
+ if has_active_fee {
+ let activation_time = env.ledger().timestamp() + FEE_UPDATE_DELAY_SECS;
+ env.storage()
+ .persistent()
+ .set(&DataKey::PendingTokenFee(token.clone()), &fee);
+ env.storage()
+ .persistent()
+ .set(&DataKey::PendingTokenFeeActivation(token.clone()), &activation_time);
+ } else {
+ env.storage()
+ .persistent()
+ .set(&DataKey::TokenFee(token.clone()), &fee);
+ }
events::publish_fee_set_event(env, token.clone(), fee, env.ledger().timestamp());
reentrancy::exit(env);
}
pub fn get_fee(env: &Env, token: &Address) -> i128 {
+ let now = env.ledger().timestamp();
+ if let Some(pending_fee) = env
+ .storage()
+ .persistent()
+ .get::<_, i128>(&DataKey::PendingTokenFee(token.clone()))
+ {
+ let activation_time: u64 = env
+ .storage()
+ .persistent()
+ .get(&DataKey::PendingTokenFeeActivation(token.clone()))
+ .unwrap_or(now + 1);
+ if now >= activation_time {
+ env.storage()
+ .persistent()
+ .set(&DataKey::TokenFee(token.clone()), &pending_fee);
+ env.storage()
+ .persistent()
+ .remove(&DataKey::PendingTokenFee(token.clone()));
+ env.storage()
+ .persistent()
+ .remove(&DataKey::PendingTokenFeeActivation(token.clone()));
+ }
+ }
+
env.storage()
.persistent()
.get(&DataKey::TokenFee(token.clone()))
diff --git a/contracts/shade/src/components/invoice.rs b/contracts/shade/src/components/invoice.rs
index 44004f9..e45c88a 100644
--- a/contracts/shade/src/components/invoice.rs
+++ b/contracts/shade/src/components/invoice.rs
@@ -10,6 +10,8 @@ pub trait MerchantAccountRefund {
}
pub const MAX_REFUND_DURATION: u64 = 604_800;
+const DISCOUNT_TIER1_VOLUME: i128 = 1_000_000;
+const DISCOUNT_TIER2_VOLUME: i128 = 10_000_000;
pub fn create_invoice(
env: &Env,
@@ -361,7 +363,7 @@ pub fn pay_invoice_partial(env: &Env, payer: &Address, invoice_id: u64, amount:
panic_with_error!(env, ContractError::TokenNotAccepted);
}
- let fee_amount = get_fee_for_amount(env, &invoice.token, amount);
+ let fee_amount = get_fee_for_amount(env, &invoice.token, amount, invoice.merchant_id);
let merchant_amount = amount - fee_amount;
let token_client = token::TokenClient::new(env, &invoice.token);
@@ -392,6 +394,8 @@ pub fn pay_invoice_partial(env: &Env, payer: &Address, invoice_id: u64, amount:
.persistent()
.set(&DataKey::Invoice(invoice_id), &invoice);
+ record_merchant_volume(env, invoice.merchant_id, amount);
+
events::publish_invoice_paid_event(
env,
invoice_id,
@@ -403,6 +407,13 @@ pub fn pay_invoice_partial(env: &Env, payer: &Address, invoice_id: u64, amount:
invoice.token.clone(),
env.ledger().timestamp(),
);
+ events::publish_fee_collected_event(
+ env,
+ fee_amount,
+ invoice.token.clone(),
+ merchant_account_id.clone(),
+ env.ledger().timestamp(),
+ );
fee_amount
}
@@ -492,16 +503,38 @@ pub fn amend_invoice(
);
}
-fn get_fee_for_amount(env: &Env, token: &Address, amount: i128) -> i128 {
- let fee_bps: i128 = env
- .storage()
- .persistent()
- .get(&DataKey::TokenFee(token.clone()))
- .unwrap_or(0);
+fn get_fee_for_amount(env: &Env, token: &Address, amount: i128, merchant_id: u64) -> i128 {
+ let fee_bps: i128 = admin::get_fee(env, token);
if fee_bps == 0 {
return 0;
}
- (amount * fee_bps) / 10_000i128
+ let adjusted_fee_bps = apply_fee_discount(env, merchant_id, fee_bps);
+ (amount * adjusted_fee_bps) / 10_000i128
+}
+
+fn apply_fee_discount(env: &Env, merchant_id: u64, fee_bps: i128) -> i128 {
+ let volume = get_merchant_volume(env, merchant_id);
+ if volume >= DISCOUNT_TIER2_VOLUME {
+ return fee_bps / 4;
+ }
+ if volume >= DISCOUNT_TIER1_VOLUME {
+ return fee_bps / 2;
+ }
+ fee_bps
+}
+
+fn get_merchant_volume(env: &Env, merchant_id: u64) -> i128 {
+ env.storage()
+ .persistent()
+ .get(&DataKey::MerchantVolume(merchant_id))
+ .unwrap_or(0)
+}
+
+fn record_merchant_volume(env: &Env, merchant_id: u64, amount: i128) {
+ let current = get_merchant_volume(env, merchant_id);
+ env.storage()
+ .persistent()
+ .set(&DataKey::MerchantVolume(merchant_id), &(current + amount));
}
diff --git a/contracts/shade/src/components/merchant.rs b/contracts/shade/src/components/merchant.rs
index c113cd1..a49b099 100644
--- a/contracts/shade/src/components/merchant.rs
+++ b/contracts/shade/src/components/merchant.rs
@@ -1,4 +1,4 @@
-use crate::components::access_control;
+use crate::components::{access_control, account_factory};
use crate::components::core as core_component;
use crate::errors::ContractError;
use crate::events;
@@ -29,9 +29,24 @@ pub fn register_merchant(env: &Env, merchant: &Address) {
let new_id = merchant_count + 1;
+ let wasm_hash: BytesN<32> = env
+ .storage()
+ .persistent()
+ .get(&DataKey::MerchantAccountWasmHash)
+ .unwrap_or_else(|| panic_with_error!(env, ContractError::WasmHashNotSet));
+
+ let merchant_account = account_factory::deploy_account(
+ env,
+ merchant.clone(),
+ env.current_contract_address(),
+ new_id,
+ wasm_hash,
+ );
+
let merchant_data = Merchant {
id: new_id,
address: merchant.clone(),
+ account: merchant_account.clone(),
active: true,
verified: false,
date_registered: env.ledger().timestamp(),
@@ -46,6 +61,9 @@ pub fn register_merchant(env: &Env, merchant: &Address) {
env.storage()
.persistent()
.set(&DataKey::MerchantCount, &new_id);
+ env.storage()
+ .persistent()
+ .set(&DataKey::MerchantAccount(new_id), &merchant_account);
events::publish_merchant_registered_event(
env,
@@ -248,7 +266,8 @@ pub fn restrict_merchant_account(
let account_address: Address = env
.storage()
.persistent()
- .get(&DataKey::MerchantAccount(merchant_id))
+ .get::<_, Merchant>(&DataKey::Merchant(merchant_id))
+ .map(|m| m.account)
.unwrap_or_else(|| merchant_address.clone());
let client = MerchantAccountClient::new(env, &account_address);
@@ -276,6 +295,15 @@ pub fn set_merchant_account(env: &Env, merchant: &Address, account: &Address) {
.get(&DataKey::MerchantId(merchant.clone()))
.unwrap();
+ let mut merchant_data: Merchant = env
+ .storage()
+ .persistent()
+ .get(&DataKey::Merchant(merchant_id))
+ .unwrap_or_else(|| panic_with_error!(env, ContractError::MerchantNotFound));
+ merchant_data.account = account.clone();
+ env.storage()
+ .persistent()
+ .set(&DataKey::Merchant(merchant_id), &merchant_data);
env.storage()
.persistent()
.set(&DataKey::MerchantAccount(merchant_id), account);
@@ -284,6 +312,7 @@ pub fn set_merchant_account(env: &Env, merchant: &Address, account: &Address) {
pub fn get_merchant_account(env: &Env, merchant_id: u64) -> Address {
env.storage()
.persistent()
- .get(&DataKey::MerchantAccount(merchant_id))
+ .get::<_, Merchant>(&DataKey::Merchant(merchant_id))
+ .map(|m| m.account)
.unwrap_or_else(|| panic_with_error!(env, ContractError::MerchantAccountNotSet))
}
diff --git a/contracts/shade/src/components/subscription.rs b/contracts/shade/src/components/subscription.rs
index 837496b..5585fb0 100644
--- a/contracts/shade/src/components/subscription.rs
+++ b/contracts/shade/src/components/subscription.rs
@@ -4,6 +4,9 @@ use crate::events;
use crate::types::{DataKey, Merchant, Subscription, SubscriptionPlan, SubscriptionStatus};
use soroban_sdk::{panic_with_error, token, Address, Env, String};
+const DISCOUNT_TIER1_VOLUME: i128 = 1_000_000;
+const DISCOUNT_TIER2_VOLUME: i128 = 10_000_000;
+
pub fn create_plan(
env: &Env,
merchant_address: &Address,
@@ -142,7 +145,7 @@ pub fn charge_subscription(env: &Env, subscription_id: u64) {
}
// Calculate fee split
- let fee_amount = get_fee_for_plan(env, &plan);
+ let fee_amount = get_fee_for_plan(env, &plan, plan.merchant_id);
let merchant_amount = plan.amount - fee_amount;
let token_client = token::TokenClient::new(env, &plan.token);
@@ -171,6 +174,8 @@ pub fn charge_subscription(env: &Env, subscription_id: u64) {
.persistent()
.set(&DataKey::Subscription(subscription_id), &subscription);
+ record_merchant_volume(env, plan.merchant_id, plan.amount);
+
events::publish_subscription_charged_event(
env,
subscription_id,
@@ -178,6 +183,13 @@ pub fn charge_subscription(env: &Env, subscription_id: u64) {
fee_amount,
env.ledger().timestamp(),
);
+ events::publish_fee_collected_event(
+ env,
+ fee_amount,
+ plan.token.clone(),
+ merchant_account_id.clone(),
+ env.ledger().timestamp(),
+ );
}
pub fn cancel_subscription(env: &Env, caller: &Address, subscription_id: u64) {
@@ -240,10 +252,36 @@ pub fn get_subscription(env: &Env, subscription_id: u64) -> Subscription {
.unwrap_or_else(|| panic_with_error!(env, ContractError::SubscriptionNotFound))
}
-fn get_fee_for_plan(env: &Env, plan: &SubscriptionPlan) -> i128 {
+fn get_fee_for_plan(env: &Env, plan: &SubscriptionPlan, merchant_id: u64) -> i128 {
let fee: i128 = admin::get_fee(env, &plan.token);
if fee == 0 {
return 0;
}
- (plan.amount * fee) / 10_000i128
+ let adjusted_fee = apply_fee_discount(env, merchant_id, fee);
+ (plan.amount * adjusted_fee) / 10_000i128
+}
+
+fn apply_fee_discount(env: &Env, merchant_id: u64, fee_bps: i128) -> i128 {
+ let volume = get_merchant_volume(env, merchant_id);
+ if volume >= DISCOUNT_TIER2_VOLUME {
+ return fee_bps / 4;
+ }
+ if volume >= DISCOUNT_TIER1_VOLUME {
+ return fee_bps / 2;
+ }
+ fee_bps
+}
+
+fn get_merchant_volume(env: &Env, merchant_id: u64) -> i128 {
+ env.storage()
+ .persistent()
+ .get(&DataKey::MerchantVolume(merchant_id))
+ .unwrap_or(0)
+}
+
+fn record_merchant_volume(env: &Env, merchant_id: u64, amount: i128) {
+ let current = get_merchant_volume(env, merchant_id);
+ env.storage()
+ .persistent()
+ .set(&DataKey::MerchantVolume(merchant_id), &(current + amount));
}
diff --git a/contracts/shade/src/events.rs b/contracts/shade/src/events.rs
index 4c0b0f5..d674277 100644
--- a/contracts/shade/src/events.rs
+++ b/contracts/shade/src/events.rs
@@ -283,6 +283,30 @@ pub fn publish_fee_set_event(env: &Env, token: Address, fee: i128, timestamp: u6
.publish(env);
}
+#[contractevent]
+pub struct FeeCollectedEvent {
+ pub fee: i128,
+ pub token: Address,
+ pub merchant_account: Address,
+ pub timestamp: u64,
+}
+
+pub fn publish_fee_collected_event(
+ env: &Env,
+ fee: i128,
+ token: Address,
+ merchant_account: Address,
+ timestamp: u64,
+) {
+ FeeCollectedEvent {
+ fee,
+ token,
+ merchant_account,
+ timestamp,
+ }
+ .publish(env);
+}
+
#[contractevent]
pub struct ContractUpgradedEvent {
pub new_wasm_hash: BytesN<32>,
diff --git a/contracts/shade/src/interface.rs b/contracts/shade/src/interface.rs
index 9c30a15..ff64864 100644
--- a/contracts/shade/src/interface.rs
+++ b/contracts/shade/src/interface.rs
@@ -5,7 +5,7 @@ use soroban_sdk::{contracttrait, Address, BytesN, Env, String, Vec};
#[contracttrait]
pub trait ShadeTrait {
- fn initialize(env: Env, admin: Address);
+ fn initialize(env: Env, admin: Address, account_wasm_hash: BytesN<32>);
fn get_admin(env: Env) -> Address;
fn add_accepted_token(env: Env, admin: Address, token: Address);
fn add_accepted_tokens(env: Env, admin: Address, tokens: Vec
);
diff --git a/contracts/shade/src/shade.rs b/contracts/shade/src/shade.rs
index 4bd38e1..7ab5c14 100644
--- a/contracts/shade/src/shade.rs
+++ b/contracts/shade/src/shade.rs
@@ -17,7 +17,7 @@ pub struct Shade;
#[contractimpl]
impl ShadeTrait for Shade {
- fn initialize(env: Env, admin: Address) {
+ fn initialize(env: Env, admin: Address, account_wasm_hash: BytesN<32>) {
if env.storage().persistent().has(&DataKey::Admin) {
panic_with_error!(&env, ContractError::AlreadyInitialized);
}
@@ -26,6 +26,9 @@ impl ShadeTrait for Shade {
timestamp: env.ledger().timestamp(),
};
env.storage().persistent().set(&DataKey::Admin, &admin);
+ env.storage()
+ .persistent()
+ .set(&DataKey::MerchantAccountWasmHash, &account_wasm_hash);
env.storage()
.persistent()
.set(&DataKey::ContractInfo, &contract_info);
diff --git a/contracts/shade/src/tests/test.rs b/contracts/shade/src/tests/test.rs
index f137b45..f0334ec 100644
--- a/contracts/shade/src/tests/test.rs
+++ b/contracts/shade/src/tests/test.rs
@@ -3,7 +3,7 @@
use crate::shade::Shade;
use crate::shade::ShadeClient;
use soroban_sdk::testutils::Address as _;
-use soroban_sdk::{Address, Env};
+use soroban_sdk::{Address, BytesN, Env};
#[test]
fn test_initialize() {
@@ -12,7 +12,8 @@ fn test_initialize() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
assert_eq!(client.get_admin(), admin);
}
@@ -24,8 +25,9 @@ fn test_initialize_twice() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
+ client.initialize(&admin, &account_wasm_hash);
}
#[should_panic(expected = "HostError: Error(Contract, #3)")]
diff --git a/contracts/shade/src/tests/test_accepted_tokens.rs b/contracts/shade/src/tests/test_accepted_tokens.rs
index 0bb330a..da95d93 100644
--- a/contracts/shade/src/tests/test_accepted_tokens.rs
+++ b/contracts/shade/src/tests/test_accepted_tokens.rs
@@ -5,7 +5,7 @@ use crate::errors::ContractError;
use crate::shade::Shade;
use crate::shade::ShadeClient;
use soroban_sdk::testutils::{Address as _, Events as _};
-use soroban_sdk::{Address, Env, Map, Symbol, TryIntoVal, Val, Vec};
+use soroban_sdk::{Address, BytesN, Env, Map, Symbol, TryIntoVal, Val, Vec};
fn assert_latest_token_event(
env: &Env,
@@ -44,7 +44,8 @@ fn test_admin_adds_token_and_emits_event() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env
@@ -76,7 +77,8 @@ fn test_batch_add_tokens() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token1 = env
@@ -110,7 +112,8 @@ fn test_admin_removes_token_and_emits_event() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env
@@ -147,7 +150,8 @@ fn test_duplicate_add_is_handled_gracefully() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env
@@ -173,7 +177,8 @@ fn test_non_admin_cannot_add_or_remove_tokens() {
let admin = Address::generate(&env);
let non_admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env
@@ -202,7 +207,8 @@ fn test_invalid_token_address_panics() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let invalid_token = Address::generate(&env);
client.add_accepted_token(&admin, &invalid_token);
diff --git a/contracts/shade/src/tests/test_access_control.rs b/contracts/shade/src/tests/test_access_control.rs
index 5e7d599..2b3dc5c 100644
--- a/contracts/shade/src/tests/test_access_control.rs
+++ b/contracts/shade/src/tests/test_access_control.rs
@@ -4,13 +4,14 @@ use crate::shade::Shade;
use crate::shade::ShadeClient;
use crate::types::Role;
use soroban_sdk::testutils::{Address as _, Events, MockAuth, MockAuthInvoke};
-use soroban_sdk::{Address, Env, FromVal, IntoVal, Symbol};
+use soroban_sdk::{Address, BytesN, Env, FromVal, IntoVal, Symbol};
fn setup_test(env: &Env) -> (ShadeClient<'_>, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(env, &contract_id);
let admin = Address::generate(env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(client, admin)
}
diff --git a/contracts/shade/src/tests/test_account_factory.rs b/contracts/shade/src/tests/test_account_factory.rs
index d0d79ac..49c2c79 100644
--- a/contracts/shade/src/tests/test_account_factory.rs
+++ b/contracts/shade/src/tests/test_account_factory.rs
@@ -1,11 +1,9 @@
#![cfg(test)]
+use crate::errors::ContractError;
use crate::shade::{Shade, ShadeClient};
use soroban_sdk::testutils::{Address as _, Events as _};
-use soroban_sdk::{Address, BytesN, Env, Map, Symbol, TryIntoVal, Val, Vec};
-
-// const ACCOUNT_WASM: &[u8] =
-// include_bytes!("../../../../target/wasm32-unknown-unknown/release/account.wasm");
+use soroban_sdk::{Address, BytesN, Env, Map, Symbol, TryIntoVal, Val};
fn setup() -> (Env, ShadeClient<'static>, Address) {
let env = Env::default();
@@ -15,7 +13,8 @@ fn setup() -> (Env, ShadeClient<'static>, Address) {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id)
}
@@ -25,12 +24,10 @@ fn assert_latest_merchant_account_deployed_event(
contract_id: &Address,
expected_merchant: &Address,
expected_contract: &Address,
- expected_timestamp: u64,
) {
let events = env.events().all();
assert!(!events.is_empty());
- // Find the most recent merchant_account_deployed_event
let mut found = false;
for i in (0..events.len()).rev() {
let (event_contract_id_i, topics_i, data_i) = events.get(i).unwrap();
@@ -41,15 +38,12 @@ fn assert_latest_merchant_account_deployed_event(
let data_map: Map = data_i.try_into_val(env).unwrap();
let merchant_val = data_map.get(Symbol::new(env, "merchant")).unwrap();
let contract_val = data_map.get(Symbol::new(env, "contract")).unwrap();
- let timestamp_val = data_map.get(Symbol::new(env, "timestamp")).unwrap();
let merchant_in_event: Address = merchant_val.try_into_val(env).unwrap();
let contract_in_event: Address = contract_val.try_into_val(env).unwrap();
- let timestamp_in_event: u64 = timestamp_val.try_into_val(env).unwrap();
assert_eq!(merchant_in_event, expected_merchant.clone());
assert_eq!(contract_in_event, expected_contract.clone());
- assert_eq!(timestamp_in_event, expected_timestamp);
found = true;
break;
}
@@ -59,115 +53,58 @@ fn assert_latest_merchant_account_deployed_event(
}
#[test]
-fn test_deploy_account_in_isolation_and_initialize() {
- let (env, _client, contract_id) = setup();
+fn test_register_merchant_deploys_account_and_links() {
+ let (env, client, contract_id) = setup();
let merchant = Address::generate(&env);
+ client.register_merchant(&merchant);
- // Use test helper to register and initialize a MerchantAccount directly (avoids deployer/wasm complexity)
- let expected_timestamp = env.ledger().timestamp();
- let deployed = {
- let acct_id = env.register(account::account::MerchantAccount, ());
- let acct_client = account::account::MerchantAccountClient::new(&env, &acct_id);
- acct_client.initialize(&merchant, &contract_id, &1_u64);
- // publish event as the factory would (emit from Shade contract context)
- env.as_contract(&contract_id, || {
- crate::events::publish_merchant_account_deployed_event(
- &env,
- merchant.clone(),
- acct_id.clone(),
- env.ledger().timestamp(),
- );
- });
- acct_id
- };
-
- assert_latest_merchant_account_deployed_event(
- &env,
- &contract_id,
- &merchant,
- &deployed,
- expected_timestamp,
- );
-
- // Verify the deployed account is initialized and returns the merchant
- let merchant_from_account: Address = env.as_contract(&deployed, || {
- env.storage()
- .persistent()
- .get(&account::types::DataKey::Merchant)
- .unwrap()
- });
- assert_eq!(merchant_from_account, merchant);
+ let merchant_data = client.get_merchant(&1u64);
+ let account_addr = client.get_merchant_account(&1u64);
+
+ assert_eq!(merchant_data.account, account_addr);
+ assert_latest_merchant_account_deployed_event(&env, &contract_id, &merchant, &account_addr);
+
+ let account_client = account::account::MerchantAccountClient::new(&env, &account_addr);
+ assert_eq!(account_client.get_merchant(), merchant);
}
#[test]
-fn test_register_merchant_integration_and_uniqueness() {
- let (env, client, contract_id) = setup();
+fn test_register_multiple_merchants_have_unique_accounts() {
+ let (env, client, _contract_id) = setup();
let merchant_a = Address::generate(&env);
let merchant_b = Address::generate(&env);
- // Register merchants in shade
client.register_merchant(&merchant_a);
client.register_merchant(&merchant_b);
- // For testing integration we register account instances directly and simulate factory behavior
- let deployed_a = {
- let acct_id = env.register(account::account::MerchantAccount, ());
- let acct_client = account::account::MerchantAccountClient::new(&env, &acct_id);
- acct_client.initialize(&merchant_a, &contract_id, &1_u64);
- env.as_contract(&contract_id, || {
- crate::events::publish_merchant_account_deployed_event(
- &env,
- merchant_a.clone(),
- acct_id.clone(),
- env.ledger().timestamp(),
- );
- });
- acct_id
- };
-
- let deployed_b = {
- let acct_id = env.register(account::account::MerchantAccount, ());
- let acct_client = account::account::MerchantAccountClient::new(&env, &acct_id);
- acct_client.initialize(&merchant_b, &contract_id, &2_u64);
- env.as_contract(&contract_id, || {
- crate::events::publish_merchant_account_deployed_event(
- &env,
- merchant_b.clone(),
- acct_id.clone(),
- env.ledger().timestamp(),
- );
- });
- acct_id
- };
-
- // Ensure uniqueness
- assert_ne!(deployed_a, deployed_b);
-
- // Link accounts in shade (simulate on-chain linkage)
- client.set_merchant_account(&merchant_a, &deployed_a);
- client.set_merchant_account(&merchant_b, &deployed_b);
-
- // Verify shade reports the linked accounts
- let linked_a = client.get_merchant_account(&1u64);
- let linked_b = client.get_merchant_account(&2u64);
- assert_eq!(linked_a, deployed_a);
- assert_eq!(linked_b, deployed_b);
-
- // Verify deployed accounts are initialized correctly
- let mac_a_merchant: Address = env.as_contract(&deployed_a, || {
- env.storage()
- .persistent()
- .get(&account::types::DataKey::Merchant)
- .unwrap()
- });
- let mac_b_merchant: Address = env.as_contract(&deployed_b, || {
+ let account_a = client.get_merchant_account(&1u64);
+ let account_b = client.get_merchant_account(&2u64);
+ assert_ne!(account_a, account_b);
+}
+
+#[test]
+fn test_register_merchant_missing_wasm_hash_fails() {
+ let env = Env::default();
+ env.mock_all_auths();
+
+ let contract_id = env.register(Shade, ());
+ let client = ShadeClient::new(&env, &contract_id);
+
+ let admin = Address::generate(&env);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
+
+ env.as_contract(&contract_id, || {
env.storage()
.persistent()
- .get(&account::types::DataKey::Merchant)
- .unwrap()
+ .remove(&crate::types::DataKey::MerchantAccountWasmHash);
});
- assert_eq!(mac_a_merchant, merchant_a);
- assert_eq!(mac_b_merchant, merchant_b);
+
+ let merchant = Address::generate(&env);
+ let expected_error =
+ soroban_sdk::Error::from_contract_error(ContractError::WasmHashNotSet as u32);
+ let result = client.try_register_merchant(&merchant);
+ assert!(matches!(result, Err(Ok(err)) if err == expected_error));
}
diff --git a/contracts/shade/src/tests/test_admin_payment.rs b/contracts/shade/src/tests/test_admin_payment.rs
index 627cb21..122d2b3 100644
--- a/contracts/shade/src/tests/test_admin_payment.rs
+++ b/contracts/shade/src/tests/test_admin_payment.rs
@@ -2,8 +2,8 @@
use crate::shade::{Shade, ShadeClient};
use crate::types::{InvoiceStatus, Role};
-use soroban_sdk::testutils::Address as _;
-use soroban_sdk::{Address, Env, String};
+use soroban_sdk::testutils::{Address as _, Ledger as _};
+use soroban_sdk::{Address, BytesN, Env, String};
fn setup_invoice_test() -> (
Env,
@@ -20,7 +20,8 @@ fn setup_invoice_test() -> (
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let manager = Address::generate(&env);
let merchant = Address::generate(&env);
@@ -112,7 +113,9 @@ fn test_fee_preservation() {
// Set custom fee
let fee = 250i128;
+ env.ledger().set_timestamp(100);
client.set_fee(&admin, &token, &fee);
+ env.ledger().set_timestamp(100 + 3600);
// Create invoice
let invoice_id = client.create_invoice(
diff --git a/contracts/shade/src/tests/test_admin_transfer.rs b/contracts/shade/src/tests/test_admin_transfer.rs
index 574aff9..76fd17d 100644
--- a/contracts/shade/src/tests/test_admin_transfer.rs
+++ b/contracts/shade/src/tests/test_admin_transfer.rs
@@ -2,13 +2,14 @@
use crate::shade::{Shade, ShadeClient};
use soroban_sdk::testutils::{Address as _, MockAuth, MockAuthInvoke};
-use soroban_sdk::{Address, Env, IntoVal};
+use soroban_sdk::{Address, BytesN, Env, IntoVal};
fn setup_test(env: &Env) -> (ShadeClient<'_>, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(env, &contract_id);
let admin = Address::generate(env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(client, admin)
}
diff --git a/contracts/shade/src/tests/test_fees.rs b/contracts/shade/src/tests/test_fees.rs
index 0380ff6..3ca5ba1 100644
--- a/contracts/shade/src/tests/test_fees.rs
+++ b/contracts/shade/src/tests/test_fees.rs
@@ -4,8 +4,8 @@ use crate::components::admin as admin_component;
use crate::errors::ContractError;
use crate::shade::Shade;
use crate::shade::ShadeClient;
-use soroban_sdk::testutils::{Address as _, Events as _};
-use soroban_sdk::{Address, Env, Map, Symbol, TryIntoVal, Val};
+use soroban_sdk::testutils::{Address as _, Events as _, Ledger as _};
+use soroban_sdk::{Address, BytesN, Env, Map, Symbol, TryIntoVal, Val};
fn setup_with_accepted_token(env: &Env) -> (Address, ShadeClient<'_>, Address) {
env.mock_all_auths();
@@ -14,7 +14,8 @@ fn setup_with_accepted_token(env: &Env) -> (Address, ShadeClient<'_>, Address) {
let client = ShadeClient::new(env, &contract_id);
let admin = Address::generate(env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(env);
let token = env
@@ -83,7 +84,8 @@ fn test_set_fee_unaccepted_token() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let unaccepted_token = Address::generate(&env);
@@ -113,10 +115,14 @@ fn test_update_fee() {
let env = Env::default();
let (admin, client, token) = setup_with_accepted_token(&env);
+ env.ledger().set_timestamp(100);
client.set_fee(&admin, &token, &200);
assert_eq!(client.get_fee(&token), 200);
client.set_fee(&admin, &token, &750);
+ assert_eq!(client.get_fee(&token), 200);
+
+ env.ledger().set_timestamp(100 + 3600);
assert_eq!(client.get_fee(&token), 750);
}
diff --git a/contracts/shade/src/tests/test_invoice.rs b/contracts/shade/src/tests/test_invoice.rs
index 644b2fa..41ad712 100644
--- a/contracts/shade/src/tests/test_invoice.rs
+++ b/contracts/shade/src/tests/test_invoice.rs
@@ -4,7 +4,7 @@ use crate::shade::{Shade, ShadeClient};
use crate::types::{DataKey, InvoiceStatus};
use account::account::{MerchantAccount, MerchantAccountClient};
use soroban_sdk::testutils::{Address as _, Events as _, Ledger as _};
-use soroban_sdk::{token, Address, Env, Map, String, Symbol, TryIntoVal, Val};
+use soroban_sdk::{token, Address, BytesN, Env, Map, String, Symbol, TryIntoVal, Val};
fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let env = Env::default();
@@ -12,7 +12,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
@@ -348,9 +349,7 @@ fn test_void_invoice_already_paid() {
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = client.get_merchant_account(&1u64);
// Create and pay invoice
let description = String::from_str(&env, "Test Invoice");
@@ -394,9 +393,7 @@ fn test_pay_cancelled_invoice() {
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = client.get_merchant_account(&1u64);
// Create invoice
let description = String::from_str(&env, "Test Invoice");
@@ -510,9 +507,7 @@ fn test_amend_invoice_paid_fails() {
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = client.get_merchant_account(&1u64);
// Create and pay invoice
let description = String::from_str(&env, "Test Invoice");
@@ -618,7 +613,8 @@ fn setup_test_with_payment() -> (Env, ShadeClient<'static>, Address, Address, Ad
let shade_client = ShadeClient::new(&env, &shade_contract_id);
let admin = Address::generate(&env);
- shade_client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ shade_client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env.register_stellar_asset_contract_v2(token_admin.clone());
diff --git a/contracts/shade/src/tests/test_invoice_partial_refund.rs b/contracts/shade/src/tests/test_invoice_partial_refund.rs
index fd1956d..6de8b7e 100644
--- a/contracts/shade/src/tests/test_invoice_partial_refund.rs
+++ b/contracts/shade/src/tests/test_invoice_partial_refund.rs
@@ -4,7 +4,7 @@ use crate::shade::{Shade, ShadeClient};
use crate::types::{DataKey, InvoiceStatus};
use account::account::{MerchantAccount, MerchantAccountClient};
use soroban_sdk::testutils::{Address as _, Ledger as _};
-use soroban_sdk::{token, Address, Env, String};
+use soroban_sdk::{token, Address, BytesN, Env, String};
const INVOICE_AMOUNT: i128 = 1_000;
const REFUND_WINDOW_SECS: u64 = 604_800;
@@ -15,7 +15,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
diff --git a/contracts/shade/src/tests/test_invoice_signed.rs b/contracts/shade/src/tests/test_invoice_signed.rs
index 41bbb0c..e8887e0 100644
--- a/contracts/shade/src/tests/test_invoice_signed.rs
+++ b/contracts/shade/src/tests/test_invoice_signed.rs
@@ -15,7 +15,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
diff --git a/contracts/shade/src/tests/test_invoice_void.rs b/contracts/shade/src/tests/test_invoice_void.rs
index 86301d7..ac80b55 100644
--- a/contracts/shade/src/tests/test_invoice_void.rs
+++ b/contracts/shade/src/tests/test_invoice_void.rs
@@ -3,7 +3,7 @@
use crate::shade::{Shade, ShadeClient};
use crate::types::InvoiceStatus;
use soroban_sdk::testutils::Address as _;
-use soroban_sdk::{Address, Env, String};
+use soroban_sdk::{Address, BytesN, Env, String};
fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let env = Env::default();
@@ -11,7 +11,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
@@ -96,7 +97,8 @@ fn test_void_invoice_already_paid() {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env
@@ -110,9 +112,7 @@ fn test_void_invoice_already_paid() {
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = client.get_merchant_account(&1u64);
// Create invoice
let description = String::from_str(&env, "Test Invoice");
@@ -139,7 +139,8 @@ fn test_pay_voided_invoice() {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env
@@ -153,9 +154,7 @@ fn test_pay_voided_invoice() {
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = client.get_merchant_account(&1u64);
// Create invoice
let description = String::from_str(&env, "Test Invoice");
@@ -218,7 +217,8 @@ fn test_void_refunded_invoice() {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env
@@ -231,11 +231,7 @@ fn test_void_refunded_invoice() {
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
- let merchant_account_id = env.register(account::account::MerchantAccount, ());
- let merchant_account = account::account::MerchantAccountClient::new(&env, &merchant_account_id);
- merchant_account.initialize(&merchant, &contract_id, &1_u64);
-
- client.set_merchant_account(&merchant, &merchant_account_id);
+ let merchant_account_id = client.get_merchant_account(&1u64);
let description = String::from_str(&env, "Refundable Invoice");
let invoice_id = client.create_invoice(&merchant, &description, &1000, &token, &None);
diff --git a/contracts/shade/src/tests/test_merchant.rs b/contracts/shade/src/tests/test_merchant.rs
index a1ccca7..04c5d27 100644
--- a/contracts/shade/src/tests/test_merchant.rs
+++ b/contracts/shade/src/tests/test_merchant.rs
@@ -4,7 +4,7 @@ use crate::errors::ContractError;
use crate::shade::{Shade, ShadeClient};
use crate::types::DataKey;
use soroban_sdk::testutils::Address as _;
-use soroban_sdk::{Address, Env};
+use soroban_sdk::{Address, BytesN, Env};
fn setup_test() -> (Env, ShadeClient<'static>, Address) {
let env = Env::default();
@@ -14,7 +14,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address) {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id)
}
@@ -29,6 +30,7 @@ fn test_register_merchant_successfully() {
let merchant_data = client.get_merchant(&1u64);
assert_eq!(merchant_data.id, 1);
assert_eq!(merchant_data.address, merchant);
+ assert_eq!(merchant_data.account, client.get_merchant_account(&1u64));
assert!(merchant_data.active);
assert!(client.is_merchant(&merchant));
diff --git a/contracts/shade/src/tests/test_merchant_activation.rs b/contracts/shade/src/tests/test_merchant_activation.rs
index 247877e..24de1e2 100644
--- a/contracts/shade/src/tests/test_merchant_activation.rs
+++ b/contracts/shade/src/tests/test_merchant_activation.rs
@@ -5,7 +5,7 @@ use crate::errors::ContractError;
use crate::shade::Shade;
use crate::shade::ShadeClient;
use soroban_sdk::testutils::{Address as _, Events as _};
-use soroban_sdk::{Address, Env, Map, Symbol, TryIntoVal, Val};
+use soroban_sdk::{Address, BytesN, Env, Map, Symbol, TryIntoVal, Val};
fn assert_latest_merchant_status_event(
env: &Env,
@@ -50,7 +50,8 @@ fn test_successful_activation() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
@@ -91,7 +92,8 @@ fn test_successful_deactivation() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
@@ -127,7 +129,8 @@ fn test_unauthorized_status_change() {
let admin = Address::generate(&env);
let non_admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
@@ -153,7 +156,8 @@ fn test_invalid_merchant_id_status_change() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let invalid_merchant_id = 999u64;
client.set_merchant_status(&admin, &invalid_merchant_id, &true);
@@ -169,7 +173,8 @@ fn test_is_merchant_active_invalid_id() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let invalid_merchant_id = 999u64;
client.is_merchant_active(&invalid_merchant_id);
@@ -185,7 +190,8 @@ fn test_set_merchant_status_zero_id() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let zero_merchant_id = 0u64;
client.set_merchant_status(&admin, &zero_merchant_id, &true);
@@ -201,7 +207,8 @@ fn test_is_merchant_active_zero_id() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let zero_merchant_id = 0u64;
client.is_merchant_active(&zero_merchant_id);
diff --git a/contracts/shade/src/tests/test_merchant_key.rs b/contracts/shade/src/tests/test_merchant_key.rs
index fae8ffc..666d603 100644
--- a/contracts/shade/src/tests/test_merchant_key.rs
+++ b/contracts/shade/src/tests/test_merchant_key.rs
@@ -10,7 +10,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
diff --git a/contracts/shade/src/tests/test_merchant_verification.rs b/contracts/shade/src/tests/test_merchant_verification.rs
index 217538f..1b9a748 100644
--- a/contracts/shade/src/tests/test_merchant_verification.rs
+++ b/contracts/shade/src/tests/test_merchant_verification.rs
@@ -4,7 +4,7 @@ use crate::components::merchant as merchant_component;
use crate::errors::ContractError;
use crate::shade::{Shade, ShadeClient};
use soroban_sdk::testutils::{Address as _, Events as _};
-use soroban_sdk::{Address, Env, Map, Symbol, TryIntoVal, Val};
+use soroban_sdk::{Address, BytesN, Env, Map, Symbol, TryIntoVal, Val};
fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let env = Env::default();
@@ -14,7 +14,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
diff --git a/contracts/shade/src/tests/test_pausable.rs b/contracts/shade/src/tests/test_pausable.rs
index 2141e99..1dc6ca0 100644
--- a/contracts/shade/src/tests/test_pausable.rs
+++ b/contracts/shade/src/tests/test_pausable.rs
@@ -4,7 +4,7 @@ use crate::components::pausable as pausable_component;
use crate::errors::ContractError;
use crate::shade::{Shade, ShadeClient};
use soroban_sdk::testutils::{Address as _, Events as _};
-use soroban_sdk::{Address, Env, Map, Symbol, TryIntoVal, Val};
+use soroban_sdk::{Address, BytesN, Env, Map, Symbol, TryIntoVal, Val};
fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let env = Env::default();
@@ -14,7 +14,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
diff --git a/contracts/shade/src/tests/test_payment.rs b/contracts/shade/src/tests/test_payment.rs
index 5f1e637..c64f3a7 100644
--- a/contracts/shade/src/tests/test_payment.rs
+++ b/contracts/shade/src/tests/test_payment.rs
@@ -3,7 +3,7 @@
use crate::shade::{Shade, ShadeClient};
use crate::types::InvoiceStatus;
use soroban_sdk::testutils::{Address as _, Events, Ledger as _};
-use soroban_sdk::{token, Address, Env, Map, String, Symbol, TryIntoVal, Val};
+use soroban_sdk::{token, Address, BytesN, Env, Map, String, Symbol, TryIntoVal, Val};
fn setup_test_with_payment() -> (Env, ShadeClient<'static>, Address, Address, Address) {
let env = Env::default();
@@ -15,7 +15,8 @@ fn setup_test_with_payment() -> (Env, ShadeClient<'static>, Address, Address, Ad
// Initialize with admin
let admin = Address::generate(&env);
- shade_client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ shade_client.initialize(&admin, &account_wasm_hash);
// Create and register token
let token_admin = Address::generate(&env);
@@ -44,34 +45,87 @@ fn assert_latest_paid_event(
let events = env.events().all();
assert!(!events.is_empty(), "No events captured for payment");
- let (event_contract_id, _topics, data) = events.get(events.len() - 1).unwrap();
- assert_eq!(&event_contract_id, contract_id);
-
- let data_map: Map = data.try_into_val(env).unwrap();
-
- let invoice_id_val = data_map.get(Symbol::new(env, "invoice_id")).unwrap();
- let merchant_id_val = data_map.get(Symbol::new(env, "merchant_id")).unwrap();
- let merchant_account_val = data_map.get(Symbol::new(env, "merchant_account")).unwrap();
- let payer_val = data_map.get(Symbol::new(env, "payer")).unwrap();
- let amount_val = data_map.get(Symbol::new(env, "amount")).unwrap();
- let fee_val = data_map.get(Symbol::new(env, "fee")).unwrap();
- let token_val = data_map.get(Symbol::new(env, "token")).unwrap();
-
- let invoice_id_in_event: u64 = invoice_id_val.try_into_val(env).unwrap();
- let merchant_id_in_event: u64 = merchant_id_val.try_into_val(env).unwrap();
- let merchant_account_in_event: Address = merchant_account_val.try_into_val(env).unwrap();
- let payer_in_event: Address = payer_val.try_into_val(env).unwrap();
- let amount_in_event: i128 = amount_val.try_into_val(env).unwrap();
- let fee_in_event: i128 = fee_val.try_into_val(env).unwrap();
- let token_in_event: Address = token_val.try_into_val(env).unwrap();
-
- assert_eq!(invoice_id_in_event, expected_invoice_id);
- assert_eq!(merchant_id_in_event, expected_merchant_id);
- assert_eq!(merchant_account_in_event, expected_merchant_account.clone());
- assert_eq!(payer_in_event, expected_payer.clone());
- assert_eq!(amount_in_event, expected_amount);
- assert_eq!(fee_in_event, expected_fee);
- assert_eq!(token_in_event, expected_token.clone());
+ let mut found = false;
+ for i in (0..events.len()).rev() {
+ let (event_contract_id, topics, data) = events.get(i).unwrap();
+ if topics.len() != 1 {
+ continue;
+ }
+ let event_name: Symbol = topics.get(0).unwrap().try_into_val(env).unwrap();
+ if event_name != Symbol::new(env, "invoice_paid_event") {
+ continue;
+ }
+ assert_eq!(&event_contract_id, contract_id);
+
+ let data_map: Map = data.try_into_val(env).unwrap();
+
+ let invoice_id_val = data_map.get(Symbol::new(env, "invoice_id")).unwrap();
+ let merchant_id_val = data_map.get(Symbol::new(env, "merchant_id")).unwrap();
+ let merchant_account_val = data_map.get(Symbol::new(env, "merchant_account")).unwrap();
+ let payer_val = data_map.get(Symbol::new(env, "payer")).unwrap();
+ let amount_val = data_map.get(Symbol::new(env, "amount")).unwrap();
+ let fee_val = data_map.get(Symbol::new(env, "fee")).unwrap();
+ let token_val = data_map.get(Symbol::new(env, "token")).unwrap();
+
+ let invoice_id_in_event: u64 = invoice_id_val.try_into_val(env).unwrap();
+ let merchant_id_in_event: u64 = merchant_id_val.try_into_val(env).unwrap();
+ let merchant_account_in_event: Address = merchant_account_val.try_into_val(env).unwrap();
+ let payer_in_event: Address = payer_val.try_into_val(env).unwrap();
+ let amount_in_event: i128 = amount_val.try_into_val(env).unwrap();
+ let fee_in_event: i128 = fee_val.try_into_val(env).unwrap();
+ let token_in_event: Address = token_val.try_into_val(env).unwrap();
+
+ assert_eq!(invoice_id_in_event, expected_invoice_id);
+ assert_eq!(merchant_id_in_event, expected_merchant_id);
+ assert_eq!(merchant_account_in_event, expected_merchant_account.clone());
+ assert_eq!(payer_in_event, expected_payer.clone());
+ assert_eq!(amount_in_event, expected_amount);
+ assert_eq!(fee_in_event, expected_fee);
+ assert_eq!(token_in_event, expected_token.clone());
+ found = true;
+ break;
+ }
+ assert!(found, "invoice_paid_event not found in events");
+}
+
+fn assert_latest_fee_collected_event(
+ env: &Env,
+ contract_id: &Address,
+ expected_fee: i128,
+ expected_token: &Address,
+ expected_merchant_account: &Address,
+) {
+ let events = env.events().all();
+ assert!(!events.is_empty(), "No events captured for fee collection");
+
+ let mut found = false;
+ for i in (0..events.len()).rev() {
+ let (event_contract_id, topics, data) = events.get(i).unwrap();
+ if topics.len() != 1 {
+ continue;
+ }
+ let event_name: Symbol = topics.get(0).unwrap().try_into_val(env).unwrap();
+ if event_name != Symbol::new(env, "fee_collected_event") {
+ continue;
+ }
+ assert_eq!(&event_contract_id, contract_id);
+
+ let data_map: Map = data.try_into_val(env).unwrap();
+ let fee_val = data_map.get(Symbol::new(env, "fee")).unwrap();
+ let token_val = data_map.get(Symbol::new(env, "token")).unwrap();
+ let merchant_account_val = data_map.get(Symbol::new(env, "merchant_account")).unwrap();
+
+ let fee_in_event: i128 = fee_val.try_into_val(env).unwrap();
+ let token_in_event: Address = token_val.try_into_val(env).unwrap();
+ let merchant_account_in_event: Address = merchant_account_val.try_into_val(env).unwrap();
+
+ assert_eq!(fee_in_event, expected_fee);
+ assert_eq!(token_in_event, expected_token.clone());
+ assert_eq!(merchant_account_in_event, expected_merchant_account.clone());
+ found = true;
+ break;
+ }
+ assert!(found, "fee_collected_event not found in events");
}
#[test]
@@ -82,9 +136,7 @@ fn test_successful_payment_with_fee() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account (using a regular address as mock)
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice for 1000 units
let description = String::from_str(&env, "Test Invoice");
@@ -110,6 +162,7 @@ fn test_successful_payment_with_fee() {
50,
&token,
);
+ assert_latest_fee_collected_event(&env, &shade_contract_id, 50, &token, &merchant_account);
// Verify balances
let token_balance_client = token::TokenClient::new(&env, &token);
@@ -131,15 +184,15 @@ fn test_payment_with_zero_fee() {
let (env, shade_client, shade_contract_id, admin, token) = setup_test_with_payment();
// Set fee to 0 bps (0%)
+ env.ledger().set_timestamp(100);
shade_client.set_fee(&admin, &token, &0);
+ env.ledger().set_timestamp(100 + 3600);
// Register merchant
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice for 1000 units
let description = String::from_str(&env, "Test Invoice");
@@ -167,15 +220,15 @@ fn test_payment_with_maximum_fee() {
let (env, shade_client, shade_contract_id, admin, token) = setup_test_with_payment();
// Set fee to 10000 bps (100%)
+ env.ledger().set_timestamp(100);
shade_client.set_fee(&admin, &token, &10000);
+ env.ledger().set_timestamp(100 + 3600);
// Register merchant
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice for 1000 units
let description = String::from_str(&env, "Test Invoice");
@@ -206,8 +259,7 @@ fn test_payment_rejects_expired_invoice() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
let description = String::from_str(&env, "Expired Invoice");
let expires_at = 1000_u64;
@@ -231,9 +283,7 @@ fn test_payment_invoice_already_paid() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice
let description = String::from_str(&env, "Test Invoice");
@@ -260,9 +310,7 @@ fn test_payment_insufficient_funds() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice for 1000 units
let description = String::from_str(&env, "Test Invoice");
@@ -286,9 +334,7 @@ fn test_payment_token_not_accepted() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice with unaccepted token
let unaccepted_token_admin = Address::generate(&env);
@@ -321,7 +367,11 @@ fn test_payment_merchant_account_not_set() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // DO NOT set merchant account - this will cause the panic
+ env.as_contract(&shade_client.address, || {
+ env.storage()
+ .persistent()
+ .remove(&crate::types::DataKey::Merchant(1u64));
+ });
// Create invoice
let description = String::from_str(&env, "Test Invoice");
@@ -344,9 +394,7 @@ fn test_payment_payer_authorization() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice
let description = String::from_str(&env, "Test Invoice");
@@ -373,9 +421,7 @@ fn test_payment_updates_invoice_timestamps() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice
let description = String::from_str(&env, "Test Invoice");
@@ -404,15 +450,15 @@ fn test_fee_calculation_accuracy() {
let (env, shade_client, shade_contract_id, admin, token) = setup_test_with_payment();
// Test with 1% fee (100 bps)
+ env.ledger().set_timestamp(100);
shade_client.set_fee(&admin, &token, &100);
+ env.ledger().set_timestamp(100 + 3600);
// Register merchant
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- // Create merchant account
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
// Create invoice for 10000 units
let description = String::from_str(&env, "Test Invoice");
@@ -441,8 +487,7 @@ fn test_partial_payment_two_equal_steps_reaches_paid() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
let description = String::from_str(&env, "Partial Payment Invoice");
let invoice_id = shade_client.create_invoice(&merchant, &description, &1000, &token, &None);
@@ -477,8 +522,7 @@ fn test_partial_payment_collects_fees_proportionally_each_step() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
let description = String::from_str(&env, "Proportional Fee Invoice");
let invoice_id = shade_client.create_invoice(&merchant, &description, &1000, &token, &None);
@@ -497,6 +541,41 @@ fn test_partial_payment_collects_fees_proportionally_each_step() {
assert_eq!(token_balance_client.balance(&merchant_account), 950);
}
+#[test]
+fn test_fee_discount_applies_after_volume_threshold() {
+ let (env, shade_client, shade_contract_id, admin, token) = setup_test_with_payment();
+
+ env.ledger().set_timestamp(100);
+ shade_client.set_fee(&admin, &token, &1000); // 10%
+ env.ledger().set_timestamp(100 + 3600);
+
+ let merchant = Address::generate(&env);
+ shade_client.register_merchant(&merchant);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
+
+ let payer = Address::generate(&env);
+ let token_client = token::StellarAssetClient::new(&env, &token);
+ token_client.mint(&payer, &1_020_000);
+
+ let invoice_id_1 =
+ shade_client.create_invoice(&merchant, &String::from_str(&env, "Vol 1"), &1_000_000, &token, &None);
+ shade_client.pay_invoice(&payer, &invoice_id_1);
+
+ let invoice_id_2 =
+ shade_client.create_invoice(&merchant, &String::from_str(&env, "Vol 2"), &10_000, &token, &None);
+ shade_client.pay_invoice(&payer, &invoice_id_2);
+
+ let tok = token::TokenClient::new(&env, &token);
+ let shade_balance = tok.balance(&shade_contract_id);
+ let merchant_balance = tok.balance(&merchant_account);
+
+ // First payment at 10%: 100_000 fee, second payment at 5%: 500 fee
+ assert_eq!(shade_balance, 100_500);
+ assert_eq!(merchant_balance, 1_009_500);
+
+ assert_latest_fee_collected_event(&env, &shade_contract_id, 500, &token, &merchant_account);
+}
+
#[test]
#[should_panic(expected = "HostError: Error(Contract, #7)")]
fn test_partial_payment_cannot_exceed_requested_amount() {
@@ -504,8 +583,7 @@ fn test_partial_payment_cannot_exceed_requested_amount() {
let merchant = Address::generate(&env);
shade_client.register_merchant(&merchant);
- let merchant_account = Address::generate(&env);
- shade_client.set_merchant_account(&merchant, &merchant_account);
+ let merchant_account = shade_client.get_merchant_account(&1u64);
let description = String::from_str(&env, "Overpay Guard Invoice");
let invoice_id = shade_client.create_invoice(&merchant, &description, &1000, &token, &None);
diff --git a/contracts/shade/src/tests/test_refund.rs b/contracts/shade/src/tests/test_refund.rs
index bfc0c82..48b86ce 100644
--- a/contracts/shade/src/tests/test_refund.rs
+++ b/contracts/shade/src/tests/test_refund.rs
@@ -2,9 +2,8 @@
use crate::shade::{Shade, ShadeClient};
use crate::types::InvoiceStatus;
-use account::account::{MerchantAccount, MerchantAccountClient};
use soroban_sdk::testutils::{Address as _, Ledger as _};
-use soroban_sdk::{token, Address, Env, String};
+use soroban_sdk::{token, Address, BytesN, Env, String};
/// Shared setup: deploy Shade, initialize, register a token with **0 fee**,
/// register a merchant, deploy + link a merchant account, create an invoice,
@@ -34,7 +33,8 @@ fn setup_paid_invoice(pay_timestamp: u64) -> RefundTestContext<'static> {
let shade_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &shade_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
// Register token with 0 fee – keeps full amount in merchant account
let token_admin = Address::generate(&env);
@@ -46,11 +46,7 @@ fn setup_paid_invoice(pay_timestamp: u64) -> RefundTestContext<'static> {
// Register merchant + deploy merchant account contract
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
-
- let merchant_account_id = env.register(MerchantAccount, ());
- let merchant_account = MerchantAccountClient::new(&env, &merchant_account_id);
- merchant_account.initialize(&merchant, &shade_id, &1_u64);
- client.set_merchant_account(&merchant, &merchant_account_id);
+ let merchant_account_id = client.get_merchant_account(&1u64);
// Create invoice
let amount = 1_000_i128;
@@ -216,7 +212,8 @@ fn test_refund_pending_invoice_fails() {
let shade_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &shade_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
@@ -243,7 +240,8 @@ fn test_refund_cancelled_invoice_fails() {
let shade_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &shade_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
@@ -318,7 +316,8 @@ fn test_partial_refund_with_fee() {
let shade_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &shade_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token_contract = env.register_stellar_asset_contract_v2(token_admin.clone());
@@ -328,11 +327,7 @@ fn test_partial_refund_with_fee() {
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
-
- let merchant_account_id = env.register(MerchantAccount, ());
- let merchant_account = MerchantAccountClient::new(&env, &merchant_account_id);
- merchant_account.initialize(&merchant, &shade_id, &1_u64);
- client.set_merchant_account(&merchant, &merchant_account_id);
+ let merchant_account_id = client.get_merchant_account(&1u64);
let amount = 1_000_i128;
let description = String::from_str(&env, "Fee Refund");
diff --git a/contracts/shade/src/tests/test_signatures.rs b/contracts/shade/src/tests/test_signatures.rs
index 447dc20..1fadb19 100644
--- a/contracts/shade/src/tests/test_signatures.rs
+++ b/contracts/shade/src/tests/test_signatures.rs
@@ -19,7 +19,8 @@ fn setup_test() -> (Env, ShadeClient<'static>, Address, Address) {
let contract_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
(env, client, contract_id, admin)
}
diff --git a/contracts/shade/src/tests/test_subscription.rs b/contracts/shade/src/tests/test_subscription.rs
index c4a9268..0330810 100644
--- a/contracts/shade/src/tests/test_subscription.rs
+++ b/contracts/shade/src/tests/test_subscription.rs
@@ -2,9 +2,8 @@
use crate::shade::{Shade, ShadeClient};
use crate::types::SubscriptionStatus;
-use account::account::{MerchantAccount, MerchantAccountClient};
use soroban_sdk::testutils::{Address as _, Ledger as _};
-use soroban_sdk::{token, Address, Env, String};
+use soroban_sdk::{token, Address, BytesN, Env, String};
/// Monthly interval constant: 30 days = 2 592 000 seconds.
const MONTHLY_INTERVAL: u64 = 2_592_000;
@@ -28,7 +27,8 @@ fn setup_subscription_env() -> SubTestContext<'static> {
let shade_id = env.register(Shade, ());
let client = ShadeClient::new(&env, &shade_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
// Register token with 5% fee
let token_admin = Address::generate(&env);
@@ -40,11 +40,7 @@ fn setup_subscription_env() -> SubTestContext<'static> {
// Register merchant + merchant account
let merchant = Address::generate(&env);
client.register_merchant(&merchant);
-
- let merchant_account_id = env.register(MerchantAccount, ());
- let merchant_account = MerchantAccountClient::new(&env, &merchant_account_id);
- merchant_account.initialize(&merchant, &shade_id, &1_u64);
- client.set_merchant_account(&merchant, &merchant_account_id);
+ let merchant_account_id = client.get_merchant_account(&1u64);
// Merchant creates a monthly plan
let description = String::from_str(&env, "Monthly Pro Plan");
@@ -373,7 +369,9 @@ fn test_charge_with_zero_fee() {
let ctx = setup_subscription_env();
// Set fee to 0
+ ctx.env.ledger().set_timestamp(100);
ctx.client.set_fee(&ctx.admin, &ctx.token, &0);
+ ctx.env.ledger().set_timestamp(100 + 3600);
let customer = Address::generate(&ctx.env);
fund_and_approve(&ctx, &customer, 5_000);
diff --git a/contracts/shade/src/tests/test_upgrade.rs b/contracts/shade/src/tests/test_upgrade.rs
index 0d6f42d..c544796 100644
--- a/contracts/shade/src/tests/test_upgrade.rs
+++ b/contracts/shade/src/tests/test_upgrade.rs
@@ -42,7 +42,8 @@ fn test_admin_can_upgrade_successfully() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let v2_hash = env.deployer().upload_contract_wasm(V2_WASM);
client.upgrade(&v2_hash);
@@ -57,7 +58,8 @@ fn test_state_persists_after_upgrade() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let token_admin = Address::generate(&env);
let token = env
@@ -98,7 +100,8 @@ fn test_upgrade_emits_contract_upgraded_event() {
let client = ShadeClient::new(&env, &contract_id);
let admin = Address::generate(&env);
- client.initialize(&admin);
+ let account_wasm_hash = BytesN::from_array(&env, &[0; 32]);
+ client.initialize(&admin, &account_wasm_hash);
let v2_hash = env.deployer().upload_contract_wasm(V2_WASM);
let expected_timestamp = env.ledger().timestamp();
diff --git a/contracts/shade/src/types.rs b/contracts/shade/src/types.rs
index e7e456b..deacd78 100644
--- a/contracts/shade/src/types.rs
+++ b/contracts/shade/src/types.rs
@@ -14,19 +14,22 @@ pub enum DataKey {
MerchantCount,
MerchantId(Address),
TokenFee(Address),
+ PendingTokenFee(Address),
+ PendingTokenFeeActivation(Address),
MerchantTokens,
MerchantBalance(Address),
MerchantAccount(u64),
Invoice(u64),
InvoiceCount,
ReentrancyStatus,
- AccountWasmHash,
+ MerchantAccountWasmHash,
Role(Address, Role),
UsedNonce(Address, BytesN<32>),
Plan(u64),
PlanCount,
Subscription(u64),
SubscriptionCount,
+ MerchantVolume(u64),
}
#[contracttype]
@@ -41,6 +44,7 @@ pub struct ContractInfo {
pub struct Merchant {
pub id: u64,
pub address: Address,
+ pub account: Address,
pub active: bool,
pub verified: bool,
pub date_registered: u64,