DeFi Patterns
An automated market maker holds reserves x and y of two tokens and keeps x * y = k (ignoring fees).
37.1 Constant-product AMM (Uniswap v2 style)
An automated market maker holds reserves x and y of two tokens and keeps x * y = k (ignoring fees). Price = y / x. Liquidity providers deposit both tokens and receive LP shares.
Output amount for input dx with a 0.3% fee: dy = (dx * 997 * y) / (x * 1000 + dx * 997)
Evolution: Uniswap v3 (concentrated liquidity, ticks, NFT positions, TWAP oracle), Uniswap v4 (2025: singleton PoolManager, hooks, flash accounting with transient storage), Curve (StableSwap invariant for pegged assets), Balancer (weighted pools).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract MiniAMM is ERC20, ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public immutable token0;
IERC20 public immutable token1;
uint256 public reserve0;
uint256 public reserve1;
uint256 private constant MINIMUM_LIQUIDITY = 1000;
error InsufficientOutput();
error InsufficientLiquidity();
error InvalidToken();
error Expired();
event Swap(address indexed user, address tokenIn, uint256 amountIn, uint256 amountOut);
constructor(IERC20 _t0, IERC20 _t1) ERC20("MiniAMM LP", "MLP") {
token0 = _t0;
token1 = _t1;
}
function addLiquidity(uint256 amount0, uint256 amount1) external nonReentrant returns (uint256 shares) {
token0.safeTransferFrom(msg.sender, address(this), amount0);
…37.2 Staking rewards (Synthetix StakingRewards pattern)
Rewards are distributed per second proportionally to stake, using a cumulative "reward per token" accumulator — O(1) per user action, no loops.
(If the staking and reward token are the same, the balance check must subtract totalStaked.)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
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 {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract StakingRewards is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public immutable stakingToken;
IERC20 public immutable rewardToken;
uint256 public rewardRate; // reward tokens per second
uint256 public periodFinish;
uint256 public lastUpdate;
uint256 public rewardPerTokenStored; // scaled by 1e18
uint256 public totalStaked;
mapping(address => uint256) public staked;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
constructor(IERC20 _stake, IERC20 _reward) Ownable(msg.sender) {
stakingToken = _stake;
rewardToken = _reward;
}
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdate = lastTimeRewardApplicab
…37.3 Lending basics
Lending protocols (Aave, Compound, Morpho, Spark) let users supply assets to earn interest and borrow against collateral.
Collateral factor / LTV max borrow as a fraction of collateral value (e.g., 75%)
collateralValue * liquidationThreshold / debtValue; < 1 → liquidatable
borrows / (cash + borrows); drives the interest rate curve cumulative index (like reward-per-token) so interest accrues without loops third parties repay debt and seize collateral at a bonus prices from Chainlink etc. — the most critical dependency
// Core health check (simplified, 18-decimal USD values)
function _isHealthy(address user) internal view returns (bool) {
uint256 collateralUsd = collateral[user] * oracle.price(collateralToken) / 1e18;
uint256 debtUsd = debt[user] * oracle.price(debtToken) / 1e18;
return debtUsd * 10_000 <= collateralUsd * LIQUIDATION_THRESHOLD_BPS;
}37.4 Flash loans
A flash loan lends any amount with no collateral, provided it is repaid (plus fee) within the same transaction; otherwise everything reverts. Standard interface: ERC-3156.
Legitimate uses: arbitrage, liquidations, collateral swaps, refinancing. Flash loans also amplify attacks on contracts that trust spot prices or instant voting power — design assuming any user can have unlimited capital for one transaction.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
interface IFlashBorrower {
function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data)
external returns (bytes32);
}
contract FlashLender is ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
uint256 public constant FEE_BPS = 9; // 0.09%
function flashLoan(IFlashBorrower receiver, address token, uint256 amount, bytes calldata data)
external nonReentrant returns (bool)
{
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
uint256 fee = amount * FEE_BPS / 10_000;
IERC20(token).safeTransfer(address(receiver), amount);
require(
receiver.onFlashLoan(msg.sender, token, amount, fee, data) == CALLBACK_SUCCESS,
"callback failed"
);
IERC20(token).s
…37.5 Other DeFi primitives
Stablecoins (collateralized like DAI/USDS, fiat-backed like USDC, synthetic like USDe), yield aggregators (ERC-4626 vaults), perpetual DEXs, options, liquid staking (stETH, rETH) and restaking, intents and solvers (UniswapX, CoW), cross-chain bridges.