Skip to content

Commit 62fac5f

Browse files
author
mikemaccana-edwardbot
committed
defi/asset-leasing: rename roles to holder/short_seller; rewrite README
WHY - Mike's terminology rule: one canonical name per concept, used consistently across code, tests, and docs. The old README mixed "lessor", "lender", "long holder", "lessee", "borrower", and "short seller" for the same two parties; the code used yet another pair (lessor/lessee) that the README excused as a legacy artefact. That's three names per role for readers to keep straight, which is two too many. - Mike's writing rules (no abbreviations, no ambiguous "it"/"this", no "TradFi-vs-onchain" framing for things that exist onchain too, no "skip ahead" suggestions, no inline glossary when Solana docs already define the terms). - "Securities lending" framing is wrong: SOL is not legally a security, this program isn't restricted to tokenised securities, and the mechanics work for any directional token loan. WHAT Code + tests: - Renamed `lessor` -> `holder` and `lessee` -> `short_seller` across every Rust source file, the test file, and Cargo.toml. Includes struct fields (Lease.holder, Lease.short_seller), account context fields (e.g. holder_leased_account, short_seller_collateral_account), local variables, function parameters, comments, and doc strings. - Updated PDA seed bytes to match: b"lessor" -> b"holder", b"lessee" -> b"short_seller". This breaks address compatibility with the previous build, which is fine — the program is not deployed. - Cleaned up a couple of incidental abbreviations Mike's coding style forbids (denom -> denominator in liquidate.rs, liq_ix -> liquidate_instruction in tests, "8 disc" comment -> "8 discriminator"). - All 11 LiteSVM integration tests still pass; `anchor build` produces a clean .so. README: - Used "holder" and "short seller" exclusively; dropped lessor / lessee / lender / borrower / long holder framings entirely. - Replaced the ambiguous "if it falls" sentence with explicit antecedents ("if the asset falls, the short seller profits"). - Reframed the TradFi parenthetical: the entities listed (ETFs, pension funds, passive allocators) exist both in TradFi and onchain, so dropped the "vs onchain" split. - Removed the "you can skip straight to" suggestion. Background sections are useful for everyone. - Replaced "onchain securities lending" with "directional token lending" in the lede; SOL isn't a security, and the program works for any directional loan. - Inline-linked Solana terminology (Anchor, instruction handler, program-derived address, associated token account, cross-program invocation) to https://solana.com/docs/terminology on first occurrence. No glossary footer note. - Removed the paragraph that excused the lessor/lessee identifiers as legacy — those identifiers no longer exist. - Misc consistency pass: "instruction handler" not "instruction" for the function/code, "onchain" one word everywhere, kept "TradFi" only where the analogy is to actual TradFi (securities lending).
1 parent cde6b0d commit 62fac5f

13 files changed

Lines changed: 625 additions & 623 deletions

File tree

defi/asset-leasing/anchor/README.md

Lines changed: 337 additions & 335 deletions
Large diffs are not rendered by default.

defi/asset-leasing/anchor/programs/asset-leasing/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ custom-panic = []
2121

2222
[dependencies]
2323
# `init-if-needed` is required because several instructions lazily create the
24-
# counterparty's associated token accounts (keeper's collateral associated token account on first liquidation, lessor's
24+
# counterparty's associated token accounts (keeper's collateral associated token account on first liquidation, holder's
2525
# leased associated token account on first return, etc.). Anchor forces an opt-in to make us
2626
# re-affirm that we verify ownership on every touch — which we do via the
2727
# `associated_token::authority = ...` constraints.

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
/// program-derived address seed for the `Lease` account. Combined with the lessor pubkey and a
2-
/// u64 `lease_id` so one lessor can run many leases in parallel.
1+
/// program-derived address seed for the `Lease` account. Combined with the holder pubkey and a
2+
/// u64 `lease_id` so one holder can run many leases in parallel.
33
pub const LEASE_SEED: &[u8] = b"lease";
44

55
/// program-derived address seed for the token vault that holds the leased tokens while the lease
66
/// is `Listed` and that accepts returned tokens on settlement.
77
pub const LEASED_VAULT_SEED: &[u8] = b"leased_vault";
88

9-
/// program-derived address seed for the token vault that escrows the lessee's collateral for the
9+
/// program-derived address seed for the token vault that escrows the short_seller's collateral for the
1010
/// life of the lease.
1111
pub const COLLATERAL_VAULT_SEED: &[u8] = b"collateral_vault";
1212

