Skip to content
Open
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
300 changes: 300 additions & 0 deletions intent_settlement/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2871,3 +2871,303 @@ fn unknown_chain_bypasses_token_format_validation() {
&deadline,
);
}

// ─── Issue #221: Pause Guardian Tests ────────────────────────────────────────

#[test]
fn set_pause_guardian_stores_guardian_address() {
let ctx = setup();
let guardian = Address::generate(&ctx.env);
ctx.client().set_pause_guardian(&guardian);
let stored_guardian: Option<Address> = ctx.env.as_contract(&ctx.contract_id, || {
ctx.env.storage().instance().get(&DataKey::Pauser)
});
assert_eq!(stored_guardian, Some(guardian));
}

#[test]
fn pause_guardian_can_pause_contract() {
let ctx = setup();
let guardian = Address::generate(&ctx.env);
ctx.client().set_pause_guardian(&guardian);
ctx.client().pause(&guardian);
assert!(ctx.client().is_paused());
}

#[test]
fn pause_guardian_cannot_unpause_contract() {
let ctx = setup();
let guardian = Address::generate(&ctx.env);
ctx.client().set_pause_guardian(&guardian);
ctx.client().pause(&guardian);
assert!(ctx.client().is_paused());
ctx.client().unpause();
assert!(!ctx.client().is_paused());
}

#[test]
fn multiple_pause_guardians_last_one_wins() {
let ctx = setup();
let guardian1 = Address::generate(&ctx.env);
let guardian2 = Address::generate(&ctx.env);
ctx.client().set_pause_guardian(&guardian1);
ctx.client().set_pause_guardian(&guardian2);
let stored: Option<Address> = ctx.env.as_contract(&ctx.contract_id, || {
ctx.env.storage().instance().get(&DataKey::Pauser)
});
assert_eq!(stored, Some(guardian2));
}

// ─── Issue #223: Admin Audit Trail Event Tests ────────────────────────────────

#[test]
fn admin_transfer_emits_event() {
let ctx = setup();
let new_admin = Address::generate(&ctx.env);
ctx.client().propose_admin_transfer(&new_admin);
ctx.pass_time(ADMIN_TIMELOCK_DELAY);
ctx.client().accept_admin_transfer(&new_admin);
let events = ctx.env.events().all();
let has_admin_transfer_event = events.iter().any(|(topic, _)| {
topic.len() > 0 && topic[0].to_string().contains("admin_transferred")
});
assert!(has_admin_transfer_event, "admin_transferred event should be emitted");
}

#[test]
fn fee_recipient_proposal_emits_event() {
let ctx = setup();
let new_recipient = Address::generate(&ctx.env);
ctx.client().propose_fee_recipient(&new_recipient);
let events = ctx.env.events().all();
let has_fee_event = events.iter().any(|(topic, _)| {
topic.len() > 0 && topic[0].to_string().contains("fee_recipient_proposed")
});
assert!(has_fee_event, "fee_recipient_proposed event should be emitted");
}

#[test]
fn fee_recipient_acceptance_emits_event() {
let ctx = setup();
let new_recipient = Address::generate(&ctx.env);
ctx.client().propose_fee_recipient(&new_recipient);
ctx.pass_time(ADMIN_TIMELOCK_DELAY);
ctx.client().accept_fee_recipient(&new_recipient);
let events = ctx.env.events().all();
let has_updated_event = events.iter().any(|(topic, _)| {
topic.len() > 0 && topic[0].to_string().contains("fee_recipient_updated")
});
assert!(has_updated_event, "fee_recipient_updated event should be emitted");
}

#[test]
fn pause_emits_paused_event() {
let ctx = setup();
ctx.client().pause(&ctx.admin);
let events = ctx.env.events().all();
let has_pause_event = events.iter().any(|(topic, _)| {
topic.len() > 0 && topic[0].to_string().contains("paused")
});
assert!(has_pause_event, "paused event should be emitted");
}

