Skip to content

Commit 28a715d

Browse files
author
Edward (Edwardbot)
committed
fix(asset-leasing): reject leased_mint == collateral_mint on create_lease
If both mints are the same SPL mint, the two vaults' PDA derivations still collapse to different addresses (their seeds differ) but they hold the same asset — rent streams out of the same token supply the lessee posted as collateral, and the 'what do I owe vs what do I hold' invariant breaks. Guard the case at the top of `handle_create_lease` with a new error, `LeasedMintEqualsCollateralMint`. New litesvm test `create_lease_rejects_same_mint_for_leased_and_collateral` verifies the rejection using a handcrafted instruction that sets both mint fields to the leased mint. 10 tests now pass (was 9).
1 parent 4fb8cc0 commit 28a715d

3 files changed

Lines changed: 69 additions & 0 deletions

File tree

defi/asset-leasing/anchor/programs/asset-leasing/src/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ pub enum AssetLeasingError {
3030
MathOverflow,
3131
#[msg("Signer is not authorised for this action")]
3232
Unauthorised,
33+
#[msg("Leased mint and collateral mint must be different")]
34+
LeasedMintEqualsCollateralMint,
3335
}

defi/asset-leasing/anchor/programs/asset-leasing/src/instructions/create_lease.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ pub fn handle_create_lease(
8080
maintenance_margin_bps: u16,
8181
liquidation_bounty_bps: u16,
8282
) -> Result<()> {
83+
// Reject leased_mint == collateral_mint. Allowing both to be the same SPL
84+
// mint would collapse the two vaults' seed derivations into one shared
85+
// token-balance pool, making rent-vs-collateral accounting ambiguous and
86+
// enabling griefing paths where the lessee's "collateral" is the same
87+
// asset they already hold as the lease principal.
88+
require!(
89+
context.accounts.leased_mint.key() != context.accounts.collateral_mint.key(),
90+
AssetLeasingError::LeasedMintEqualsCollateralMint
91+
);
92+
8393
require!(leased_amount > 0, AssetLeasingError::InvalidLeasedAmount);
8494
require!(
8595
required_collateral_amount > 0,

defi/asset-leasing/anchor/programs/asset-leasing/tests/test_asset_leasing.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,3 +878,60 @@ fn close_expired_cancels_listed_lease() {
878878
assert!(sc.svm.get_account(&leased_vault).is_none());
879879
assert!(sc.svm.get_account(&collateral_vault).is_none());
880880
}
881+
882+
#[test]
883+
fn create_lease_rejects_same_mint_for_leased_and_collateral() {
884+
// Collapsing leased_mint and collateral_mint into a single SPL mint would
885+
// also collapse the two vaults into one token-balance pool (same mint,
886+
// same authority seed pattern) and make rent-vs-collateral accounting
887+
// ambiguous. The program rejects this up-front with
888+
// `LeasedMintEqualsCollateralMint`.
889+
let mut sc = full_setup();
890+
let lease_id = 42u64;
891+
892+
// Build a `create_lease` instruction where the collateral_mint field
893+
// carries the same mint as leased_mint. We bypass `build_create_lease_ix`
894+
// because that helper always wires the two mints from the scenario.
895+
let (lease, leased_vault, collateral_vault) =
896+
lease_pdas(&sc.program_id, &sc.lessor.pubkey(), lease_id);
897+
let ix = Instruction::new_with_bytes(
898+
sc.program_id,
899+
&asset_leasing::instruction::CreateLease {
900+
lease_id,
901+
leased_amount: LEASED_AMOUNT,
902+
required_collateral_amount: REQUIRED_COLLATERAL,
903+
rent_per_second: RENT_PER_SECOND,
904+
duration_seconds: DURATION_SECONDS,
905+
maintenance_margin_bps: MAINTENANCE_MARGIN_BPS,
906+
liquidation_bounty_bps: LIQUIDATION_BOUNTY_BPS,
907+
}
908+
.data(),
909+
asset_leasing::accounts::CreateLease {
910+
lessor: sc.lessor.pubkey(),
911+
leased_mint: sc.leased_mint,
912+
// Same mint on both sides — should be rejected.
913+
collateral_mint: sc.leased_mint,
914+
lessor_leased_account: sc.lessor_leased_ata,
915+
lease,
916+
leased_vault,
917+
collateral_vault,
918+
token_program: token_program_id(),
919+
system_program: system_program::id(),
920+
}
921+
.to_account_metas(None),
922+
);
923+
924+
let result = send_transaction_from_instructions(
925+
&mut sc.svm,
926+
vec![ix],
927+
&[&sc.lessor],
928+
&sc.lessor.pubkey(),
929+
);
930+
931+
let err = result.expect_err("create_lease must reject identical leased/collateral mints");
932+
let rendered = format!("{err:?}");
933+
assert!(
934+
rendered.contains("LeasedMintEqualsCollateralMint") || rendered.contains("0x177e"),
935+
"unexpected failure mode: {rendered}"
936+
);
937+
}

0 commit comments

Comments
 (0)