Appearance
L3-4: Shorting
1. Problem
Shorting is the fundamental operation for expressing bearish views in financial markets, but on-chain implementation is more complex than in traditional finance. The traditional shorting flow: borrow shares → sell → wait for price drop → buy back → return, with profit from the price spread. In DeFi, this process must be fully executed on-chain: users deposit collateral (e.g., USDC), borrow the target token (e.g., ETH), sell on a DEX, and when the price drops, buy back and repay.
The core design challenges are: (1) how to ensure borrowed tokens can be repaid (over-collateralization); (2) how to trigger liquidation when the price moves adversely; (3) how to design the liquidation mechanism to prevent bad debt. These issues also exist in perpetual contract protocols (like GMX, dYdX) and are central to shorting mechanisms.
2. Rationale
Shorting is not just a speculative tool — it is an essential component of market efficiency. Short sellers provide liquidity, accelerate price discovery, and curb bubbles. In DeFi, understanding shorting mechanisms enables you to build hedging strategies, participate in perpetual contract markets, and even design synthetic assets. Leveraged shorts on Aave, perpetual contracts on GMX, and Ethena's delta-neutral strategy are all built on shorting primitives.
From a contract design perspective, a shorting market is an excellent position management exercise: it involves collateral management, price oracle integration, liquidation threshold calculation, and real-time P&L settlement. These patterns are the foundation for all margin trading and perpetual contract protocols.
3. Solution
ShortingMarket.sol implements a complete shorting market with the following core flow:
Open Short: openShort() takes two parameters — collateral amount (_collateral) and borrow amount (_borrowAmount). It then verifies that the collateral ratio meets the minimum requirement (MIN_COLLATERAL_RATIO = 150%). The calculation:
borrowValue = borrowAmount * currentPrice / 1e18
minCollateral = borrowValue * 150 / 100
Once verified, the user deposits collateral and receives the borrowed tokens (which will be sold on an external DEX).
Close Short: closeShort() calculates profit and loss. If the price has dropped, the shorter profits:
borrowValueAtOpen = borrowAmount * openPrice / 1e18
borrowValueNow = borrowAmount * currentPrice / 1e18
profit = borrowValueAtOpen - borrowValueNow // positive when price drops
The user needs to repay the borrowed tokens, then withdraws collateral + profit (or collateral - loss).
Liquidate: liquidate() is triggered when the collateral ratio falls below LIQUIDATION_COLLATERAL_RATIO = 120%. The liquidator must provide the borrowed tokens to cover the position and receives the collateral + a 10% liquidation bonus.
Oracle: currentPrice is manually set by the contract owner (setPrice()) as a simplified design. In production, a decentralized oracle like Chainlink should be used.
Reference source file: src/level3/ShortingMarket.sol
4. Pitfalls Encountered
- Price manipulation and oracle choice:
currentPriceis a manually set single price with no TWAP or decentralized verification; an attacker could exploit the window at the moment the price is set - Insufficient collateral during liquidation: when prices move violently (e.g., a flash crash), the collateral may be insufficient to cover the liquidation bonus, resulting in bad debt
- Borrowed tokens unavailable: in
openShort(), the contract directly transfersborrowTokento the user, but the contract address must hold sufficient borrowed tokens — this design implies either pre-funding or reliance on external liquidity pools - Position ID manipulation: using
positions.lengthas the ID can lead to ID collisions in edge cases (e.g., via delegatecall) - Precision loss in P&L calculation: in
closeShort(), multiple divisions by1e18can cause small cumulative rounding errors
5. Root Causes of Pitfalls
Price oracle is the most vulnerable link in a shorting market. In the current design of ShortingMarket.sol, currentPrice is updated by the owner calling setPrice(). This introduces centralized trust — if the owner maliciously sets an incorrect price (e.g., setting the price of a crashing asset too high), they could prevent legitimate liquidations or make unhealthy positions appear artificially healthy.
A deeper issue is liquidity: when the market moves violently, on-chain DEX slippage can be extreme. Even if the contract correctly calculates the liquidation price, the liquidator may not actually be able to realize the arbitrage due to lack of liquidity on the DEX. Production-grade shorting markets typically use a combination of oracles and off-chain market makers to mitigate this problem.
The source of borrowed tokens also deserves attention: the contract directly transfer(borrowToken) to the user in openShort(), meaning the contract address must pre-hold these tokens. In practice, this requires a liquidity pool (similar to Aave's aToken pool). The "borrowing" in shorting is typically implemented in two ways: (1) borrowing from a lending pool (like Aave's flash loan pattern); (2) a synthetic asset model (like GMX's GLP pool), where users are not selling on a real market but betting against the protocol.
6. How to Resolve the Pitfalls
Oracle security: replace the manual setPrice() with a Chainlink price feed:
solidity
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
AggregatorV3Interface public priceFeed;
function getCurrentPrice() public view returns (uint256) {
(, int256 price,,,) = priceFeed.latestRoundData();
return uint256(price);
}
Preventing bad debt in liquidations: add "cover remains" logic in liquidations — if the collateral after liquidation is insufficient to fully cover the debt, the shortfall is still recorded as bad debt. In real designs, you can use an Insurance Fund to absorb bad debt, or introduce a partial liquidation mechanism that allows partial liquidation when the position approaches the liquidation line.
Liquidity assurance: for the source of borrowed tokens, the recommended approach is an asset pool model — user deposits form a pool, and short sellers borrow tokens from the pool. This creates a natural lending market where the borrowing interest rate is determined by supply and demand.
Precision consistency: unify the precision unit across all price calculations — the current implementation uses 1e18 scaling. Ensure the same scaling factor is used throughout multiplication and division, and avoid precision loss from multiple divisions like (a * b / c) * d / e. The recommended approach is to multiply first, then divide: (a * b * d) / (c * e).
7. Technical Highlights
| Technique | Description |
|---|---|
| Essence of Shorting | Borrow → Sell → Price drops → Buy back → Repay |
| Minimum Collateral Ratio | 150% (MIN_COLLATERAL_RATIO) |
| Liquidation Collateral Ratio | 120% (LIQUIDATION_COLLATERAL_RATIO) |
| Liquidation Bonus | 10% (LIQUIDATION_BONUS) |
| P&L Calculation | borrowValueAtOpen - borrowValueNow |
| Oracle | Manual setting (simplified); Chainlink in production |
| Position Tracking | userPositions mapping + positions array |
| State Management | active flag + PositionNotActive error |