
AMMDeFiAudit
How to Audit complex DeFi protocols: The Divide and Conquer methodology
December 9, 2025•
M3D
12 min read
•7 views
•
Securing a monolithic smart contract codebase is not merely a code review task; it is a systemic engineering challenge. As DeFi protocols evolve into meshes of lending markets, yield aggregators, and cross-chain bridges, a linear read of the code is insufficient for guaranteeing security.

For enterprise-grade security, linear scanning is a liability. To ensure protocol solvency, Zealynx employs the Divide and Conquer methodology. This is the engineering practice of dismantling a system into isolated components, verifying their local invariants, and reassembling them to stress-test the seams.
We will demonstrate this methodology using Uniswap V2 as a case study. By stripping it down and isolating the
Pair logic, we reveal how rigorous component analysis protects billions of dollars in value.The theory: Systemic complexity vs linear review
The human capacity for processing complex state transitions is finite. Research into Cognitive Load suggests a hard limit on the number of active variables a reviewer can track simultaneously.
A complex DeFi protocol involves hundreds of state variables and interactions. Attempting a linear audit forces the engineer to load every variable into their mental stack, leading to cognitive overflow and, inevitably, overlooked vulnerabilities.

Divide and Conquer is a necessity for correctness in high-value systems. It formally decomposes the problem:
- Decomposition: Slicing the system into isolated modules with clear boundaries.
- Invariant Definition: Defining the "Laws of Physics" for each module.
- Local Verification: Proving the module obeys its laws in isolation.
- Compositional Analysis: Verifying that the modules interact safely.
By verifying components in isolation, we ensure that the security guarantee is built on a foundation of proven local truths, rather than a probabilistic overview.
Phase 1: The Divide (Architectural Mapping)
Before analyzing syntax, we map the territory. Effective risk management requires a clear understanding of state transitions across interacting contracts.

We categorize code into four distinct clusters to eliminate context switching and focus on specific risk vectors:
- The Vault/Asset Cluster: Where is the capital? (ERC20s, ETH balances).
- The Logic Cluster: The "brains." (Pricing, Swapping, Rate calculation).
- The Data/Oracle Cluster: External truth sources.
- The Governance Cluster: Admin powers and time-locks.
General Application: This applies to any system. In a Lending Protocol, we separate theComptroller(Logic) from thecToken(Vault). In a Bridge, we separate theRelayer(Governance) from theCustody(Vault). The goal is always to identify the Trust Boundaries—where does data enter, and where does money leave?
Visualizing Uniswap V2
Mapping Uniswap V2 reveals two primary distinct components:
- The Factory: The Governance/Admin cluster. It deploys pairs and manages the
feeToaddress. - The Pair: The Logic and Vault cluster. This is the "God Class" of the protocol.
The Strategy: We deprioritize the Factory, as it is primarily administrative. The critical solvency risk lies in the Pair contract. We isolate
UniswapV2Pair.sol, treating it as a black box with strict inputs and outputs.Phase 2: The Invariants (Defining Solvency)
Security requires defining Protocol Invariants—the mathematical truths that must never be broken, regardless of user input or market conditions.
For Uniswap V2, the documentation provides the "Constant Product Formula": .