1313
/// Denominator for basis-point (basis points) ratios used for the maintenance margin
1414
/// and the liquidation bounty. 10_000 basis points = 100%.
1515
pub const BASIS_POINTS_DENOMINATOR: u64 = 10_000;
1616

17-
/// Maximum allowed maintenance margin: 50_000 basis points = 500%. Prevents the lessor
17+
/// Maximum allowed maintenance margin: 50_000 basis points = 500%. Prevents the holder
1818
/// setting an impossible margin that would let them liquidate on day one.
1919
pub const MAX_MAINTENANCE_MARGIN_BASIS_POINTS: u16 = 50_000;
2020

2121
/// Maximum liquidation bounty the keeper can claim: 2_000 basis points = 20%. Keeps
22-
/// most of the collateral flowing to the lessor on default.
22+
/// most of the collateral flowing to the holder on default.
2323
pub const MAX_LIQUIDATION_BOUNTY_BASIS_POINTS: u16 = 2_000;
2424

2525
/// A Pyth price update is considered stale if its `publish_time` is older

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

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,27 @@ use crate::{
1414
state::{Lease, LeaseStatus},
1515
};
1616

17-
/// Lessor-only recovery path. Two real-world situations collapse here:
17+
/// Holder-only recovery path. Two real-world situations collapse here:
1818
///
19-
/// - The lease sat in `Listed` and the lessor wants to cancel it, recovering
19+
/// - The lease sat in `Listed` and the holder wants to cancel it, recovering
2020
/// the leased tokens they pre-funded. Allowed any time.
21-
/// - The lease was `Active` but the lessee ghosted past `end_timestamp`. The lessor
21+
/// - The lease was `Active` but the short_seller ghosted past `end_timestamp`. The holder
2222
/// takes the collateral as compensation and closes the books.
2323
#[derive(Accounts)]
2424
pub struct CloseExpired<'info> {
2525
#[account(mut)]
26-
pub lessor: Signer<'info>,
26+
pub holder: Signer<'info>,
2727

2828
#[account(
2929
mut,
30-
seeds = [LEASE_SEED, lessor.key().as_ref(), &lease.lease_id.to_le_bytes()],
30+
seeds = [LEASE_SEED, holder.key().as_ref(), &lease.lease_id.to_le_bytes()],
3131
bump = lease.bump,
32-
has_one = lessor,
32+
has_one = holder,
3333
has_one = leased_mint,
3434
has_one = collateral_mint,
3535
constraint = matches!(lease.status, LeaseStatus::Listed | LeaseStatus::Active)
3636
@ AssetLeasingError::InvalidLeaseStatus,
37-
close = lessor,
37+
close = holder,
3838
)]
3939
pub lease: Account<'info, Lease>,
4040

@@ -63,21 +63,21 @@ pub struct CloseExpired<'info> {
6363

6464
#[account(
6565
init_if_needed,
66-
payer = lessor,
66+
payer = holder,
6767
associated_token::mint = leased_mint,
68-
associated_token::authority = lessor,
68+
associated_token::authority = holder,
6969
associated_token::token_program = token_program,
7070
)]
71-
pub lessor_leased_account: Box<InterfaceAccount<'info, TokenAccount>>,
71+
pub holder_leased_account: Box<InterfaceAccount<'info, TokenAccount>>,
7272

7373
#[account(
7474
init_if_needed,
75-
payer = lessor,
75+
payer = holder,
7676
associated_token::mint = collateral_mint,
77-
associated_token::authority = lessor,
77+
associated_token::authority = holder,
7878
associated_token::token_program = token_program,
7979
)]
80-
pub lessor_collateral_account: Box<InterfaceAccount<'info, TokenAccount>>,
80+
pub holder_collateral_account: Box<InterfaceAccount<'info, TokenAccount>>,
8181

8282
pub token_program: Interface<'info, TokenInterface>,
8383
pub associated_token_program: Program<'info, AssociatedToken>,
@@ -111,14 +111,14 @@ pub fn handle_close_expired(context: Context<CloseExpired>) -> Result<()> {
111111
core::slice::from_ref(&collateral_vault_bump),
112112
];
113113