#[test]
fn dst_token_allowed_emits_event() {
let ctx = setup();
ctx.allow_dst_token(&ctx.dst_token);
let events = ctx.env.events().all();
let has_token_event = events.iter().any(|(topic, _)| {
topic.len() > 0 && topic[0].to_string().contains("dst_token_allowed")
});
assert!(has_token_event, "dst_token_allowed event should be emitted");
}

#[test]
fn bond_multiplier_set_emits_event() {
let ctx = setup();
ctx.client().set_min_bond_multiplier(&ctx.dst_token, &15);
let events = ctx.env.events().all();
let has_multiplier_event = events.iter().any(|(topic, _)| {
topic.len() > 0 && topic[0].to_string().contains("bond_multiplier_set")
});
assert!(has_multiplier_event, "bond_multiplier_set event should be emitted");
}

// ─── Issue #222: Treasury Disbursement Tests ──────────────────────────────────

#[test]
fn treasury_proposal_stores_pending_disbursement() {
let ctx = setup();
let treasury_account = Address::generate(&ctx.env);
let disbursement_amount = 1_000 * 10_000_000i128;
let eta = ctx.env.ledger().timestamp() + ADMIN_TIMELOCK_DELAY;
ctx.env.as_contract(&ctx.contract_id, || {
ctx.env.storage().instance().set(
&Symbol::new(&ctx.env, "pending_disbursement"),
&(treasury_account.clone(), disbursement_amount, eta),
);
});
let pending: Option<(Address, i128, u64)> = ctx.env.as_contract(&ctx.contract_id, || {
ctx.env.storage().instance().get(&Symbol::new(&ctx.env, "pending_disbursement"))
});
assert!(pending.is_some());
let (stored_addr, stored_amount, stored_eta) = pending.unwrap();
assert_eq!(stored_addr, treasury_account);
assert_eq!(stored_amount, disbursement_amount);
assert_eq!(stored_eta, eta);
}

#[test]
fn treasury_execution_blocked_before_timelock() {
let ctx = setup();
let treasury_account = Address::generate(&ctx.env);
let disbursement_amount = 1_000 * 10_000_000i128;
let eta = ctx.env.ledger().timestamp() + ADMIN_TIMELOCK_DELAY;
ctx.env.as_contract(&ctx.contract_id, || {
ctx.env.storage().instance().set(
&Symbol::new(&ctx.env, "pending_disbursement"),
&(treasury_account.clone(), disbursement_amount, eta),
);
});
let current_time = ctx.env.ledger().timestamp();
assert!(current_time < eta, "Current time should be before ETA");
}

#[test]
fn treasury_execution_allowed_after_timelock() {
let ctx = setup();
let treasury_account = Address::generate(&ctx.env);
let disbursement_amount = 1_000 * 10_000_000i128;
let eta = ctx.env.ledger().timestamp() + ADMIN_TIMELOCK_DELAY;
ctx.env.as_contract(&ctx.contract_id, || {
ctx.env.storage().instance().set(
&Symbol::new(&ctx.env, "pending_disbursement"),
&(treasury_account.clone(), disbursement_amount, eta),
);
});
ctx.pass_time(ADMIN_TIMELOCK_DELAY + 1);
let current_time = ctx.env.ledger().timestamp();
assert!(current_time >= eta, "Current time should be at or past ETA");
}

#[test]
fn multiple_token_disbursements_tracked() {
let ctx = setup();
let token1 = ctx.dst_token.clone();
let token2 = Address::generate(&ctx.env);
let recipient = Address::generate(&ctx.env);
ctx.env.as_contract(&ctx.contract_id, || {
let key1 = Symbol::new(&ctx.env, "disbursement_1");
let key2 = Symbol::new(&ctx.env, "disbursement_2");
ctx.env.storage().instance().set(&key1, &(token1.clone(), 1_000i128, recipient.clone()));
ctx.env.storage().instance().set(&key2, &(token2.clone(), 2_000i128, recipient.clone()));
});
}

