Back to Learn

Advanced DeFi Patterns

Production DeFi protocols face challenges beyond basic smart contract development: MEV extraction, gas cost at scale, complex yield strategies, decentralised governance, and real-time monitoring. This guide covers the patterns that separate prototypes from production systems.

MEV Protection

Maximal Extractable Value (MEV) lets miners and validators reorder, insert, or censor transactions for profit. Without protection, your users face sandwich attacks on swaps, front-running on large orders, and back-running on arbitrage opportunities. In 2025, MEV protection is a baseline requirement for any user-facing DeFi protocol.

The commit-reveal pattern is the most effective on-chain defence. Users first commit a hash of their swap parameters, then reveal and execute after a delay. This prevents front-running because the parameters are hidden during the commitment phase. Combine this with minimum delay enforcement between swaps and deadline-based expiry for stale commitments.

Commit-reveal swap protection
struct SwapParams {
    address tokenIn;
    address tokenOut;
    uint256 amountIn;
    uint256 minAmountOut;
    uint256 deadline;
    bytes32 commitment;
}

// Step 1: Commit (parameters hidden as hash)
function commitSwap(bytes32 commitment) external {
    require(block.number >= lastSwapBlock[msg.sender] + MIN_DELAY, "Too frequent");
    commitments[commitment] = block.number + COMMIT_DURATION;
}

// Step 2: Reveal and execute
function executeSwap(SwapParams calldata params, uint256 nonce)
    external nonReentrant
{
    bytes32 commitment = keccak256(abi.encode(params, nonce, msg.sender));
    require(commitments[commitment] != 0, "Invalid commitment");
    require(block.number >= commitments[commitment], "Reveal too early");
    require(block.number <= commitments[commitment] + 10, "Reveal too late");
    require(block.timestamp <= params.deadline, "Swap expired");

    delete commitments[commitment];
    uint256 amountOut = _executeSwap(params);
    require(amountOut >= params.minAmountOut, "Insufficient output");
}

Flash Loan Protection

Flash loans enable same-block attacks where an attacker borrows a massive amount, manipulates your protocol, and repays in a single transaction. The simplest defence is blocking same-block interactions: if a user deposited in block N, they cannot withdraw until block N+1.

This breaks the atomic exploit loop without affecting normal users, who never need to deposit and withdraw in the same block. Combine with a blocklist of known flash loan provider addresses for additional protection.

Same-block interaction guard
mapping(address => uint256) public lastInteractionBlock;

modifier noFlashloanExploit() {
    require(
        lastInteractionBlock[msg.sender] != block.number,
        "Same block interaction"
    );
    lastInteractionBlock[msg.sender] = block.number;
    _;
}

function deposit(uint256 amount) external noFlashloanExploit { /* ... */ }
function withdraw(uint256 amount) external noFlashloanExploit { /* ... */ }

Advanced Gas Optimization

At scale, gas optimization goes beyond custom errors and struct packing. Storage layout optimization packs multiple related fields into a single 32-byte slot — a UserInfo struct with balance (uint128), last update (uint64), multiplier (uint32), and active flag (bool) fits in 29 bytes, using one slot instead of four.

Batch operations amortise the base transaction cost across multiple users. A single batchDeposit call with arrays of recipients and amounts performs one token transfer instead of N, saving roughly 21,000 gas per additional user. For hot paths, inline assembly can further reduce overhead — but only use it when profiling proves the saving is worth the readability cost.

Optimised storage and batch operations
// Single-slot struct: 29 bytes total
struct UserInfo {
    uint128 balance;        // 16 bytes
    uint64  lastUpdate;     //  8 bytes
    uint32  multiplier;     //  4 bytes
    bool    isActive;       //  1 byte
}

// Batch deposit — one transfer instead of N
function batchDeposit(
    address[] calldata recipients,
    uint256[] calldata amounts
) external {
    require(recipients.length == amounts.length, "Length mismatch");
    uint256 totalAmount = 0;
    for (uint256 i = 0; i < recipients.length; i++) {
        totalAmount += amounts[i];
        users[recipients[i]].balance += uint128(amounts[i]);
    }
    IERC20(token).transferFrom(msg.sender, address(this), totalAmount);
}

Auto-Compounding Vaults

Auto-compounding vaults automatically harvest rewards from an underlying protocol and reinvest them, increasing the effective APY through compound interest. Users receive shares proportional to their deposit relative to the total staked value. As the vault harvests and reinvests, each share becomes worth more underlying tokens.