114-
// Drain whatever is in the leased vault back to the lessor. For a Listed
114+
// Drain whatever is in the leased vault back to the holder. For a Listed
115115
// lease this is the full leased_amount; for a defaulted Active lease the
116-
// vault is empty (the lessee never returned) and this is a no-op.
116+
// vault is empty (the short_seller never returned) and this is a no-op.
117117
let leased_vault_balance = context.accounts.leased_vault.amount;
118118
if leased_vault_balance > 0 {
119119
transfer_tokens_from_vault(
120120
&context.accounts.leased_vault,
121-
&context.accounts.lessor_leased_account,
121+
&context.accounts.holder_leased_account,
122122
leased_vault_balance,
123123
&context.accounts.leased_mint,
124124
&context.accounts.leased_vault.to_account_info(),
@@ -127,13 +127,13 @@ pub fn handle_close_expired(context: Context<CloseExpired>) -> Result<()> {
127127
)?;
128128
}
129129

130-
// Drain the collateral vault to the lessor. For a Listed lease this is 0.
131-
// For a defaulted Active lease this is the lessee's forfeited collateral.
130+
// Drain the collateral vault to the holder. For a Listed lease this is 0.
131+
// For a defaulted Active lease this is the short_seller's forfeited collateral.
132132
let collateral_vault_balance = context.accounts.collateral_vault.amount;
133133
if collateral_vault_balance > 0 {
134134
transfer_tokens_from_vault(
135135
&context.accounts.collateral_vault,
136-
&context.accounts.lessor_collateral_account,
136+
&context.accounts.holder_collateral_account,
137137
collateral_vault_balance,
138138
&context.accounts.collateral_mint,
139139
&context.accounts.collateral_vault.to_account_info(),
@@ -144,26 +144,26 @@ pub fn handle_close_expired(context: Context<CloseExpired>) -> Result<()> {
144144

145145
close_vault(
146146
&context.accounts.leased_vault,
147-
&context.accounts.lessor.to_account_info(),
147+
&context.accounts.holder.to_account_info(),
148148
&context.accounts.token_program,
149149
&[leased_vault_seeds],
150150
)?;
151151
close_vault(
152152
&context.accounts.collateral_vault,
153-
&context.accounts.lessor.to_account_info(),
153+
&context.accounts.holder.to_account_info(),
154154
&context.accounts.token_program,
155155
&[collateral_vault_seeds],
156156
)?;
157157

158158
// Settle lease-fee accounting on the default path.
159159
//
160-
// We are not forwarding any accrued lease fees to the lessor here — on default
161-
// the lessor takes the whole collateral vault as compensation — but we
160+
// We are not forwarding any accrued lease fees to the holder here — on default
161+
// the holder takes the whole collateral vault as compensation — but we
162162
// still bump \`last_paid_timestamp\` so the invariant
163163
// \`last_paid_timestamp <= now.min(end_timestamp)\` stays intact. That matters for
164164
// any future version of the program that wants to split the collateral
165165
// differently (pro-rata lease fees, partial refund on default, haircut to the
166-
// lessee for unused time): such a version can read
166+
// short_seller for unused time): such a version can read
167167
// \`last_paid_timestamp\` and trust that everything up to \`now\` is already
168168
// settled, rather than having to reason about whether this branch ever
169169
// bumped the timestamp.

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
#[instruction(lease_id: u64)]
1616
pub struct CreateLease<'info> {
1717
#[account(mut)]
18-
pub lessor: Signer<'info>,
18+
pub holder: Signer<'info>,
1919

2020
#[account(mint::token_program = token_program)]
2121
pub leased_mint: InterfaceAccount<'info, Mint>,
@@ -26,16 +26,16 @@ pub struct CreateLease<'info> {
2626
#[account(
2727
mut,
2828
associated_token::mint = leased_mint,
29-
associated_token::authority = lessor,
29+
associated_token::authority = holder,
3030
associated_token::token_program = token_program,
3131
)]
32-
pub lessor_leased_account: Box<InterfaceAccount<'info, TokenAccount>>,
32+
pub holder_leased_account: Box<InterfaceAccount<'info, TokenAccount>>,
3333

3434
#[account(
3535
init,
36-
payer = lessor,
36+
payer = holder,
3737
space = Lease::DISCRIMINATOR.len() + Lease::INIT_SPACE,
38-
seeds = [LEASE_SEED, lessor.key().as_ref(), &lease_id.to_le_bytes()],
38+
seeds = [LEASE_SEED, holder.key().as_ref(), &lease_id.to_le_bytes()],
3939
bump,
4040
)]
4141
pub lease: Account<'info, Lease>,
@@ -45,7 +45,7 @@ pub struct CreateLease<'info> {
4545
/// returns / liquidation; any handler just signs with the vault seeds.
4646
#[account(
4747
init,
48-
payer = lessor,
48+
payer = holder,
4949
seeds = [LEASED_VAULT_SEED, lease.key().as_ref()],
5050
bump,
5151
token::mint = leased_mint,
@@ -56,7 +56,7 @@ pub struct CreateLease<'info> {
5656

