Skip to content
Open
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
9 changes: 9 additions & 0 deletions contracts/stream/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ impl StreamContract {
/// Create a salary stream with an optional cliff period (#123).
///
/// `cliff_time` — ledger timestamp before which nothing is claimable (0 = no cliff).
///
/// # Token approval / transfer security (#65)
///
/// This function calls `token::transfer(employer → contract, deposit)` — it transfers
/// **exactly** the `deposit` amount and nothing more. No `approve` call is made by
/// the contract; the caller must have pre-approved at least `deposit` tokens to this
/// contract address before invoking `create_stream`. This design prevents
/// over-approval: the contract can never pull more than the caller explicitly
/// authorised for this single transaction.
pub fn create_stream(
env: Env,
employer: Address,
Expand Down
25 changes: 25 additions & 0 deletions contracts/stream/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,31 @@ fn test_create_stream() {
assert!(!s.locked);
}

/// Issue #65: create_stream transfers exactly `deposit` tokens — no more, no less.
/// Verifies the exact-deposit approval model: employer balance decreases by exactly
/// `deposit` and the contract balance increases by exactly `deposit`.
#[test]
fn test_create_stream_transfers_exact_deposit() {
let (env, client) = setup();
let admin = Address::generate(&env);
let employer = Address::generate(&env);
let employee = Address::generate(&env);
let token_id = setup_token(&env, &employer);
let token = paystream_token::TokenContractClient::new(&env, &token_id);

let deposit: i128 = 5_000;
let employer_balance_before = token.balance(&employer);

client.initialize(&admin);
client.set_min_deposit(&admin, &0, &100);
client.create_stream(&employer, &employee, &token_id, &deposit, &1, &0, &0, &0);

// Employer lost exactly `deposit` tokens.
assert_eq!(token.balance(&employer), employer_balance_before - deposit);
// Contract holds exactly `deposit` tokens.
assert_eq!(token.balance(&env.current_contract_address()), deposit);
}

#[test]
fn test_claimable_increases_with_time() {
let (env, client) = setup();
Expand Down
Loading