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
169 changes: 169 additions & 0 deletions contracts/invoice_liquidity/src/tests_distribution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#![cfg(test)]

use super::*;
use soroban_sdk::{
contract, contractimpl, contracttype,
testutils::{Address as _, Ledger, MockAuth, MockAuthInvoke},
token::{Client as TokenClient, StellarAssetClient},
Address, Env, IntoVal,
};

const INVOICE_AMOUNT: i128 = 1_000_000_000;
const DISCOUNT_RATE: u32 = 300;
const DUE_DATE_OFFSET: u64 = 60 * 60 * 24 * 30;

#[contracttype]
enum DistDataKey {
Lp(Address),
}

#[contract]
struct MockDistribution;

#[contractimpl]
impl MockDistribution {
pub fn accrue_lp(env: Env, lp: Address, amount_usdc_equivalent: i128) {
let key = DistDataKey::Lp(lp);
let current: i128 = env.storage().persistent().get(&key).unwrap_or(0);
env.storage()
.persistent()
.set(&key, &(current + amount_usdc_equivalent));
}

pub fn lp_volume(env: Env, lp: Address) -> i128 {
env.storage()
.persistent()
.get(&DistDataKey::Lp(lp))
.unwrap_or(0)
}
}

struct DistTestEnv {
env: Env,
contract: InvoiceLiquidityContractClient<'static>,
token: TokenClient<'static>,
freelancer: Address,
payer: Address,
funder: Address,
}

fn setup() -> DistTestEnv {
let env = Env::default();
env.mock_all_auths();

let admin = Address::generate(&env);

let usdc_admin = Address::generate(&env);
let usdc_contract_id = env.register_stellar_asset_contract_v2(usdc_admin.clone());
let usdc_address = usdc_contract_id.address();
let token = TokenClient::new(&env, &usdc_address);
let token_admin = StellarAssetClient::new(&env, &usdc_address);

let eurc_admin = Address::generate(&env);
let eurc_contract_id = env.register_stellar_asset_contract_v2(eurc_admin);
let eurc_address = eurc_contract_id.address();

let xlm_admin = Address::generate(&env);
let xlm_contract_id = env.register_stellar_asset_contract_v2(xlm_admin);
let xlm_address = xlm_contract_id.address();

let freelancer = Address::generate(&env);
let payer = Address::generate(&env);
let funder = Address::generate(&env);

token_admin.mint(&funder, &(INVOICE_AMOUNT * 10));
token_admin.mint(&payer, &(INVOICE_AMOUNT * 10));

let contract_id = env.register_contract(None, InvoiceLiquidityContract);
let contract = InvoiceLiquidityContractClient::new(&env, &contract_id);
token_admin.mint(&contract.address, &(INVOICE_AMOUNT * 100));

contract.initialize(&admin, &usdc_address, &eurc_address, &xlm_address);

let mut ledger_info = env.ledger().get();
ledger_info.timestamp = 1_700_000_000;
env.ledger().set(ledger_info);

DistTestEnv {
env,
contract,
token,
freelancer,
payer,
funder,
}
}

#[test]
fn test_set_distribution_contract_succeeds_as_admin() {
let t = setup();
let dist_id = t.env.register_contract(None, MockDistribution);

let result = t.contract.try_set_distribution_contract(&dist_id);
assert!(result.is_ok());
}

#[test]
fn test_set_distribution_contract_rejects_non_admin() {
let t = setup();
let dist_id = t.env.register_contract(None, MockDistribution);
let imposter = Address::generate(&t.env);

t.env.mock_auths(&[MockAuth {
address: &imposter,
invoke: &MockAuthInvoke {
contract: &t.contract.address,
fn_name: "set_distribution_contract",
args: (dist_id.clone(),).into_val(&t.env),
sub_invokes: &[],
},
}]);

let result = t.contract.try_set_distribution_contract(&dist_id);
assert!(result.is_err());
}

#[test]
fn test_notify_distribution_funding_called_during_funding() {
let t = setup();
let dist_id = t.env.register_contract(None, MockDistribution);
let dist = MockDistributionClient::new(&t.env, &dist_id);

t.contract.set_distribution_contract(&dist_id);

let due_date = t.env.ledger().timestamp() + DUE_DATE_OFFSET;
let invoice_id = t.contract.submit_invoice(
&t.freelancer,
&t.payer,
&INVOICE_AMOUNT,
&due_date,
&DISCOUNT_RATE,
&t.token.address,
&ReferralCode::None,
);

t.contract
.fund_invoice(&t.funder, &invoice_id, &INVOICE_AMOUNT, &false);

assert_eq!(dist.lp_volume(&t.funder), INVOICE_AMOUNT);
}