5757
#[account(
5858
init,
59-
payer = lessor,
59+
payer = holder,
6060
seeds = [COLLATERAL_VAULT_SEED, lease.key().as_ref()],
6161
bump,
6262
token::mint = collateral_mint,
@@ -84,7 +84,7 @@ pub fn handle_create_lease(
8484
// Reject leased_mint == collateral_mint. Allowing both to be the same
8585
// mint would collapse the two vaults' seed derivations into one shared
8686
// token-balance pool, making lease-fee-vs-collateral accounting ambiguous and
87-
// enabling griefing paths where the lessee's "collateral" is the same
87+
// enabling griefing paths where the short_seller's "collateral" is the same
8888
// asset they already hold as the lease principal.
8989
require!(
9090
context.accounts.leased_mint.key() != context.accounts.collateral_mint.key(),
@@ -108,23 +108,23 @@ pub fn handle_create_lease(
108108
);
109109

110110
// Lock the leased tokens into the program-owned vault up-front. Doing this
111-
// here (not on take_lease) guarantees a lessee can never accept a lease
112-
// the lessor no longer has the funds to deliver.
111+
// here (not on take_lease) guarantees a short_seller can never accept a lease
112+
// the holder no longer has the funds to deliver.
113113
transfer_tokens_from_user(
114-
&context.accounts.lessor_leased_account,
114+
&context.accounts.holder_leased_account,
115115
&context.accounts.leased_vault,
116116
leased_amount,
117117
&context.accounts.leased_mint,
118-
&context.accounts.lessor,
118+
&context.accounts.holder,
119119
&context.accounts.token_program,
120120
)?;
121121

122122
let lease = &mut context.accounts.lease;
123123
lease.set_inner(Lease {
124124
lease_id,
125-
lessor: context.accounts.lessor.key(),
126-
// No lessee yet — will be populated by take_lease.
127-
lessee: Pubkey::default(),
125+
holder: context.accounts.holder.key(),
126+
// No short_seller yet — will be populated by take_lease.
127+
short_seller: Pubkey::default(),
128128
leased_mint: context.accounts.leased_mint.key(),
129129
leased_amount,
130130
collateral_mint: context.accounts.collateral_mint.key(),

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

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ pub struct Liquidate<'info> {
3636

3737
/// CHECK: program-derived address seed + lease-fee / collateral destination.
3838
#[account(mut)]
39-
pub lessor: UncheckedAccount<'info>,
39+
pub holder: UncheckedAccount<'info>,
4040

4141
#[account(
4242
mut,
43-
seeds = [LEASE_SEED, lessor.key().as_ref(), &lease.lease_id.to_le_bytes()],
43+
seeds = [LEASE_SEED, holder.key().as_ref(), &lease.lease_id.to_le_bytes()],
4444
bump = lease.bump,
45-
has_one = lessor,
45+
has_one = holder,
4646
has_one = leased_mint,
4747
has_one = collateral_mint,
4848
constraint = lease.status == LeaseStatus::Active @ AssetLeasingError::InvalidLeaseStatus,
49-
close = lessor,
49+
close = holder,
5050
)]
5151
pub lease: Account<'info, Lease>,
5252

@@ -77,10 +77,10 @@ pub struct Liquidate<'info> {
7777
init_if_needed,
7878
payer = keeper,
7979
associated_token::mint = collateral_mint,
80-
associated_token::authority = lessor,
80+
associated_token::authority = holder,
8181
associated_token::token_program = token_program,
8282
)]
83-
pub lessor_collateral_account: Box<InterfaceAccount<'info, TokenAccount>>,
83+
pub holder_collateral_account: Box<InterfaceAccount<'info, TokenAccount>>,
8484