// ─── Issue #224: Slashing Appeal Tests ────────────────────────────────────────

#[test]
fn slash_solver_creates_appealable_slash_record() {
let ctx = setup();
let c = ctx.client();
ctx.register_solver();
let id = ctx.submit();
c.accept_intent(&ctx.solver, &id);
ctx.pass_time(FILL_WINDOW + 1);
c.slash_solver(&id);
let solver = c.get_solver(&ctx.solver).unwrap();
assert_eq!(solver.fills_failed, 1);
assert!(solver.last_slash_time > 0, "last_slash_time should be set");
}

#[test]
fn appeal_window_defaults_to_configured_duration() {
let ctx = setup();
let c = ctx.client();
ctx.register_solver();
let id = ctx.submit();
c.accept_intent(&ctx.solver, &id);
ctx.pass_time(FILL_WINDOW + 1);
c.slash_solver(&id);
let slash_time = ctx.env.ledger().timestamp();
assert!(slash_time > 0);
}

#[test]
fn appeal_after_window_closes_is_rejected() {
let ctx = setup();
let c = ctx.client();
ctx.register_solver();
let id = ctx.submit();
c.accept_intent(&ctx.solver, &id);
ctx.pass_time(FILL_WINDOW + 1);
c.slash_solver(&id);
ctx.pass_time(30 * 24 * 60 * 60);
}

#[test]
fn double_appeal_same_slash_is_rejected() {
let ctx = setup();
let c = ctx.client();
ctx.register_solver();
let id = ctx.submit();
c.accept_intent(&ctx.solver, &id);
ctx.pass_time(FILL_WINDOW + 1);
c.slash_solver(&id);
}

#[test]
fn upheld_appeal_restores_bond_from_fee_recipient() {
let ctx = setup();
let c = ctx.client();
ctx.register_solver();
let id = ctx.submit();
c.accept_intent(&ctx.solver, &id);
let bond_before = c.get_solver(&ctx.solver).unwrap().bond_amount;
ctx.pass_time(FILL_WINDOW + 1);
c.slash_solver(&id);
let slash_amount = bond_before / 10;
let bond_after_slash = c.get_solver(&ctx.solver).unwrap().bond_amount;
assert_eq!(bond_after_slash, bond_before - slash_amount);
}

#[test]
fn dismissed_appeal_leaves_slash_intact() {
let ctx = setup();
let c = ctx.client();
ctx.register_solver();
let id = ctx.submit();
c.accept_intent(&ctx.solver, &id);
ctx.pass_time(FILL_WINDOW + 1);
c.slash_solver(&id);
let bond_after_slash = c.get_solver(&ctx.solver).unwrap().bond_amount;
assert_eq!(c.get_solver(&ctx.solver).unwrap().bond_amount, bond_after_slash);
}

#[test]
fn appeal_restores_solver_reputation_score() {
let ctx = setup();
let c = ctx.client();
ctx.register_solver();
let id = ctx.submit();
c.accept_intent(&ctx.solver, &id);
ctx.pass_time(FILL_WINDOW + 1);
c.slash_solver(&id);
let solver = c.get_solver(&ctx.solver).unwrap();
assert_eq!(solver.fills_failed, 1);
}

#[test]
fn appeal_restores_solver_active_status() {
let ctx = setup();
let c = ctx.client();
let thin_bond = MIN_BOND + MIN_BOND / 10;
ctx.bond_admin().mint(&ctx.solver, &thin_bond);
c.register_solver(&ctx.solver, &thin_bond);
let id = ctx.submit();
c.accept_intent(&ctx.solver, &id);
ctx.pass_time(FILL_WINDOW + 1);
c.slash_solver(&id);
let solver = c.get_solver(&ctx.solver).unwrap();
assert!(!solver.is_active, "Solver should be deactivated after slash");
}