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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ Cargo.lock
/target/
/target_local/
.md-*
hidden
hidden
7 changes: 4 additions & 3 deletions contracts/revenue_pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ impl RevenuePool {
/// * `usdc_token` – Stellar USDC (or wrapped USDC) token contract address.
pub fn init(env: Env, admin: Address, usdc_token: Address) {
admin.require_auth();
if env.storage().instance().has(&Symbol::new(&env, ADMIN_KEY)) {
let inst = env.storage().instance();
if inst.has(&Symbol::new(&env, ADMIN_KEY)) {
panic!("revenue pool already initialized");
}
let inst = env.storage().instance();
Expand All @@ -36,7 +37,7 @@ impl RevenuePool {
env.storage()
.instance()
.get(&Symbol::new(&env, ADMIN_KEY))
.unwrap_or_else(|| panic!("revenue pool not initialized"))
.expect("revenue pool not initialized")
}

/// Replace the current admin. Only the existing admin may call this.
Expand Down Expand Up @@ -156,7 +157,7 @@ impl RevenuePool {
.storage()
.instance()
.get(&Symbol::new(&env, USDC_KEY))
.unwrap_or_else(|| panic!("revenue pool not initialized"));
.expect("revenue pool not initialized");
let usdc = token::Client::new(&env, &usdc_address);
usdc.balance(&env.current_contract_address())
}
Expand Down
146 changes: 143 additions & 3 deletions contracts/revenue_pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,48 @@ fn distribute_negative_panics() {
assert!(result.is_err());
}

#[test]
fn receive_payment_from_non_vault() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let (_, client) = create_pool(&env);
let (usdc, _, _) = create_usdc(&env, &admin);

client.init(&admin, &usdc);
client.receive_payment(&admin, &250, &false);

let events = env.events().all();
assert!(!events.is_empty());

client.init(&admin, &usdc);
client.set_admin(&attacker, &new_admin);
}

#[test]
#[should_panic(expected = "revenue pool not initialized")]
fn balance_before_init_panics() {
let env = Env::default();
let (_, client) = create_pool(&env);
client.balance();
}

#[test]
fn distribute_negative_panics() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let developer = Address::generate(&env);
let (_, client) = create_pool(&env);
let (usdc, _, _) = create_usdc(&env, &admin);

client.init(&admin, &usdc);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
client.distribute(&admin, &developer, &-1);
}));
assert!(result.is_err());
}

#[test]
fn receive_payment_from_non_vault() {
let env = Env::default();
Expand Down Expand Up @@ -260,18 +302,43 @@ fn batch_distribute_success() {
assert_eq!(client.balance(), 500);
}

/// Full lifecycle test: init, get_admin, balance, distribute, receive_payment, set_admin.
#[test]
#[should_panic(expected = "unauthorized: caller is not admin")]
fn batch_distribute_unauthorized_panics() {
fn full_lifecycle() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let new_admin = Address::generate(&env);
let developer = Address::generate(&env);
let attacker = Address::generate(&env);
let dev = Address::generate(&env);
let (pool_addr, client) = create_pool(&env);
let (usdc_address, _, usdc_admin) = create_usdc(&env, &admin);
let (usdc_address, usdc_client, usdc_admin) = create_usdc(&env, &admin);

// Init
client.init(&admin, &usdc_address);
assert_eq!(client.get_admin(), admin);

// Fund and check balance
fund_pool(&usdc_admin, &pool_addr, 1000);
assert_eq!(client.balance(), 1000);

// Distribute
client.distribute(&admin, &developer, &400);
assert_eq!(usdc_client.balance(&developer), 400);
assert_eq!(client.balance(), 600);

// Receive payment event
client.receive_payment(&admin, &100, &true);

// Set admin
client.set_admin(&admin, &new_admin);
assert_eq!(client.get_admin(), new_admin);

// New admin can distribute
client.distribute(&new_admin, &developer, &100);
assert_eq!(usdc_client.balance(&developer), 500);
assert_eq!(client.balance(), 500);
fund_pool(&usdc_admin, &pool_addr, 500);

let mut payments: Vec<(Address, i128)> = Vec::new(&env);
Expand Down Expand Up @@ -314,3 +381,76 @@ fn batch_distribute_insufficient_balance_panics() {
payments.push_back((dev.clone(), 100_i128));
client.batch_distribute(&admin, &payments);
}

#[test]
fn batch_distribute_success() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let dev1 = Address::generate(&env);
let dev2 = Address::generate(&env);
let (pool_addr, client) = create_pool(&env);
let (usdc_address, usdc_client, usdc_admin) = create_usdc(&env, &admin);

client.init(&admin, &usdc_address);
fund_pool(&usdc_admin, &pool_addr, 1_000);

let payments = soroban_sdk::vec![&env, (dev1.clone(), 300_i128), (dev2.clone(), 200_i128)];
client.batch_distribute(&admin, &payments);

assert_eq!(usdc_client.balance(&dev1), 300);
assert_eq!(usdc_client.balance(&dev2), 200);
assert_eq!(usdc_client.balance(&pool_addr), 500);
}

#[test]
#[should_panic(expected = "unauthorized: caller is not admin")]
fn batch_distribute_unauthorized_panics() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let attacker = Address::generate(&env);
let dev = Address::generate(&env);
let (pool_addr, client) = create_pool(&env);
let (usdc_address, _, usdc_admin) = create_usdc(&env, &admin);

client.init(&admin, &usdc_address);
fund_pool(&usdc_admin, &pool_addr, 500);

let payments = soroban_sdk::vec![&env, (dev.clone(), 100_i128)];
client.batch_distribute(&attacker, &payments);
}

