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
8 changes: 8 additions & 0 deletions savings_goals/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,14 @@ impl SavingsGoalContract {
// Core goal operations
// -----------------------------------------------------------------------

/// Creates a new savings goal.
///
/// - `owner` must authorize the call.
/// - `target_amount` must be positive.
/// - `target_date` is stored as provided and may be in the past. This
/// supports backfill or migration use cases where historical goals are
/// recorded after the fact. Callers that need strictly future-dated
/// goals should validate this before invoking the contract.
pub fn create_goal(
env: Env,
owner: Address,
Expand Down
25 changes: 25 additions & 0 deletions savings_goals/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@ fn test_create_goal_unique_ids_succeeds() {
assert_ne!(id1, id2);
}

/// Documented behavior: past target dates are allowed (e.g. for backfill or
/// data migration). This test locks in that create_goal accepts a target_date
/// earlier than the current ledger timestamp and persists it as provided.
#[test]
fn test_create_goal_allows_past_target_date() {
let env = Env::default();
let contract_id = env.register_contract(None, SavingsGoalContract);
let client = SavingsGoalContractClient::new(&env, &contract_id);
let user = Address::generate(&env);

client.init();
env.mock_all_auths();

// Move ledger time forward so our target_date is clearly in the past.
set_time(&env, 2_000_000_000);
let past_target_date = 1_000_000_000u64;

let name = String::from_str(&env, "Backfill Goal");
let id = client.create_goal(&user, &name, &1000, &past_target_date);

assert_eq!(id, 1);
let goal = client.get_goal(&id).unwrap();
assert_eq!(goal.target_date, past_target_date);
}

// ============================================================================
// init() idempotency and NEXT_ID behavior
//
Expand Down
37 changes: 37 additions & 0 deletions savings_goals/tests/stress_test_large_amounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,43 @@ fn test_add_to_goal_overflow_panics() {
env.mock_all_auths();
client.add_to_goal(&owner, &goal_id, &overflow_amount);
}
#[test]
fn test_withdraw_from_goal_with_large_amount() {
let env = Env::default();
let contract_id = env.register_contract(None, SavingsGoalContract);
let client = SavingsGoalContractClient::new(&env, &contract_id);
let owner = <soroban_sdk::Address as AddressTrait>::generate(&env);

env.mock_all_auths();

let large_target = i128::MAX / 2;
let large_amount = i128::MAX / 4;

let goal_id = client.create_goal(
&owner,
&String::from_str(&env, "Large Goal"),
&large_target,
&2000000,
);

// Add funds
env.mock_all_auths();
client.add_to_goal(&owner, &goal_id, &large_amount);

// Unlock to allow withdrawal
env.mock_all_auths();
client.unlock_goal(&owner, &goal_id);

// Withdraw half
env.mock_all_auths();
let to_withdraw = large_amount / 2;
let remaining = client.withdraw_from_goal(&owner, &goal_id, &to_withdraw);

// For odd large_amount values, large_amount - (large_amount / 2) equals
// ceil(large_amount / 2), not exactly large_amount / 2. Assert on the
// invariant instead of assuming evenness.
assert_eq!(remaining + to_withdraw, large_amount);
}
// #[test]
// fn test_withdraw_from_goal_with_large_amount() {
// let env = Env::default();
Expand Down
Loading