skills/protocol-patterns/lending-borrowing/SKILL.md
Security review framework for lending and borrowing systems including liquidations and accounting.
npx skillsauth add apegurus/solidity-argus lending-borrowingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Lending protocols (Aave, Compound, Morpho) enable collateralized borrowing. Core security concerns: liquidation logic, interest accrual, oracle reliance, and collateral management.
┌─────────────────────────────────────────────────────────────┐
│ LENDING PROTOCOL │
├─────────────────────────────────────────────────────────────┤
│ SUPPLY │ BORROW │ LIQUIDATE │
│ ───────── │ ───────── │ ───────── │
│ Deposit asset │ Lock collateral │ Seize collateral │
│ Receive shares │ Receive asset │ Repay debt │
│ Earn interest │ Pay interest │ Get bonus │
├─────────────────────────────────────────────────────────────┤
│ RISK ENGINE │
│ Health Factor = Collateral Value / Borrowed Value │
│ If HF < 1: Position is liquidatable │
└─────────────────────────────────────────────────────────────┘
Attack Vectors:
Checklist:
// VULNERABLE: No self-liquidation check
function liquidate(address borrower, uint256 amount) external {
require(getHealthFactor(borrower) < 1e18, "Healthy");
// Missing: require(msg.sender != borrower, "No self-liquidation");
}
Attack Vectors:
Checklist:
// VULNERABLE: Interest not accrued before operation
function withdraw(uint256 amount) external {
// Missing: accrueInterest();
uint256 shares = amount * totalShares / totalAssets;
_burn(msg.sender, shares);
}
// SECURE
function withdraw(uint256 amount) external {
accrueInterest(); // Always accrue first
uint256 shares = amount * totalShares / totalAssets;
_burn(msg.sender, shares);
}
Attack Vectors:
Checklist:
See: oracle.md
Attack Vectors:
Checklist:
Attack Vectors:
Checklist:
// Inflation attack protection
function deposit(uint256 assets) external returns (uint256 shares) {
require(assets >= MINIMUM_DEPOSIT, "Below minimum");
shares = totalSupply == 0
? assets // First deposit
: assets * totalSupply / totalAssets;
require(shares > 0, "Zero shares");
// ...
}
// VULNERABLE: CEI violation
function borrow(uint256 amount) external {
require(checkCollateral(msg.sender, amount), "Undercollateralized");
token.transfer(msg.sender, amount); // External call before state update
borrowBalances[msg.sender] += amount; // State update after
}
// VULNERABLE: Assumes all tokens have 18 decimals
function calculateValue(address token, uint256 amount) public view returns (uint256) {
uint256 price = oracle.getPrice(token); // Price in USD with 8 decimals
return amount * price / 1e8; // Wrong if token isn't 18 decimals!
}
// SECURE
function calculateValue(address token, uint256 amount) public view returns (uint256) {
uint256 price = oracle.getPrice(token);
uint8 decimals = IERC20Metadata(token).decimals();
return amount * price / (10 ** decimals);
}
// VULNERABLE: Dust prevents liquidation
function liquidate(address borrower, uint256 amount) external {
uint256 debt = borrowBalances[borrower];
require(amount <= debt, "Too much");
// If debt = 100 wei and minimum repay = 100 wei, can't liquidate
}
testing
Specialist profile for mechanically applying the attack-vector deck and classifying vectors as skip, drop, or investigate.
tools
Specialist profile for libraries, helpers, base contracts, adapters, encoders, wrappers, and integration glue.
testing
Specialist profile for rounding, scale, decimal, downcast, and arithmetic accounting edge cases.
testing
Specialist profile for extracting conservation laws and state couplings, then searching for violating paths.