Translated into a security invariant:
The Solvency Invariant: After any Swap operation, the product of the reserves must be greater than or equal to the product before the swap (adjusting for fees).
Note: We specify "Swap" because
mint and burn operations are designed to change k.If this invariant breaks, the pool is insolvent. Our objective is not merely bug hunting; it is to attempt to violate .
Phase 3: The Conquer (Case Study: UniswapV2Pair.sol)
Examining the
swap function in UniswapV2Pair.sol reveals the distinction between standard review and architectural analysis. A senior engineer focuses on the interaction between the Asset Layer and the Logic Layer.1. The asset layer check (optimistic transfer)
Uniswap V2 utilizes an "Optimistic Transfer" pattern: it transfers tokens out before verifying receipt of payment.
1// UniswapV2Pair.sol2function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {3 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');4 (uint112 _reserve0, uint112 _reserve1,) = getReserves();56 // ... logic ...78 // OPTIMISTIC TRANSFER9 if (amount0Out > 0) _token0.safeTransfer(to, amount0Out);10 if (amount1Out > 0) _token1.safeTransfer(to, amount1Out);1112 // FLASH LOAN HOOK13 if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);1415 // ... check invariants ...16}
The Engineering Question: If the contract releases funds first, how is solvency guaranteed?
The answer lies not in the transfer logic, but in the post-execution state validation.
2. The logic layer check (enforcing k)
The protocol does not rely on
msg.value or user arguments to determine received funds. It verifies its own balance at the end of execution.1 uint balance0 = IERC20(_token0).balanceOf(address(this));2 uint balance1 = IERC20(_token1).balanceOf(address(this));34 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;5 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
The Divide Strategy in Action:
This demonstrates a clear separation of concerns:
- State: The contract trusts
balanceOf(address(this)). - Logic: It calculates
amountInbased on the delta between the current balance and the cached reserve.
3. The vulnerability hunt: fee-on-transfer tokens
By isolating the Asset Layer, a critical discrepancy becomes apparent.
- Expectation: Logic layer assumes a user transfer of 100 tokens results in a
balanceOfincrease of 100. - Reality: Certain tokens (USDT, PAXG) levy a fee on transfer. A user sends 100, the contract receives 99.
If Uniswap trusted input arguments, the math would break. However, because Uniswap relies on
balanceOf (actual state), it handles these non-standard tokens correctly.Critical Insight: This highlights a major risk vector for forks. If a protocol fork replaces
balanceOf checks with amount input parameters to optimize gas, they introduce a fatal insolvency bug.
Phase 4: The Reassembly (Interaction Risks)
Following component verification, we reassemble the system to analyze its interaction with the external environment.
The reentrancy vector
Uniswap V2 employs a
lock modifier (mutex).1modifier lock() {2 require(unlocked == 1, 'UniswapV2: LOCKED');3 unlocked = 0;4 _;5 unlocked = 1;6}
While standard reentrancy is mitigated, Read-Only Reentrancy remains a risk.

During the
uniswapV2Call (flash loan hook), the reserves state variable remains outdated, while token balances have changed. If an external contract reads getReserves() during this callback, it receives stale pricing data. If it reads balanceOf(), it sees the new balances.Risk Assessment:
While
UniswapV2Pair is self-protected, integrating protocols are vulnerable if they calculate price based on a mix of getReserves() and balanceOf() during a flash swap.Victim Scenario: A lending protocol calculating collateral value using
Pair.getReserves() during a flash loan will see the old (high) price, despite the assets having left the pool. The "Divide and Conquer" method reveals that the risk resides at the interface boundary.Phase 5: The Verification (The Proof)
Security is not a guess; it is a proof. Once the component and invariant ( must not decrease) are identified, we employ tools to enforce it.
This methodology lays the foundation for Formal Verification. Tools like Halmos, Certora, or Hevm cannot "solve" an entire protocol at once—the state space is too vast. They require defined invariants for specific components.
Property-based testing (Foundry)
We do not write unit tests for trivial arithmetic. We write fuzz tests that attempt to break the protocol's laws.
1// Foundry Invariant Test2contract UniswapInvariant is Test {3 UniswapV2Pair pair;45 function invariant_k_never_decreases() public {6 (uint112 reserve0, uint112 reserve1,) = pair.getReserves();7 uint k = uint(reserve0) * uint(reserve1);89 // Assert that K is always maintained (allowing for small fee growth)10 assert(k >= initial_k);11 }12}
By running this against a handler that executes
swap, mint, and burn with randomized inputs, we confirm that no edge case (overflow, rounding error) violates the core logic.Conclusion
The "Divide and Conquer" methodology transforms security from a manual review into a rigorous engineering process. It is the only consistent method for identifying "Deep Bugs"—logical flaws that linters and AI tools miss.

