diff --git a/contract/src/lib.rs b/contract/src/lib.rs index 1d2721a..a652ae8 100644 --- a/contract/src/lib.rs +++ b/contract/src/lib.rs @@ -225,6 +225,18 @@ impl FlowPay { env.storage().instance().set(&DataKey::MaxBatchSize, &size); } + pub fn get_max_batch_size(env: Env) -> u32 { + batch::get_max_batch_size(&env) + } + + pub fn set_max_batch_size(env: Env, size: u32) { + admin::require_admin(&env); + if size > 200 { + env.panic_with_error(ContractError::InvalidBatchSize); + } + env.storage().instance().set(&DataKey::MaxBatchSize, &size); + } + /// Creates or replaces a recurring subscription for `user`. /// /// # Parameters @@ -528,6 +540,11 @@ impl FlowPay { merchant.require_auth(); + pub fn cancel_and_refund_prorated(env: Env, user: Address, merchant: Address) { + user.require_auth(); + merchant.require_auth(); + + pub fn cancel_and_refund_prorated(env: Env, user: Address, merchant: Address) { user.require_auth(); merchant.require_auth(); @@ -1531,6 +1548,8 @@ impl FlowPay { fn extend_subscription_ttl(env: &Env, user: &Address) { storage::extend_subscription_ttl(env, user); env.storage().instance().extend_ttl(SUBSCRIPTION_TTL_LEDGERS, SUBSCRIPTION_TTL_LEDGERS); + storage::extend_subscription_ttl(env, user); + env.storage().instance().extend_ttl(SUBSCRIPTION_TTL_LEDGERS, SUBSCRIPTION_TTL_LEDGERS); /// Refreshes the contract instance's TTL. Instance storage holds shared /// protocol state (Admin, Token, FeeCollector, FeeBps, GracePeriod, /// WhitelistEnabled, SchemaVersion, ActiveCount, ...) which all share one diff --git a/contract/src/test.rs b/contract/src/test.rs index 5c276d1..4e41b88 100644 --- a/contract/src/test.rs +++ b/contract/src/test.rs @@ -1924,6 +1924,117 @@ fn test_cancel_and_refund_prorated_transfers_expected_amount() { assert!(!sub.active); } +#[test] +fn test_cancel_and_refund_prorated_at_interval_end_transfers_nothing() { + let (env, contract_id, token_addr, user, merchant) = setup(); + let client = FlowPayClient::new(&env, &contract_id); + let token = TokenClient::new(&env, &token_addr); + let sac = StellarAssetClient::new(&env, &token_addr); + + env.budget().reset_unlimited(); + + let num_users = 50; + let mut users = soroban_sdk::Vec::new(&env); + let interval = 86400; + sac.mint(&merchant, &10_000_0000000); + + client.subscribe(&user, &merchant, &1_0000000, &3600, &token_addr, &None, &None); + + env.ledger().with_mut(|l| { + l.timestamp = 3600; + }); + + let merchant_balance_before = token.balance(&merchant); + let user_balance_before = token.balance(&user); + + client.cancel_and_refund_prorated(&user, &merchant); + + assert_eq!(token.balance(&merchant), merchant_balance_before); + assert_eq!(token.balance(&user), user_balance_before); + + let sub = client.get_subscription(&user).unwrap(); + assert!(!sub.active); +} + +#[test] +#[should_panic] +fn test_cancel_and_refund_prorated_missing_subscription_panics() { + let (env, contract_id, _token_addr, user, merchant) = setup(); + let client = FlowPayClient::new(&env, &contract_id); + + client.cancel_and_refund_prorated(&user, &merchant); +} + +#[test] +#[should_panic(expected = "Error(Contract, #20)")] +fn test_batch_charge_over_default_limit_panics() { + let (env, contract_id, token_addr, _user, merchant) = setup(); + let client = FlowPayClient::new(&env, &contract_id); + let token = TokenClient::new(&env, &token_addr); + let sac = StellarAssetClient::new(&env, &token_addr); + + let mut users = soroban_sdk::Vec::new(&env); + for _ in 0..51 { + let u = Address::generate(&env); + sac.mint(&u, &10_000_0000000); + token.approve(&u, &contract_id, &10_000_0000000, &200); + client.subscribe(&u, &merchant, &1_0000000, &86400, &token_addr, &None, &None); + users.push_back(u); + } + + client.batch_charge(&users); +} + +#[test] +#[should_panic(expected = "Error(Contract, #29)")] +fn test_set_max_batch_size_rejects_value_above_200() { + let (env, contract_id, _token_addr, user, _merchant) = setup(); + let client = FlowPayClient::new(&env, &contract_id); + + env.as_contract(&contract_id, || { + storage::set_admin(&env, &user); + }); + + client.set_max_batch_size(&201); +} + +#[test] +#[should_panic] +fn test_non_admin_set_max_batch_size_panics() { + let (env, contract_id, _token_addr, _user, _merchant) = setup(); + let client = FlowPayClient::new(&env, &contract_id); + + env.set_auths(&[]); + client.set_max_batch_size(&10); +} + +#[test] +fn test_cancel_and_refund_prorated_transfers_expected_amount() { + let (env, contract_id, token_addr, user, merchant) = setup(); + let client = FlowPayClient::new(&env, &contract_id); + let token = TokenClient::new(&env, &token_addr); + let sac = StellarAssetClient::new(&env, &token_addr); + + sac.mint(&merchant, &10_000_0000000); + + client.subscribe(&user, &merchant, &1_0000000, &3600, &token_addr, &None, &None); + + env.ledger().with_mut(|l| { + l.timestamp = 900; + }); + + let merchant_balance_before = token.balance(&merchant); + let user_balance_before = token.balance(&user); + + client.cancel_and_refund_prorated(&user, &merchant); + + assert_eq!(token.balance(&merchant), merchant_balance_before - 7_500_000); + assert_eq!(token.balance(&user), user_balance_before + 7_500_000); + + let sub = client.get_subscription(&user).unwrap(); + assert!(!sub.active); +} + #[test] fn test_cancel_and_refund_prorated_at_interval_end_transfers_nothing() { let (env, contract_id, token_addr, user, merchant) = setup();