The harvest function is rate-limited (e.g. once per hour) and takes a performance fee before converting rewards to staking tokens and re-depositing. Share-based accounting means users can deposit and withdraw at any time without disrupting the compounding cycle.

Auto-compounding vault core
function deposit(uint256 amount) external nonReentrant {
    _harvest(); // Update share price before deposit

    uint256 shares = totalShares == 0
        ? amount
        : amount * totalShares / totalStaked();

    userShares[msg.sender] += shares;
    totalShares += shares;
    stakingToken.transferFrom(msg.sender, address(this), amount);
    _stake(amount);
}

function _harvest() internal {
    if (block.timestamp < lastHarvest + 1 hours) return;
    uint256 rewards = _claimRewards();
    if (rewards == 0) return;

    uint256 fees = rewards * performanceFee / 10000;
    rewardToken.transfer(owner(), fees);

    uint256 compound = _swapRewardForStaking(rewards - fees);
    _stake(compound);
    lastHarvest = block.timestamp;
}

Delta-Neutral Strategies

Delta-neutral strategies earn yield while hedging price exposure. The basic flow: deposit collateral to a lending protocol, borrow stablecoins against it, buy more of the base asset, then open a short perpetual position to offset the long exposure. The result is market-neutral: you earn lending yield, borrowing spread, and funding rate payments regardless of price direction.

Rebalancing is triggered when position drift exceeds a threshold (e.g. 5 %). The strategy monitors the ratio between collateral value and debt, adjusting the hedge and leverage to maintain neutrality. This is one of the most capital-efficient DeFi strategies, but requires careful risk management around liquidation thresholds and funding rate reversals.

Governance and DAO

On-chain governance gives token holders control over protocol parameters. OpenZeppelin's Governor framework provides a complete implementation: proposal creation, voting with ERC20Votes tokens, quorum requirements, and timelock execution. The standard setup uses a 1-block voting delay, 1-week voting period, and 4 % quorum.

The governance token (ERC20Votes + ERC20Permit) supports delegation — holders can delegate their voting power without transferring tokens. This enables meta-governance and participation from users who want to influence direction without actively voting on every proposal. All proposals execute through a TimelockController, giving the community a window to exit if they disagree with an approved change.

Governor setup
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract CryptaCoreGovernor is
    Governor, GovernorSettings, GovernorCountingSimple,
    GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl
{
    constructor(IVotes _token, TimelockController _timelock)
        Governor("CryptaCoreGovernor")
        GovernorSettings(1, 50400, 0) // 1 block delay, ~1 week, 0 threshold
        GovernorVotes(_token)
        GovernorVotesQuorumFraction(4) // 4% quorum
        GovernorTimelockControl(_timelock)
    {}
}

On-Chain Monitoring

Production DeFi protocols need real-time monitoring of TVL, deposit/withdrawal volumes, active users, and APY. An on-chain analytics contract records daily metrics and emits events for off-chain indexing. Each deposit and withdrawal call also updates daily counters, giving you a continuous picture of protocol health.

The metrics contract exposes a getMetricsForPeriod function that returns an array of daily snapshots for any date range. Off-chain systems (The Graph, Dune Analytics, or custom indexers) subscribe to MetricsUpdated events for dashboards and alerting. Monitor for anomalies: sudden TVL drops, unusual withdrawal patterns, or APY deviations that might indicate an exploit in progress.

On-chain metrics tracking
struct DailyMetrics {
    uint256 totalDeposits;
    uint256 totalWithdrawals;
    uint256 activeUsers;
    uint256 totalValueLocked;
    uint256 averageAPY;
}

mapping(uint256 => DailyMetrics) public dailyMetrics; // day => metrics

function recordDeposit(address user, uint256 amount) external {
    uint256 currentDay = block.timestamp / 86400;
    dailyMetrics[currentDay].totalDeposits += amount;
}

function getMetricsForPeriod(uint256 startDay, uint256 endDay)
    external view returns (DailyMetrics[] memory)
{
    uint256 length = endDay - startDay + 1;
    DailyMetrics[] memory metrics = new DailyMetrics[](length);
    for (uint256 i = 0; i < length; i++) {
        metrics[i] = dailyMetrics[startDay + i];
    }
    return metrics;
}
TipStart with the simplest pattern that solves your problem. MEV protection, auto-compounding, and delta-neutral strategies add significant complexity. Ship a secure basic version first, then layer in advanced features as usage grows and you have audit coverage for each addition.