StockTokenLendingPool
0x377543d376eca00a4f958186b9eb4e798f5635d3
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
9,672 bytes
ABI entries
84
35 read · 16 write
License
none
Contract information
- Address
- 0x377543d376eca00a4f958186b9eb4e798f5635d3
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x898F183eAa…488Cd0a7eb
- Creation tx
- 0x81030bdaba…8a8218db7f
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (35)
BPS() → uint256
MIN_HEALTH_FACTOR() → uint256
SEQUENCER_GRACE_PERIOD() → uint256
WAD() → uint256
YEAR() → uint256
baseBorrowRatePerSecond() → uint256
borrowIndex() → uint256
borrowLimit(address, address) → uint256
closeFactorBps() → uint16
collateralBalance(address, address) → uint256
collateralValueUsd(address, address) → uint256
currentBorrowAprBps() → uint256
debtAsset() → address
debtBalance(address) → uint256
globalSupplyCap() → uint256
healthFactor(address, address) → uint256
lastAccrualTimestamp() → uint256
liquidationLimit(address, address) → uint256
liquidityAvailable() → uint256
markets(address) → bool, uint16, uint16, uint16, uint64, address, uint256, uint256, uint256
originationFeeBps() → uint16
owner() → address
paused() → bool
protocolLiquidationFeeBps() → uint16
protocolReserves() → uint256
reserveFactorBps() → uint16
sequencerUptimeFeed() → address
slopeBorrowRatePerSecond() → uint256
suppliedLiquidity(address) → uint256
totalDebt() → uint256
totalLiquidityShares() → uint256
totalSuppliedLiquidity() → uint256
treasury() → address
userBorrowIndex(address) → uint256
withdrawableLiquidity(address) → uint256
Events (14)
BorrowedCollateralDepositedCollateralWithdrawnInterestAccruedLiquidatedLiquidityWithdrawnMarketListedOwnershipTransferredPausedRepaidRiskParamsUpdatedSuppliedTreasuryUpdatedUnpaused
ABI
[
{
"inputs": [
{
"internalType": "contract IERC20",
"name": "_debtAsset",
"type": "address"
},
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
},
{
"internalType": "address",
"name": "_treasury",
"type": "address"
},
{
"internalType": "address",
"name": "_sequencerUptimeFeed",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AmountZero",
"type": "error"
},
{
"inputs": [],
"name": "BorrowCapExceeded",
"type": "error"
},
{
"inputs": [],
"name": "CloseFactorExceeded",
"type": "error"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"type": "error"
},
{
"inputs": [],
"name": "HealthyPosition",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientLiquidity",
"type": "error"
},
{
"inputs": [],
"name": "InvalidOracle",
"type": "error"
},
{
"inputs": [],
"name": "MarketNotListed",
"type": "error"
},
{
"inputs": [],
"name": "OraclePaused",
"type": "error"
},
{
"inputs": [],
"name": "OracleStale",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "RepayTooLarge",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "SupplyCapExceeded",
"type": "error"
},
{
"inputs": [],
"name": "UnsafePosition",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "Borrowed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "CollateralDeposited",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "CollateralWithdrawn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "interest",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "supplierInterest",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "reserves",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newBorrowIndex",
"type": "uint256"
}
],
"name": "InterestAccrued",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "liquidator",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "repaid",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "seized",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "protocolFee",
"type": "uint256"
}
],
"name": "Liquidated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "lender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "shares",
"type": "uint256"
}
],
"name": "LiquidityWithdrawn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "priceFeed",
"type": "address"
},
{
"indexed": false,
"internalType": "uint16",
"name": "collateralFactorBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "liquidationThresholdBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "liquidationBonusBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint64",
"name": "maxStaleness",
"type": "uint64"
},
{
"indexed": false,
"internalType": "uint256",
"name": "borrowCap",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "supplyCap",
"type": "uint256"
}
],
"name": "MarketListed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Repaid",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "reserveFactorBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "originationFeeBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "closeFactorBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "protocolLiquidationFeeBps",
"type": "uint16"
}
],
"name": "RiskParamsUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "lender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "shares",
"type": "uint256"
}
],
"name": "Supplied",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "treasury",
"type": "address"
}
],
"name": "TreasuryUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_HEALTH_FACTOR",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SEQUENCER_GRACE_PERIOD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WAD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "YEAR",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "accrueInterest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "baseBorrowRatePerSecond",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "borrow",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "borrowIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "stockToken",
"type": "address"
}
],
"name": "borrowLimit",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "closeFactorBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "collateralBalance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "stockToken",
"type": "address"
}
],
"name": "collateralValueUsd",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "currentBorrowAprBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "debtAsset",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "debtBalance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "depositCollateral",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "globalSupplyCap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "stockToken",
"type": "address"
}
],
"name": "healthFactor",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastAccrualTimestamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "repayAmount",
"type": "uint256"
}
],
"name": "liquidate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "stockToken",
"type": "address"
}
],
"name": "liquidationLimit",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "liquidityAvailable",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"internalType": "contract AggregatorV3Interface",
"name": "priceFeed",
"type": "address"
},
{
"internalType": "uint16",
"name": "collateralFactorBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "liquidationThresholdBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "liquidationBonusBps",
"type": "uint16"
},
{
"internalType": "uint64",
"name": "maxStaleness",
"type": "uint64"
},
{
"internalType": "uint256",
"name": "borrowCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "supplyCap",
"type": "uint256"
}
],
"name": "listMarket",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "markets",
"outputs": [
{
"internalType": "bool",
"name": "listed",
"type": "bool"
},
{
"internalType": "uint16",
"name": "collateralFactorBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "liquidationThresholdBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "liquidationBonusBps",
"type": "uint16"
},
{
"internalType": "uint64",
"name": "maxStaleness",
"type": "uint64"
},
{
"internalType": "contract AggregatorV3Interface",
"name": "priceFeed",
"type": "address"
},
{
"internalType": "uint256",
"name": "borrowCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "supplyCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalBorrowed",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "originationFeeBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolLiquidationFeeBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolReserves",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "repay",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "reserveFactorBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "sequencerUptimeFeed",
"outputs": [
{
"internalType": "contract AggregatorV3Interface",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "_reserveFactorBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "_originationFeeBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "_closeFactorBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "_protocolLiquidationFeeBps",
"type": "uint16"
}
],
"name": "setRiskParams",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_treasury",
"type": "address"
}
],
"name": "setTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "slopeBorrowRatePerSecond",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "suppliedLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "supplyLiquidity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "totalDebt",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalLiquidityShares",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSuppliedLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "treasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "userBorrowIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "stockToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdrawCollateral",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdrawLiquidity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdrawProtocolReserves",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "lender",
"type": "address"
}
],
"name": "withdrawableLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80);
}
interface IOraclePauseFlag {
function oraclePaused() external view returns (bool);
}
/// @notice Retail-facing collateralized lending pool for Robinhood Chain stock tokens.
/// @dev Production-oriented accounting: utilization interest, LP receipt shares, reserves, caps, close factor, and L2 sequencer checks.
contract StockTokenLendingPool is Ownable, Pausable, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 public constant BPS = 10_000;
uint256 public constant WAD = 1e18;
uint256 public constant YEAR = 365 days;
uint256 public constant MIN_HEALTH_FACTOR = 1e18;
uint256 public constant SEQUENCER_GRACE_PERIOD = 1 hours;
IERC20 public immutable debtAsset; // USDG on Robinhood Chain in production.
AggregatorV3Interface public sequencerUptimeFeed;
address public treasury;
uint16 public reserveFactorBps = 2_000; // 20% of interest to protocol reserves.
uint16 public originationFeeBps = 25; // 0.25% one-time borrow fee.
uint16 public closeFactorBps = 5_000; // max 50% debt repay in one liquidation.
uint16 public protocolLiquidationFeeBps = 200; // 2% of repaid value paid to treasury.
uint256 public baseBorrowRatePerSecond = uint256(2e16) / YEAR; // 2% APR.
uint256 public slopeBorrowRatePerSecond = uint256(30e16) / YEAR; // +30% APR at 100% utilization.
uint256 public borrowIndex = WAD;
uint256 public lastAccrualTimestamp;
uint256 public totalDebt;
uint256 public totalSuppliedLiquidity;
uint256 public protocolReserves;
uint256 public totalLiquidityShares;
uint256 public globalSupplyCap;
struct Market {
bool listed;
uint16 collateralFactorBps;
uint16 liquidationThresholdBps;
uint16 liquidationBonusBps;
uint64 maxStaleness;
AggregatorV3Interface priceFeed;
uint256 borrowCap;
uint256 supplyCap;
uint256 totalBorrowed;
}
mapping(address => Market) public markets;
mapping(address => mapping(address => uint256)) public collateralBalance; // user => stock token => raw token amount
mapping(address => uint256) private debtPrincipal; // principal scaled at userBorrowIndex
mapping(address => uint256) public userBorrowIndex;
mapping(address => uint256) public suppliedLiquidity; // LP receipt shares; 1:1 before yield accrues.
event MarketListed(address indexed token, address indexed priceFeed, uint16 collateralFactorBps, uint16 liquidationThresholdBps, uint16 liquidationBonusBps, uint64 maxStaleness, uint256 borrowCap, uint256 supplyCap);
event Supplied(address indexed lender, uint256 amount, uint256 shares);
event LiquidityWithdrawn(address indexed lender, uint256 amount, uint256 shares);
event CollateralDeposited(address indexed user, address indexed token, uint256 amount);
event CollateralWithdrawn(address indexed user, address indexed token, uint256 amount);
event Borrowed(address indexed user, uint256 amount, uint256 fee);
event Repaid(address indexed user, uint256 amount);
event Liquidated(address indexed user, address indexed liquidator, address indexed collateralToken, uint256 repaid, uint256 seized, uint256 protocolFee);
event InterestAccrued(uint256 interest, uint256 supplierInterest, uint256 reserves, uint256 newBorrowIndex);
event RiskParamsUpdated(uint16 reserveFactorBps, uint16 originationFeeBps, uint16 closeFactorBps, uint16 protocolLiquidationFeeBps);
event TreasuryUpdated(address indexed treasury);
error AmountZero();
error MarketNotListed();
error UnsafePosition();
error InsufficientLiquidity();
error InvalidOracle();
error OracleStale();
error OraclePaused();
error HealthyPosition();
error RepayTooLarge();
error BorrowCapExceeded();
error SupplyCapExceeded();
error CloseFactorExceeded();
constructor(IERC20 _debtAsset, address initialOwner, address _treasury, address _sequencerUptimeFeed) Ownable(initialOwner) {
require(address(_debtAsset) != address(0) && initialOwner != address(0) && _treasury != address(0), "zero address");
debtAsset = _debtAsset;
treasury = _treasury;
sequencerUptimeFeed = AggregatorV3Interface(_sequencerUptimeFeed);
lastAccrualTimestamp = block.timestamp;
}
function listMarket(
address stockToken,
AggregatorV3Interface priceFeed,
uint16 collateralFactorBps,
uint16 liquidationThresholdBps,
uint16 liquidationBonusBps,
uint64 maxStaleness,
uint256 borrowCap,
uint256 supplyCap
) external onlyOwner {
require(stockToken != address(0) && address(priceFeed) != address(0), "zero address");
require(collateralFactorBps < liquidationThresholdBps && liquidationThresholdBps <= BPS, "bad risk params");
require(liquidationBonusBps <= 2_000, "bonus too high");
require(maxStaleness > 0, "staleness required");
markets[stockToken] = Market(true, collateralFactorBps, liquidationThresholdBps, liquidationBonusBps, maxStaleness, priceFeed, borrowCap, supplyCap, markets[stockToken].totalBorrowed);
if (supplyCap > 0 && (globalSupplyCap == 0 || supplyCap < globalSupplyCap)) globalSupplyCap = supplyCap;
emit MarketListed(stockToken, address(priceFeed), collateralFactorBps, liquidationThresholdBps, liquidationBonusBps, maxStaleness, borrowCap, supplyCap);
}
function setRiskParams(uint16 _reserveFactorBps, uint16 _originationFeeBps, uint16 _closeFactorBps, uint16 _protocolLiquidationFeeBps) external onlyOwner {
require(_reserveFactorBps <= 5_000 && _originationFeeBps <= 1_000 && _closeFactorBps > 0 && _closeFactorBps <= BPS && _protocolLiquidationFeeBps <= 2_000, "bad risk params");
reserveFactorBps = _reserveFactorBps;
originationFeeBps = _originationFeeBps;
closeFactorBps = _closeFactorBps;
protocolLiquidationFeeBps = _protocolLiquidationFeeBps;
emit RiskParamsUpdated(_reserveFactorBps, _originationFeeBps, _closeFactorBps, _protocolLiquidationFeeBps);
}
function setTreasury(address _treasury) external onlyOwner {
require(_treasury != address(0), "zero address");
treasury = _treasury;
emit TreasuryUpdated(_treasury);
}
function pause() external onlyOwner { _pause(); }
function unpause() external onlyOwner { _unpause(); }
function accrueInterest() public {
uint256 dt = block.timestamp - lastAccrualTimestamp;
if (dt == 0) return;
lastAccrualTimestamp = block.timestamp;
if (totalDebt == 0) return;
uint256 cash = _cashPriorReserves();
uint256 util = totalDebt * WAD / (cash + totalDebt);
uint256 rate = baseBorrowRatePerSecond + (slopeBorrowRatePerSecond * util / WAD);
uint256 interest = totalDebt * rate * dt / WAD;
if (interest == 0) return;
uint256 reserves = interest * reserveFactorBps / BPS;
uint256 supplierInterest = interest - reserves;
totalDebt += interest;
totalSuppliedLiquidity += supplierInterest;
protocolReserves += reserves;
borrowIndex += borrowIndex * interest / (totalDebt - interest);
emit InterestAccrued(interest, supplierInterest, reserves, borrowIndex);
}
function supplyLiquidity(uint256 amount) external nonReentrant whenNotPaused {
if (amount == 0) revert AmountZero();
accrueInterest();
if (globalSupplyCap > 0 && totalSuppliedLiquidity + amount > globalSupplyCap) revert SupplyCapExceeded();
uint256 shares = totalLiquidityShares == 0 || totalSuppliedLiquidity == 0 ? amount : amount * totalLiquidityShares / totalSuppliedLiquidity;
if (shares == 0) revert AmountZero();
suppliedLiquidity[msg.sender] += shares;
totalLiquidityShares += shares;
totalSuppliedLiquidity += amount;
debtAsset.safeTransferFrom(msg.sender, address(this), amount);
emit Supplied(msg.sender, amount, shares);
}
function withdrawableLiquidity(address lender) public view returns (uint256) {
if (totalLiquidityShares == 0) return 0;
return suppliedLiquidity[lender] * totalSuppliedLiquidity / totalLiquidityShares;
}
function withdrawLiquidity(uint256 amount) external nonReentrant {
if (amount == 0) revert AmountZero();
accrueInterest();
uint256 owed = withdrawableLiquidity(msg.sender);
if (amount > owed) revert InsufficientLiquidity();
if (liquidityAvailable() < amount) revert InsufficientLiquidity();
uint256 shares = amount * totalLiquidityShares / totalSuppliedLiquidity;
if (shares == 0) shares = 1;
suppliedLiquidity[msg.sender] -= shares;
totalLiquidityShares -= shares;
totalSuppliedLiquidity -= amount;
debtAsset.safeTransfer(msg.sender, amount);
emit LiquidityWithdrawn(msg.sender, amount, shares);
}
function withdrawProtocolReserves(uint256 amount) external onlyOwner {
if (amount == 0) revert AmountZero();
protocolReserves -= amount;
if (debtAsset.balanceOf(address(this)) < amount) revert InsufficientLiquidity();
debtAsset.safeTransfer(treasury, amount);
}
function depositCollateral(address stockToken, uint256 amount) external nonReentrant whenNotPaused {
if (amount == 0) revert AmountZero();
if (!markets[stockToken].listed) revert MarketNotListed();
_priceUsdWad(stockToken, markets[stockToken]);
collateralBalance[msg.sender][stockToken] += amount;
IERC20(stockToken).safeTransferFrom(msg.sender, address(this), amount);
emit CollateralDeposited(msg.sender, stockToken, amount);
}
function withdrawCollateral(address stockToken, uint256 amount) external nonReentrant {
if (amount == 0) revert AmountZero();
accrueInterest();
if (!markets[stockToken].listed) revert MarketNotListed();
collateralBalance[msg.sender][stockToken] -= amount;
if (!_isHealthy(msg.sender, stockToken)) revert UnsafePosition();
IERC20(stockToken).safeTransfer(msg.sender, amount);
emit CollateralWithdrawn(msg.sender, stockToken, amount);
}
function borrow(address stockToken, uint256 amount) external nonReentrant whenNotPaused {
if (amount == 0) revert AmountZero();
accrueInterest();
Market storage market = markets[stockToken];
if (!market.listed) revert MarketNotListed();
if (market.borrowCap > 0 && market.totalBorrowed + amount > market.borrowCap) revert BorrowCapExceeded();
uint256 fee = amount * originationFeeBps / BPS;
if (liquidityAvailable() < amount) revert InsufficientLiquidity();
_syncBorrower(msg.sender);
debtPrincipal[msg.sender] += amount;
totalDebt += amount;
market.totalBorrowed += amount;
if (!_isHealthy(msg.sender, stockToken)) revert UnsafePosition();
if (fee > 0) debtAsset.safeTransfer(treasury, fee);
debtAsset.safeTransfer(msg.sender, amount - fee);
emit Borrowed(msg.sender, amount, fee);
}
function repay(uint256 amount) external nonReentrant whenNotPaused {
if (amount == 0) revert AmountZero();
accrueInterest();
_syncBorrower(msg.sender);
uint256 debt = debtPrincipal[msg.sender];
if (amount > debt) amount = debt;
debtPrincipal[msg.sender] = debt - amount;
totalDebt -= amount;
debtAsset.safeTransferFrom(msg.sender, address(this), amount);
emit Repaid(msg.sender, amount);
}
function liquidate(address user, address stockToken, uint256 repayAmount) external nonReentrant whenNotPaused {
if (repayAmount == 0) revert AmountZero();
accrueInterest();
_syncBorrower(user);
Market storage market = markets[stockToken];
if (!market.listed) revert MarketNotListed();
if (healthFactor(user, stockToken) >= MIN_HEALTH_FACTOR) revert HealthyPosition();
uint256 userDebt = debtPrincipal[user];
uint256 maxRepay = userDebt * closeFactorBps / BPS;
if (repayAmount > maxRepay) revert CloseFactorExceeded();
if (repayAmount > userDebt) repayAmount = userDebt;
uint256 price = _priceUsdWad(stockToken, market);
uint256 liquidatorSeizeValue = repayAmount * (BPS + market.liquidationBonusBps) / BPS;
uint256 protocolFee = repayAmount * protocolLiquidationFeeBps / BPS;
uint256 seizeAmount = (liquidatorSeizeValue + protocolFee) * WAD / price;
uint256 available = collateralBalance[user][stockToken];
if (seizeAmount > available) seizeAmount = available;
uint256 protocolSeize = protocolFee * WAD / price;
if (protocolSeize > seizeAmount) protocolSeize = 0;
uint256 liquidatorSeize = seizeAmount - protocolSeize;
debtPrincipal[user] = userDebt - repayAmount;
totalDebt -= repayAmount;
market.totalBorrowed = market.totalBorrowed > repayAmount ? market.totalBorrowed - repayAmount : 0;
collateralBalance[user][stockToken] = available - seizeAmount;
debtAsset.safeTransferFrom(msg.sender, address(this), repayAmount);
if (protocolFee > 0) debtAsset.safeTransferFrom(msg.sender, treasury, protocolFee);
IERC20(stockToken).safeTransfer(msg.sender, liquidatorSeize);
if (protocolSeize > 0) IERC20(stockToken).safeTransfer(treasury, protocolSeize);
emit Liquidated(user, msg.sender, stockToken, repayAmount, seizeAmount, protocolFee);
}
function debtBalance(address user) public view returns (uint256) {
uint256 index = userBorrowIndex[user];
if (index == 0) return 0;
return debtPrincipal[user] * borrowIndex / index;
}
function collateralValueUsd(address user, address stockToken) public view returns (uint256) {
Market memory market = markets[stockToken];
if (!market.listed) revert MarketNotListed();
return collateralBalance[user][stockToken] * _priceUsdWad(stockToken, market) / WAD;
}
function borrowLimit(address user, address stockToken) public view returns (uint256) {
Market memory market = markets[stockToken];
if (!market.listed) revert MarketNotListed();
return collateralValueUsd(user, stockToken) * market.collateralFactorBps / BPS;
}
function liquidationLimit(address user, address stockToken) public view returns (uint256) {
Market memory market = markets[stockToken];
if (!market.listed) revert MarketNotListed();
return collateralValueUsd(user, stockToken) * market.liquidationThresholdBps / BPS;
}
function healthFactor(address user, address stockToken) public view returns (uint256) {
uint256 debt = debtBalance(user);
if (debt == 0) return type(uint256).max;
return liquidationLimit(user, stockToken) * WAD / debt;
}
function liquidityAvailable() public view returns (uint256) {
uint256 bal = debtAsset.balanceOf(address(this));
return bal > protocolReserves ? bal - protocolReserves : 0;
}
function currentBorrowAprBps() external view returns (uint256) {
uint256 cash = _cashPriorReserves();
if (totalDebt == 0) return baseBorrowRatePerSecond * YEAR * BPS / WAD;
uint256 util = totalDebt * WAD / (cash + totalDebt);
return (baseBorrowRatePerSecond + (slopeBorrowRatePerSecond * util / WAD)) * YEAR * BPS / WAD;
}
function _syncBorrower(address user) internal {
uint256 index = userBorrowIndex[user];
if (index == 0) {
userBorrowIndex[user] = borrowIndex;
return;
}
debtPrincipal[user] = debtPrincipal[user] * borrowIndex / index;
userBorrowIndex[user] = borrowIndex;
}
function _isHealthy(address user, address stockToken) internal view returns (bool) {
return debtBalance(user) <= borrowLimit(user, stockToken);
}
function _priceUsdWad(address stockToken, Market memory market) internal view returns (uint256) {
_checkSequencer();
try IOraclePauseFlag(stockToken).oraclePaused() returns (bool paused) {
if (paused) revert OraclePaused();
} catch {}
(uint80 roundId, int256 answer,, uint256 updatedAt, uint80 answeredInRound) = market.priceFeed.latestRoundData();
if (answer <= 0 || updatedAt == 0 || answeredInRound < roundId) revert InvalidOracle();
if (block.timestamp - updatedAt > market.maxStaleness) revert OracleStale();
uint8 feedDecimals = market.priceFeed.decimals();
if (feedDecimals > 18) {
return uint256(answer) / (10 ** (feedDecimals - 18));
}
return uint256(answer) * (10 ** (18 - feedDecimals));
}
function _checkSequencer() internal view {
if (address(sequencerUptimeFeed) == address(0)) return;
(, int256 sequencerStatus, uint256 startedAt,,) = sequencerUptimeFeed.latestRoundData();
require(sequencerStatus == 0, "Sequencer down"); // 0 = up
require(block.timestamp - startedAt > SEQUENCER_GRACE_PERIOD, "Grace period not over");
}
function _cashPriorReserves() internal view returns (uint256) {
uint256 bal = debtAsset.balanceOf(address(this));
return bal > protocolReserves ? bal - protocolReserves : 0;
}
}
Chain explorer1982msChain node84ms