- Divide: Separate the Money (Vaults) from the Math (Logic).
- Define: Establish the Invariants (e.g., ).
- Conquer: Verify the implementation of those invariants in isolation.
- Verify: Check the seams (Reentrancy, Interoperability).
When analyzing Uniswap V2 through this lens, we see a mechanism designed to enforce mathematical truth in an adversarial environment. This is the standard of security Zealynx brings to every engagement.
Applying This to Your Own Audits
To implement this methodology in practice:
- Start with Documentation: Before reading code, map the protocol architecture using documentation and deployment diagrams.
- Define Invariants First: Write down the mathematical truths that must hold (e.g., total shares = sum of individual shares).
- Isolate Components: Audit the vault logic separately from pricing logic, governance separately from execution.
- Test Boundaries: Focus on interaction points between components—this is where most critical bugs hide.
- Automate Verification: Use Foundry invariant tests to continuously verify your identified invariants.
Partner with Zealynx
At Zealynx, we specialize in the foundational AMM designs, architectural trade-offs, and security vulnerabilities that define the DeFi landscape. If you are building a protocol that integrates with an AMM, or require a rigorous audit of a complex DeFi system, our team provides the expert guidance necessary to ensure solvency and security.
FAQ: Divide and Conquer Methodology
1. What are protocol invariants in smart contract security?
Protocol invariants are mathematical or logical rules that must remain true throughout a smart contract's execution, regardless of user actions or market conditions. For example, in a liquidity pool, the invariant might be that
totalSupply of LP tokens always equals the sum of all individual balances, or in Uniswap V2, that the product of reserves () never decreases after swaps. Violations of these invariants typically indicate critical vulnerabilities that can lead to loss of funds. Unlike surface-level bugs like missing access controls, invariant violations represent fundamental flaws in the protocol's economic or mathematical logic.2. What is read-only reentrancy and why is it dangerous for DeFi protocols?
Read-only reentrancy occurs when a contract's view functions return stale or inconsistent state during an external call, even though the contract itself cannot be reentered for state changes. In the Uniswap V2 example, during a flash swap,
getReserves() returns old values while token balances have already changed. This is dangerous because external protocols integrating with your contract may calculate prices or collateral values based on this stale data, leading to exploits. Unlike traditional reentrancy, no mutex can prevent this—the vulnerability exists at the integration boundary. DeFi protocols must never mix on-chain state reads from different sources (like getReserves() and balanceOf()) without understanding their synchronization guarantees.3. Why do smart contract audits focus on "trust boundaries" between components?
Trust boundaries are the interfaces where data enters your protocol from external sources or where assets move between isolated components. These boundaries are the highest-risk areas because they represent points where assumptions from one component meet the reality of another. For example, when your vault accepts tokens from users (external → internal), or when your pricing logic queries an oracle (internal → external). Most critical vulnerabilities—like fee-on-transfer token issues, oracle manipulation, or cross-contract reentrancy—occur at these boundaries. The Divide and Conquer methodology specifically isolates and stress-tests these seams after verifying each component individually.
4. What's the difference between deep bugs and surface bugs in smart contracts?
Surface bugs are violations of known patterns that automated tools can catch: missing access controls, integer overflows (pre-0.8.0), unchecked external calls, or standard reentrancy. Deep bugs are protocol-specific logic flaws that violate the system's unique invariants—like allowing the constant product formula to decrease in an AMM, or enabling total shares to exceed deposited assets in a vault. Automated scanners miss these because they don't understand your protocol's intended behavior. Deep bugs require understanding the architecture, defining what "correct" means for your specific system, and systematically testing those definitions. This is why protocol-specific invariant testing and formal verification are critical for high-value DeFi systems.
5. How does the optimistic transfer pattern in Uniswap V2 maintain solvency?
The optimistic transfer pattern sends tokens to users before verifying payment was received—a seemingly dangerous design. Solvency is maintained through post-execution balance verification: instead of trusting user-provided amounts, the contract reads its actual token balances via
balanceOf(address(this)) after the transfer and compares them to cached reserves. The protocol calculates how much was received as actualBalance - (oldReserve - amountSent) and enforces the invariant on these actual amounts. This design is critical for handling fee-on-transfer tokens correctly. However, forks that replace this with input parameter validation to save gas introduce fatal insolvency bugs—a common vulnerability in AMM derivatives.6. What should DeFi founders look for when evaluating a smart contract audit?
Look for evidence that the auditor identified and tested your protocol's specific invariants, not just a checklist of generic vulnerabilities. A quality audit should document: (1) the architectural decomposition showing how components were isolated, (2) the mathematical or logical invariants defined for each component (e.g., "total staked must equal sum of user stakes"), (3) evidence of property-based or invariant testing using tools like Foundry, Echidna, or Certora, and (4) analysis of integration risks at trust boundaries. Ask potential auditors: "What invariants would you define for our protocol?" If they can't answer protocol-specific questions before reading the code, they're likely running automated scanners and manual checklist reviews—insufficient for complex DeFi systems handling significant TVL.