8585
#[account(
8686
init_if_needed,
@@ -160,7 +160,7 @@ pub fn handle_liquidate(context: Context<Liquidate>) -> Result<()> {
160160
drop(price_data);
161161

162162
// Feed pinning: reject any `PriceUpdateV2` whose feed_id does not match
163-
// the one the lessor committed to at `create_lease`. Without this guard,
163+
// the one the holder committed to at `create_lease`. Without this guard,
164164
// a keeper could pass in any feed the Pyth Receiver program owns — e.g.
165165
// a wildly volatile pair that dips enough to flag the position as
166166
// underwater — and trigger a spurious liquidation.
@@ -174,8 +174,8 @@ pub fn handle_liquidate(context: Context<Liquidate>) -> Result<()> {
174174
AssetLeasingError::PositionHealthy
175175
);
176176

177-
// Settle accrued lease fees first (up to end_timestamp) so the lessor is paid for the
178-
// time the lessee actually used. Only then slice off bounty + remainder.
177+
// Settle accrued lease fees first (up to end_timestamp) so the holder is paid for the
178+
// time the short_seller actually used. Only then slice off bounty + remainder.
179179
let lease_fee_due = compute_lease_fee_due(&context.accounts.lease, now)?;
180180
let lease_fee_payable = lease_fee_due.min(context.accounts.lease.collateral_amount);
181181

@@ -196,7 +196,7 @@ pub fn handle_liquidate(context: Context<Liquidate>) -> Result<()> {
196196
if lease_fee_payable > 0 {
197197
transfer_tokens_from_vault(
198198
&context.accounts.collateral_vault,
199-
&context.accounts.lessor_collateral_account,
199+
&context.accounts.holder_collateral_account,
200200
lease_fee_payable,
201201
&context.accounts.collateral_mint,
202202
&context.accounts.collateral_vault.to_account_info(),
@@ -232,33 +232,33 @@ pub fn handle_liquidate(context: Context<Liquidate>) -> Result<()> {
232232
)?;
233233
}
234234

235-
let lessor_share = remaining
235+
let holder_share = remaining
236236
.checked_sub(bounty)
237237
.ok_or(AssetLeasingError::MathOverflow)?;
238-
if lessor_share > 0 {
238+
if holder_share > 0 {
239239
transfer_tokens_from_vault(
240240
&context.accounts.collateral_vault,
241-
&context.accounts.lessor_collateral_account,
242-
lessor_share,
241+
&context.accounts.holder_collateral_account,
242+
holder_share,
243243
&context.accounts.collateral_mint,
244244
&context.accounts.collateral_vault.to_account_info(),
245245
&context.accounts.token_program,
246246
&[collateral_vault_seeds],
247247
)?;
248248
}
249249

250-
// The leased vault is empty (lessee kept the tokens on default) but was
251-
// rent-exempt funded at creation. Close both vaults so the lessor recoups
250+
// The leased vault is empty (short_seller kept the tokens on default) but was
251+
// rent-exempt funded at creation. Close both vaults so the holder recoups
252252
// the rent-exempt lamports.
253253
close_vault(
254254
&context.accounts.leased_vault,
255-
&context.accounts.lessor.to_account_info(),
255+
&context.accounts.holder.to_account_info(),
256256
&context.accounts.token_program,
257257
&[leased_vault_seeds],
258258
)?;
259259
close_vault(
260260
&context.accounts.collateral_vault,
261-
&context.accounts.lessor.to_account_info(),
261+
&context.accounts.holder.to_account_info(),
262262
&context.accounts.token_program,
263263
&[collateral_vault_seeds],
264264
)?;
@@ -286,7 +286,7 @@ pub fn is_underwater(lease: &Lease, price: &DecodedPriceUpdate, now: i64) -> Res
286286
let leased_amount = lease.leased_amount as u128;
287287
let collateral_amount = lease.collateral_amount as u128;
288288
let margin_basis_points = lease.maintenance_margin_basis_points as u128;
289-
let denom = BASIS_POINTS_DENOMINATOR as u128;
289+
let denominator = BASIS_POINTS_DENOMINATOR as u128;
290290

291291
let (collateral_scaled, debt_scaled) = if price.exponent >= 0 {
292292
let scale = ten_pow(price.exponent as u32)?;
@@ -307,7 +307,7 @@ pub fn is_underwater(lease: &Lease, price: &DecodedPriceUpdate, now: i64) -> Res
307307
};
308308

309309
let lhs = collateral_scaled
310-
.checked_mul(denom)
310+
.checked_mul(denominator)
311311
.ok_or(AssetLeasingError::MathOverflow)?;
312312
let rhs = debt_scaled
313313
.checked_mul(margin_basis_points)

0 commit comments

Comments
 (0)