DeFi Patterns
** Factory & Pool Patterns**
Factory contracts are fundamental to scalable DeFi architectures. They solve a critical problem: how to efficiently deploy and manage multiple instances of similar contracts (like lending pools or AMM pairs) without duplicating deployment logic.
Solution: Use a factory contract to deploy clones.
Example: Lending Pool Factory
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ILendingPool {
    function initialize(address _token) external;
}
contract LendingPool {
    address public token;
    bool public initialized;
    
    // Using initialize instead of constructor for proxy compatibility
    function initialize(address _token) external {
        require(!initialized, "Already initialized");
        token = _token;
        initialized = true;
    }
}
contract PoolFactory {
    event PoolCreated(address indexed pool, address indexed token);
    
    // Track all pools by token address
    mapping(address => address) public getPool;
    address[] public allPools;
    function createPool(address token) external returns (address pool) {
        require(getPool[token] == address(0), "Pool exists");
        
        // Create new pool
        pool = address(new LendingPool());
        ILendingPool(pool).initialize(token);
        
        // Update registry
        getPool[token] = pool;
        allPools.push(pool);
        
        emit PoolCreated(pool, token);
    }
    function getAllPools() external view returns (address[] memory) {
        return allPools;
    }
}
Use Case: Uniswap’s PairFactory, Aave’s LendingPoolConfigurator.
** Upgradeable Contracts (Proxy Pattern)**
Problem: Smart contracts are immutable, but upgrades may be needed.
Solution: Separate logic (implementation) from storage (proxy).
Minimal Proxy Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Implementation Contract (Upgradeable Logic)
contract LogicV1 {
    uint public value;
    address public admin;
    
    function initialize(address _admin) external {
        require(admin == address(0), "Already initialized");
        admin = _admin;
    }
    
    function setValue(uint _value) external {
        require(msg.sender == admin, "Unauthorized");
        value = _value;
    }
}
// Proxy Contract
contract Proxy {
    address public implementation;
    address public admin;
    
    constructor(address _impl) {
        implementation = _impl;
        admin = msg.sender;
    }
    
    modifier onlyAdmin {
        require(msg.sender == admin, "Not admin");
        _;
    }
    
    function upgradeTo(address newImpl) external onlyAdmin {
        implementation = newImpl;
    }
    
    fallback() external payable {
        (bool success, ) = implementation.delegatecall(msg.data);
        require(success, "Delegatecall failed");
    }
    
    receive() external payable {}
}
Use Case: OpenZeppelin’s TransparentProxy, Aave’s Configurator.
** Pull-over-Push Payments**
Problem: Direct transfers (transfer) can fail if the recipient is a contract.
Solution: Let users withdraw funds themselves (pull payments).
Example: Secure Withdrawals
mapping(address => uint) public balances;
function withdraw() external {
    uint bal = balances[msg.sender];
    balances[msg.sender] = 0;
    payable(msg.sender).transfer(bal);
}
Use Case: Used in staking contracts (e.g., Synthetix, Lido).
** Reentrancy Protection**
Problem: Malicious contracts can re-enter functions during transfer.
Solution: Use nonReentrant modifier or checks-effects-interactions.
OpenZeppelin’s ReentrancyGuard
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureVault is ReentrancyGuard {
    mapping(address => uint256) public balances;
    function deposit(uint256 amount) external {
        balances[msg.sender] += amount;
    }
    function withdraw() external nonReentrant {
        uint256 bal = balances[msg.sender];
        balances[msg.sender] = 0;
        payable(msg.sender).transfer(bal); // safe transfer
    }
}
Use Case: All major DeFi protocols (Aave, Compound).
** Circuit Breaker (Pause Mechanism)**
Problem: Exploits require emergency shutdown.
Solution: Add a pause function for critical operations.
Example: Pausable Contract
import "@openzeppelin/contracts/security/Pausable.sol";
contract Pool is Pausable {
    function deposit() external whenNotPaused { /* ... */ }
    function emergencyPause() external onlyOwner { _pause(); }
}
Use Case: MakerDAO, Compound.
** Oracle Security (Pull vs. Push)**
. Oracles bridge off-chain data to on-chain contracts, creating a critical attack vector:
. Manipulation risks (flash loan attacks, stale data)
. Centralization risks (single data source failure)
Problem: On-chain price feeds can be manipulated.
Solution: Use pull-based oracles (e.g., Chainlink).
Example: Safe Price Feed
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract PullOracle {
    AggregatorV3Interface internal priceFeed;
    uint256 public lastUpdated;
    uint256 public priceStaleThreshold = 1 hours;
    constructor(address _aggregator) {
        priceFeed = AggregatorV3Interface(_aggregator);
    }
    function getLatestPrice() public returns (uint256) {
        // Pull fresh data from Chainlink
        (, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
        
        require(updatedAt > lastUpdated, "Stale price");
        require(price > 0, "Invalid price");
        
        lastUpdated = block.timestamp;
        return uint256(price);
    }
    function isPriceFresh() public view returns (bool) {
        return block.timestamp - lastUpdated <= priceStaleThreshold;
    }
}
Use Case: Chainlink, Uniswap TWAP.
** Timelock for Admin Actions**
Problem: Admins can change parameters instantly (risky!).
Solution: Enforce a delay before execution.
Example: Governance Timelock
mapping(bytes32 => uint) public queuedTx;
function queueTx(bytes32 txHash, uint delay) external onlyOwner {
    queuedTx[txHash] = block.timestamp + delay;
}
function executeTx(bytes32 txHash) external {
    require(block.timestamp >= queuedTx[txHash], "Too early");
    delete queuedTx[txHash];
    // Execute tx...
}
Use Case: Compound, Aave governance.
** Flashloan Attack Mitigation**
Problem: Flashloans enable single-tx exploits.
Solution: Limit actions per block.
Example: Block-Level Protection
mapping(address => uint) public lastActionBlock;
function borrow(uint amount) external {
    require(lastActionBlock[msg.sender] < block.number, "Flashloan blocked");
    lastActionBlock[msg.sender] = block.number;
    // Borrow logic...
}
Use Case: Aave v3, dYdX.
** Liquidation & Close Factor**
Problem: Liquidators can seize too much collateral.
Solution: Limit repay amount (closeFactor).
Example: Safe Liquidation
uint public closeFactor = 0.5e18; // Max 50% repay
uint public liquidationBonus = 1.08e18; // 8% bonus
function liquidate(address user, uint repay) external {
    uint maxRepay = borrows[user] * closeFactor / 1e18;
    require(repay <= maxRepay, "Exceeds closeFactor");
    // Liquidate...
}
Use Case: Compound, Aave.
** Multi-Collateral & LTV Ratios**
Problem: Users deposit multiple assets as collateral.
Solution: Track collateral per asset and enforce Loan-to-Value (LTV).
Example: Collateral Tracking
mapping(address => mapping(address => uint)) public collateral;
function depositCollateral(address token, uint amount) external {
    IERC20(token).transferFrom(msg.sender, address(this), amount);
    collateral[msg.sender][token] += amount;
}
Use Case: MakerDAO, Aave.
** Slippage Protection (AMMs)**
Problem: Swaps can fail due to price impact.
Solution: Enforce a minimum output amount.
Example: Uniswap-Style Swap
function swap(uint amountIn, uint minOut) external {
    uint amountOut = getAmountOut(amountIn);
    require(amountOut >= minOut, "Slippage too high");
    // Execute swap...
}
Use Case: Uniswap, SushiSwap.
** Interest Rate Index (Compound Model)**
Problem: Accruing interest for all users is gas-heavy.
Solution: Use a global borrow index.
Example: Interest Accrual
uint public borrowIndex = 1e18;
function accrueInterest(uint rate) public {
    borrowIndex += borrowIndex * rate / 1e18;
}
Use Case: Compound, Aave.
* Emergency Token Rescue**
Problem: Tokens accidentally sent to contracts get stuck.
Solution: Allow admin to rescue non-core tokens.
Example: Admin Recovery
function rescueToken(IERC20 token, address to) external onlyOwner {
    require(token != coreToken, "Cannot withdraw core asset");
    token.transfer(to, token.balanceOf(address(this)));
}
Use Case: Used in most DeFi protocols.
** Access Control / Role management**
Hack Prevented: Unauthorized contract changes or admin actions.
Pattern : Use Ownable or role-based control.
import "@openzeppelin/contracts/access/Ownable.sol";
contract AdminControl is Ownable {
    function pausePool() external onlyOwner {
        // emergency pause
    }
}