#[test]
fn test_funding_succeeds_without_distribution_contract_set() {
let t = setup();
let due_date = t.env.ledger().timestamp() + DUE_DATE_OFFSET;
let invoice_id = t.contract.submit_invoice(
&t.freelancer,
&t.payer,
&INVOICE_AMOUNT,
&due_date,
&DISCOUNT_RATE,
&t.token.address,
&ReferralCode::None,
);

let result = t
.contract
.try_fund_invoice(&t.funder, &invoice_id, &INVOICE_AMOUNT, &false);
assert!(result.is_ok());
}
36 changes: 36 additions & 0 deletions contracts/invoice_liquidity/src/tests_new_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,3 +1057,39 @@ fn test_convert_invoice_token_emits_token_changed_event() {
assert_eq!(decoded.old_token, t.token.address);
assert_eq!(decoded.new_token, t.eurc_address);
}

#[test]
fn test_get_price_oracle_defaults_to_none() {
let t = setup();
assert_eq!(t.contract.get_price_oracle(), None);
}

#[test]
fn test_set_price_oracle_succeeds_as_admin() {
let t = setup();
let oracle_id = t.env.register_contract(None, MockPriceOracle);

let result = t.contract.try_set_price_oracle(&oracle_id);
assert!(result.is_ok());
assert_eq!(t.contract.get_price_oracle(), Some(oracle_id));
}

#[test]
fn test_set_price_oracle_rejects_non_admin() {
let t = setup();
let oracle_id = t.env.register_contract(None, MockPriceOracle);
let imposter = Address::generate(&t.env);

t.env.mock_auths(&[MockAuth {
address: &imposter,
invoke: &MockAuthInvoke {
contract: &t.contract.address,
fn_name: "set_price_oracle",
args: (oracle_id.clone(),).into_val(&t.env),
sub_invokes: &[],
},
}]);

let result = t.contract.try_set_price_oracle(&oracle_id);
assert!(result.is_err());
}
114 changes: 114 additions & 0 deletions contracts/invoice_liquidity/src/tests_referral.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#![cfg(test)]

use super::*;
use crate::test::setup;
use soroban_sdk::BytesN;

const INVOICE_AMOUNT: i128 = 1_000_000_000;
const DISCOUNT_RATE: u32 = 300;
const DUE_DATE_OFFSET: u64 = 60 * 60 * 24 * 30;

#[test]
fn test_referral_count_increments_on_submission() {
let t = setup();
let due_date = t.env.ledger().timestamp() + DUE_DATE_OFFSET;
let code = BytesN::from_array(&t.env, &[2u8; 32]);

t.contract.submit_invoice(
&t.freelancer,
&t.payer,
&INVOICE_AMOUNT,
&due_date,
&DISCOUNT_RATE,
&t.token.address,
&ReferralCode::Present(code.clone()),
);

let stats = t.contract.get_referral_stats(&code);
assert_eq!(stats, 1);
}

#[test]
fn test_referral_count_returns_zero_for_unknown_code() {
let t = setup();
let due_date = t.env.ledger().timestamp() + DUE_DATE_OFFSET;
let known_code = BytesN::from_array(&t.env, &[1u8; 32]);
let unknown_code = BytesN::from_array(&t.env, &[9u8; 32]);

t.contract.submit_invoice(
&t.freelancer,
&t.payer,
&INVOICE_AMOUNT,
&due_date,
&DISCOUNT_RATE,
&t.token.address,
&ReferralCode::Present(known_code),
);

let stats = t.contract.get_referral_stats(&unknown_code);
assert_eq!(stats, 0);
}

#[test]
fn test_referral_count_zero_when_no_submissions() {
let t = setup();
let code = BytesN::from_array(&t.env, &[5u8; 32]);

let stats = t.contract.get_referral_stats(&code);
assert_eq!(stats, 0);
}

#[test]
fn test_submit_invoice_without_referral_does_not_increment_stats() {
let t = setup();
let due_date = t.env.ledger().timestamp() + DUE_DATE_OFFSET;
let code = BytesN::from_array(&t.env, &[1u8; 32]);

t.contract.submit_invoice(
&t.freelancer,
&t.payer,
&INVOICE_AMOUNT,
&due_date,
&DISCOUNT_RATE,
&t.token.address,
&ReferralCode::None,
);

let stats = t.contract.get_referral_stats(&code);
assert_eq!(stats, 0);
}

#[test]
fn test_batch_submission_counts_referrals_correctly() {
let t = setup();
let due_date = t.env.ledger().timestamp() + DUE_DATE_OFFSET;
let code = BytesN::from_array(&t.env, &[7u8; 32]);

let mut batch = soroban_sdk::Vec::new(&t.env);
for _ in 0..3 {
batch.push_back(InvoiceParams {
freelancer: t.freelancer.clone(),
payer: t.payer.clone(),
amount: INVOICE_AMOUNT,
due_date,
discount_rate: DISCOUNT_RATE,
token: t.token.address.clone(),
referral_code: ReferralCode::Present(code.clone()),
});
}
batch.push_back(InvoiceParams {
freelancer: t.freelancer.clone(),
payer: t.payer.clone(),
amount: INVOICE_AMOUNT,
due_date,
discount_rate: DISCOUNT_RATE,
token: t.token.address.clone(),
referral_code: ReferralCode::None,
});

let result = t.contract.try_submit_invoices_batch(&batch);
assert!(result.is_ok());

let stats = t.contract.get_referral_stats(&code);
assert_eq!(stats, 3);
}
Loading
Loading