Summary
Multiple critical business logic flaws, input validation omissions, and state-corruption bugs were identified in the stellarlend-credit-delegation smart contract (contracts/credit-delegation/src/lib.rs):
-
Arbitrary Debt Erasure via Negative draw():
draw() takes amount: i128 without verifying amount > 0.
- If a borrower calls
draw() with a negative amount (e.g. amount = -1_000_000), new_drawn = credit_line.drawn_amount + amount computes to credit_line.drawn_amount - 1_000_000.
- A delinquent borrower can arbitrarily reduce or completely erase their recorded debt without repaying any funds.
-
Immediate Line Termination on Zero Repayment (repay(id, 0)):
repay() takes amount: i128 without verifying amount > 0 or checking whether any debt has actually been drawn (credit_line.drawn_amount == 0).
- If
repay(id, delegate, 0) is called on a freshly created active credit line (drawn_amount == 0):
new_repaid = 0 + 0 = 0.
new_repaid >= credit_line.drawn_amount evaluates to 0 >= 0 (True!).
credit_line.status = CreditStatus::Repaid.
- The credit line is permanently killed and can never be drawn, causing an immediate denial of service.
-
Missing Lifecycle and Entity Validations in create_credit_line():
- Allows self-delegation:
delegator == delegate.
- Allows creating expired credit lines:
maturity <= ledger.sequence().
- Allows negative collateral:
collateral < 0.
-
Missing Authority Checks on Inactive Lines:
adjust_limit() allowed adjusting new_max on defaulted/repaid lines or setting new_max <= 0.
transfer() allowed delegators to transfer debt ownership to the borrower (new_delegator == delegate) or transfer defaulted/repaid lines.
-
Unrecorded Audit History:
- Defined
DrawRecord and RepaymentRecord data types were never saved to storage, eliminating on-chain audit trails for debt movements.
Root Cause & Impact
- Severity: Critical / High (under
SECURITY.md rules: arbitrary debt balance erasure, credit line denial of service, invalid state transitions).
- Vulnerable Code:
contracts/credit-delegation/src/lib.rs:
draw(): missing amount > 0 guard.
repay(): missing amount > 0 and drawn_amount > 0 guard.
create_credit_line(): missing delegator != delegate, maturity > current_ledger, and collateral >= 0.
Proposed Remediation
-
Enforce Strict Positive Amounts:
- Require
amount > 0 in both draw() and repay().
- Reject
repay() when drawn_amount == 0.
-
Enforce Creation & Transfer Constraints:
- Reject
delegator == delegate, maturity <= ledger.sequence(), and negative collateral in create_credit_line().
- Reject transfers to
delegate or transfers of inactive lines.
-
Record Audit History:
- Save
DrawRecord and RepaymentRecord in storage on each operation and expose view functions get_draw_record() and get_repayment_record().
Bounty Payout Addresses
In accordance with SECURITY.md:
- USDC / USDT (EVM / Arbitrum / Ethereum / Polygon):
0xf5fcb1f90f8a2e658f38f72f0156ecbec7aa964d
- SOL / USDC (Solana):
ECYrMmKpVNyvWMuLNkBuWxqkk2TNRC3qVPDgonBwamKP
- BTC (Bitcoin):
bc1qjg5lug59rn9rz2j2f9ut798g99mn2asw9jfv0s
- Security Contact:
teddy.vj@gmail.com
Summary
Multiple critical business logic flaws, input validation omissions, and state-corruption bugs were identified in the
stellarlend-credit-delegationsmart contract (contracts/credit-delegation/src/lib.rs):Arbitrary Debt Erasure via Negative
draw():draw()takesamount: i128without verifyingamount > 0.draw()with a negative amount (e.g.amount = -1_000_000),new_drawn = credit_line.drawn_amount + amountcomputes tocredit_line.drawn_amount - 1_000_000.Immediate Line Termination on Zero Repayment (
repay(id, 0)):repay()takesamount: i128without verifyingamount > 0or checking whether any debt has actually been drawn (credit_line.drawn_amount == 0).repay(id, delegate, 0)is called on a freshly created active credit line (drawn_amount == 0):new_repaid = 0 + 0 = 0.new_repaid >= credit_line.drawn_amountevaluates to0 >= 0(True!).credit_line.status = CreditStatus::Repaid.Missing Lifecycle and Entity Validations in
create_credit_line():delegator == delegate.maturity <= ledger.sequence().collateral < 0.Missing Authority Checks on Inactive Lines:
adjust_limit()allowed adjustingnew_maxon defaulted/repaid lines or settingnew_max <= 0.transfer()allowed delegators to transfer debt ownership to the borrower (new_delegator == delegate) or transfer defaulted/repaid lines.Unrecorded Audit History:
DrawRecordandRepaymentRecorddata types were never saved to storage, eliminating on-chain audit trails for debt movements.Root Cause & Impact
SECURITY.mdrules: arbitrary debt balance erasure, credit line denial of service, invalid state transitions).contracts/credit-delegation/src/lib.rs:draw(): missingamount > 0guard.repay(): missingamount > 0anddrawn_amount > 0guard.create_credit_line(): missingdelegator != delegate,maturity > current_ledger, andcollateral >= 0.Proposed Remediation
Enforce Strict Positive Amounts:
amount > 0in bothdraw()andrepay().repay()whendrawn_amount == 0.Enforce Creation & Transfer Constraints:
delegator == delegate,maturity <= ledger.sequence(), and negative collateral increate_credit_line().delegateor transfers of inactive lines.Record Audit History:
DrawRecordandRepaymentRecordin storage on each operation and expose view functionsget_draw_record()andget_repayment_record().Bounty Payout Addresses
In accordance with
SECURITY.md:0xf5fcb1f90f8a2e658f38f72f0156ecbec7aa964dECYrMmKpVNyvWMuLNkBuWxqkk2TNRC3qVPDgonBwamKPbc1qjg5lug59rn9rz2j2f9ut798g99mn2asw9jfv0steddy.vj@gmail.com