#[test]
#[should_panic(expected = "amount must be positive")]
fn batch_distribute_zero_amount_panics() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let dev = Address::generate(&env);
let (pool_addr, client) = create_pool(&env);
let (usdc_address, _, usdc_admin) = create_usdc(&env, &admin);

client.init(&admin, &usdc_address);
fund_pool(&usdc_admin, &pool_addr, 500);

let payments = soroban_sdk::vec![&env, (dev.clone(), 0_i128)];
client.batch_distribute(&admin, &payments);
}

#[test]
#[should_panic(expected = "insufficient USDC balance")]
fn batch_distribute_insufficient_balance_panics() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let dev = Address::generate(&env);
let (pool_addr, client) = create_pool(&env);
let (usdc_address, _, usdc_admin) = create_usdc(&env, &admin);

client.init(&admin, &usdc_address);
fund_pool(&usdc_admin, &pool_addr, 100);

let payments = soroban_sdk::vec![&env, (dev.clone(), 200_i128)];
client.batch_distribute(&admin, &payments);
}
63 changes: 63 additions & 0 deletions contracts/vault/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,8 @@ fn withdraw_reduces_balance() {
#[test]
fn withdraw_insufficient_balance_fails() {
let env = Env::default();
env.mock_all_auths();

let owner = Address::generate(&env);
let contract_id = env.register(CalloraVault {}, ());
let client = CalloraVaultClient::new(&env, &contract_id);
Expand Down Expand Up @@ -1195,6 +1197,67 @@ fn withdraw_to_reduces_balance() {
#[test]
fn withdraw_to_insufficient_balance_fails() {
let env = Env::default();
let owner = Address::generate(&env);
let recipient = Address::generate(&env);
let contract_id = env.register(CalloraVault {}, ());
let client = CalloraVaultClient::new(&env, &contract_id);
let (usdc_token, _, usdc_admin) = create_usdc(&env, &owner);

env.mock_all_auths();
fund_vault(&usdc_admin, &contract_id, 100);
client.init(&owner, &usdc_token, &Some(100), &None, &None, &None);

let result = client.try_withdraw_to(&recipient, &500);
assert!(result.is_err(), "expected error for insufficient balance");
}

#[test]
fn deposit_below_minimum_fails() {
let env = Env::default();
let owner = Address::generate(&env);
let depositor = Address::generate(&env);
let contract_id = env.register(CalloraVault {}, ());
let client = CalloraVaultClient::new(&env, &contract_id);
let (usdc_token, _, usdc_admin) = create_usdc(&env, &owner);

env.mock_all_auths();
fund_vault(&usdc_admin, &contract_id, 100);
client.init(&owner, &usdc_token, &Some(100), &Some(50), &None, &None);

fund_vault(&usdc_admin, &depositor, 30);
let usdc_client = token::Client::new(&env, &usdc_token);
usdc_client.approve(&depositor, &contract_id, &30, &1000);
let result = client.try_deposit(&depositor, &30);
assert!(result.is_err(), "expected error for deposit below minimum");
}

#[test]
fn deposit_at_minimum_succeeds() {
let env = Env::default();
let owner = Address::generate(&env);
let depositor = Address::generate(&env);
let contract_id = env.register(CalloraVault {}, ());
let client = CalloraVaultClient::new(&env, &contract_id);
let (usdc_token, _, usdc_admin) = create_usdc(&env, &owner);

env.mock_all_auths();
fund_vault(&usdc_admin, &contract_id, 100);
client.init(&owner, &usdc_token, &Some(100), &Some(50), &None, &None);

fund_vault(&usdc_admin, &depositor, 50);
let usdc_client = token::Client::new(&env, &usdc_token);
usdc_client.approve(&depositor, &contract_id, &50, &1000);
let new_balance = client.deposit(&depositor, &50);
assert_eq!(new_balance, 150);
}

#[test]
fn double_init_fails() {
let env = Env::default();
let owner = Address::generate(&env);
let contract_id = env.register(CalloraVault {}, ());
let client = CalloraVaultClient::new(&env, &contract_id);
let (usdc_token, _, usdc_admin) = create_usdc(&env, &owner);

let owner = Address::generate(&env);
let new_owner = Address::generate(&env);
Expand Down
Loading
Loading