DeFi composability patterns are the architectural blueprints that let developers combine protocols like LEGO® bricks — each piece independent, yet infinitely combinable. This property, unique to blockchain systems, turns every deployed contract into a potential building block for the next innovation. For Vietnamese developers building on KyberSwap, VNDC, or cross-chain bridges, understanding these patterns isn't academic — it's the difference between a composable protocol and a fragile monolith.
Diagram - DeFi Composability Taxonomy: Four dimensions (Input, Output, State, Control) with example
Core Composability Primitives
Before composing, you need standardized parts. DeFi relies on three token standards and a handful of canonical interfaces:
ERC-20 — Fungible tokens (USDC, VNDC, WETH). The IERC20 interface (transfer, approve, transferFrom) is the universal language of value transfer.
ERC-721 — Non-fungible tokens (Uniswap V3 positions, NFTfi collateral). IERC721 adds ownerOf, safeTransferFrom, and metadata extensions.
ERC-1155 — Multi-tokens (gaming items, semi-fungible positions). IERC1155 batches transfers via safeBatchTransferFrom, critical for gas-efficient composition.
Canonical DeFi Interfaces:
IUniswapV3Pool—swap,mint,burn,flash,swapCallbackIPool(Aave V3) —supply,borrow,flashLoan,liquidationCallIFlashLoanReceiver—onFlashLoancallback standardICCIPReceiver—ccipReceivefor cross-chain messages
Comparison Table - Token Standards & Key Interfaces: columns for Standard, Key Methods, Composabilit
Fundamental Composition Patterns
Sequential Composition: Output Feeds Input
The simplest pattern: Protocol A's output becomes Protocol B's input, atomically within one transaction.
`solidity // Flash loan → Swap → Repay (arbitrage) function atomicArbitrage(address tokenA, address tokenB, uint256 amount) external { // 1. Borrow from Aave IPool(aavePool).flashLoan(address(this), tokenA, amount, "", 0); }
function onFlashLoan(address token, uint256 amount, uint256 fee, bytes calldata) external override { // 2. Swap on Uniswap V3 uint256 amountOut = ISwapRouter(uniswapRouter).exactInputSingle( IV3SwapRouter.ExactInputSingleParams({tokenIn: token, tokenOut: tokenB, fee: 3000, recipient: address(this), amountIn: amount, amountOutMinimum: 0, sqrtPriceLimitX96: 0}) ); // 3. Repay flash loan + keep profit IERC20(token).approve(aavePool, amount + fee); // Profit remains in this contract } `
Key invariant: If any step reverts, the entire transaction reverts — no partial state changes.
Flowchart - Sequential Composition: Flash Loan → DEX Swap → Repay → Profit, with state transitions l
Parallel Composition: Independent Operations Batched
Multiple independent operations execute in a single transaction via Multicall or batch functions.
`solidity // Batch: Approve USDC → Approve WETH → Add liquidity to two pools bytes[] memory calls = new bytes[](4); calls[0] = abi.encodeWithSelector(IERC20.approve.selector, uniswapRouter, type(uint256).max); calls[1] = abi.encodeWithSelector(IERC20.approve.selector, uniswapRouter, type(uint256).max); calls[2] = abi.encodeWithSelector(IV3PoolActions.mint.selector, params1); calls[3] = abi.encodeWithSelector(IV3PoolActions.mint.selector, params2); IMulticall3(multicall).aggregate3(calls); `
Gas savings: ~21,000 gas per call avoided vs. separate transactions.
Graph - Parallel Composition DAG: User → Multicall → [Approve A, Approve B, Mint Pool 1, Mint Pool 2
→ Done]
Conditional Composition: Logic-Gated Execution
Execution paths branch based on on-chain conditions — prices, balances, governance states.
`solidity // Conditional: Only swap if price > threshold function conditionalSwap(uint256 minPrice) external { (uint160 sqrtPriceX96,,,,,,) = IUniswapV3Pool(pool).slot0(); uint256 currentPrice = (uint256(sqrtPriceX96) ** 2) >> 192;
if (currentPrice >= minPrice) { ISwapRouter(router).exactInputSingle(params); } // Else: revert or no-op } `
Advanced pattern: Diamond proxy facet selection based on msg.sender or block.timestamp.
Flowchart - Conditional Composition: Price Check → Decision Diamond (Yes/No) → Swap / Revert
Advanced Architectural Patterns
Diamond Proxy / Facet Pattern (EIP-2535)
Modular upgradeability for composed protocols. Each "facet" implements a slice of functionality; the diamond delegates via fallback.
`solidity // Diamond.sol — minimal fallback function fallback() external payable { address facet = diamondStorage.facetAddress[msg.sig]; require(facet != address(0), "Facet not found"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) let size := returndatasize() returndatacopy(0, 0, size) switch result case 0 { revert(0, size) } default { return(0, size) } } } `
Composability advantage: Add UniswapFacet, AaveFacet, BridgeFacet independently. Upgrade one without touching others.
Diagram - Diamond Architecture: Diamond Contract → Facet Registry → [UniswapFacet, AaveFacet, Bridge
with delegatecall arrows]
Intent-Based Execution (ERC-4337)
Users sign UserOperation intents; bundlers execute; paymasters sponsor gas. Composability shifts from "which contracts to call" to "what outcome I want."
`solidity // UserOperation for: "Swap 1000 USDC → WETH on best DEX, pay gas in USDC" UserOperation { sender: smartAccount, nonce: 0, initCode: "", // account already deployed callData: abi.encodeWithSelector(ISmartAccount.execute.selector, targets, calldatas), callGasLimit: 500_000, verificationGasLimit: 100_000, preVerificationGas: 50_000, maxFeePerGas: 20 gwei, maxPriorityFeePerGas: 2 gwei, paymasterAndData: paymaster.encodeUSDCGasPayment(), signature: userSignature } `
Composability shift: Solvers compete to fulfill intents — MEV becomes a feature, not a bug.
Flowchart - Intent Resolution: User → UserOperation → Bundler → Solver Network → EntryPoint → Smart
Cross-Chain Composability
Composing across chains requires message passing with guaranteed delivery.
CCIP Pattern (Chainlink): `solidity // Source chain: Send tokens + message ccipRouter.ccipSend( destinationChainSelector, EVM2AnyMessage({ receiver: abi.encode(destinationContract), data: abi.encodeWithSelector(ICrossChainReceiver.receive.selector, params), tokenAmounts: [{token: usdc, amount: 10000}] }) );
// Destination chain: Receive + execute function ccipReceive(Client.EVM2AnyMessage memory message) external override { (bool success,) = address(this).call(message.data); require(success, "Execution failed"); } `
LayerZero V2 Pattern: `solidity // OApp sends via DVN _lzSend(dstEid, abi.encode(params), paymasterParams, adapterParams);
// OApp receives + verifies via DVN function lzReceive(uint16, bytes calldata, uint64, bytes calldata message) external override { (bool success,) = address(this).call(message); require(success); } `
Diagram - Cross-Chain Message Flow: Source Chain → CCIP/LayerZero → DVN/Committee → Destination Chai
Security Patterns for Safe Composability
Reentrancy Guards
Checks-Effects-Interactions (CEI): Update state before external calls.
`solidity // ❌ Vulnerable: External call before state update function withdraw() external { uint256 amount = balances[msg.sender]; (bool success,) = msg.sender.call{value: amount}(""); balances[msg.sender] = 0; // Too late! }
// ✅ CEI: State update first function withdraw() external { uint256 amount = balances[msg.sender]; balances[msg.sender] = 0; (bool success,) = msg.sender.call{value: amount}(""); } `
ReentrancyGuard (OpenZeppelin): Mutex pattern for non-CEI cases.
`solidity import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract ComposedProtocol is ReentrancyGuard { function complexOperation() external nonReentrant { // Safe to make multiple external calls } } `
Transient Storage (EIP-1153): Cheaper reentrancy locks (~100 gas vs 5,000+).
`solidity function transientLock() external { if (tload(0) == 1) revert ReentrantCall(); tstore(0, 1); // ... external calls ... tstore(0, 0); } `
Oracle Manipulation Resistance
Composed protocols often need prices. Never trust a single spot price.
`solidity // TWAP from Uniswap V3 (manipulation-resistant) function getTWAP(address pool, uint32 secondsAgo) external view returns (int256) { (int56 tickCumulative,,) = IUniswapV3Pool(pool).observe([secondsAgo]); // Convert tick to price }
// Chainlink + TWAP medianizer function getSafePrice() external view returns (uint256) { uint256 chainlinkPrice = AggregatorV3Interface(feed).latestAnswer(); uint256 twapPrice = getTWAP(pool, 300); // 5-min TWAP return (chainlinkPrice + twapPrice) / 2; // Median } `
MEV Protection in Composed Transactions
Private Mempool (Flashbots Protect): Submit bundles directly to builders.
Commit-Reveal: Hash intent → reveal in next block.
Batch Auctions (CoW Swap): Solvers compete for batch execution; surplus returned to users.
Comparison Table - MEV Protection Methods: columns for Method, Gas Overhead, Trust Assumptions, Comp
Upgradeability Safety
- Timelocks: 48-hour delay on admin actions (OpenZeppelin
TimelockController) - Multisig Governance:
Safe(Gnosis Safe) for protocol upgrades - Emergency Pause:
Pausableon critical functions - Facet Cut Validation: Diamond
diamondCutrequires governance approval
Gas Optimization for Composed Transactions
| Pattern | Gas Cost (Est.) | Savings vs Naive |
|---|---|---|
| Multicall3 batch (4 calls) | ~180,000 | ~84,000 (4 × 21k base) |
| Transient storage (EIP-1153) | ~100/slot | ~5,000 vs SSTORE |
| CREATE2 deterministic deploy | ~32,000 | Avoids factory lookup |
| Calldata compression (ERC-2771) | ~16/byte | ~68% reduction |
view function multicall | ~2,000 | Avoids RPC round-trips |
Pro tip: Use Foundry forge snapshot --diff to track gas regressions in composed flows.
Before/After Gas Comparison Bar Chart: Naive vs Optimized for 5 common composed flows
Testing & Simulation Frameworks
Foundry Fork Testing
`solidity // test/Composability.t.sol import "forge-std/Test.sol"; import "../src/ComposedProtocol.sol";
contract ComposabilityTest is Test { address mainnetRpc = vm.envString("MAINNET_RPC");
function setUp() public { vm.createFork(mainnetRpc); // Impersonate whale for token setup }
function test_SequentialArbitrage() public { vm.prank(whale); composedProtocol.atomicArbitrage(USDC, WETH, 100_000e6); // Assert profit > 0 }
function test_CrossChainMessage() public { vm.createFork(arbitrumRpc); // Test CCIP message delivery } } `
Tenderly Simulation
`bash
tenderly simulate --network mainnet \ --from 0xYourAddress \ --to 0xComposedProtocol \ --input "0x..." \ --block-number 19500000 `
Real-World Case Studies
Uniswap V3 + Aave V3: Leveraged Yield Farming
Strategy: Borrow USDC on Aave → Provide USDC/WETH concentrated liquidity on Uniswap V3 → Earn fees + farming rewards.
`solidity function leveragedFarm() external { // 1. Flash loan WETH from Aave // 2. Swap half to USDC on Uniswap // 3. Mint concentrated position (tickLower, tickUpper) // 4. Stake position in incentive gauge // 5. Repay flash loan } `
Risk: Impermanent loss amplified by leverage. Mitigation: Narrow range + active rebalancing.
Cross-Chain Arbitrage via LayerZero
Flow: Ethereum USDC → LayerZero → Arbitrum USDC.e → Swap on Camelot → Bridge back.
Key insight: DVN verification adds ~3-5 min latency. Use for statistical arb, not HFT.
Vietnamese DeFi: KyberSwap + VNDC
KyberSwap's concentrated liquidity + VNDC (VND-pegged stablecoin) enables:
`solidity // VNDC/USDC stable pair — near-zero IL function vnStableFarm() external { // 1. Deposit VNDC + USDC to KyberSwap elastic pool // 2. Earn swap fees + KYBER emissions // 3. Auto-compound via KyberSwap's reinvestment } `
Local context: VN users prefer VNDC for fiat on/off-ramp via MoMo/ZaloPay integrations.
Anti-Patterns & Common Pitfalls
| Anti-Pattern | Severity | Symptom | Fix |
|---|---|---|---|
| Over-composition | Critical | Tx reverts "out of gas" or exceeds block limit | Limit to ≤5 external calls; use multicall |
| Cascade failure | High | One protocol pause kills entire flow | Circuit breakers; fallback paths |
| Trust assumption drift | Medium | Oracle/bridge upgraded without notice | Version-pinned interfaces; monitoring |
| State collision | Medium | Diamond facets clobber storage slots | EIP-2535 DiamondStorage pattern |
| MEV sandwich | High | Composed swap front-run | Private mempool; commit-reveal |
Anti-Pattern Cards: 5 cards with severity badges (Critical/High/Medium), description, and mitigation
Future of DeFi Composability
- Intent-Centric Architectures — Anoma, SUAVE: Users express what, solvers determine how.
- Solver Networks — Specialized actors compete to fulfill composed intents optimally.
- ZK-Composability — RISC Zero, SP1: Prove correct execution of composed flows off-chain.
- Modular Execution Layers — Fuel, Movement: Parallel execution for composed transactions.
FAQ
What is the difference between composability and interoperability? Composability = combining protocols within a shared execution environment (same chain, atomic). Interoperability = communicating across environments (cross-chain, async).
How do flash loans enable composability? They provide instant, uncollateralized capital for the duration of one transaction — enabling arbitrage, liquidations, and refinancing without user capital lockup.
What are the gas limits for composed transactions? Ethereum mainnet: 30M gas/block. Practical limit per tx: ~15-20M. Plan for ≤5 complex external calls.
Can composability work across L2s? Yes — via shared sequencing (OP Stack, Arbitrum Orbit), cross-L2 messaging (Hop, Across), or L1 settlement. Atomic cross-L2 composability requires shared sequencer.
How do I audit a composed protocol? Audit each facet/integration in isolation, then audit the composition logic (call order, state dependencies, failure modes). Use Slither + Echidna for property testing.
Disclaimer: This article is for educational purposes only. DeFi composability involves significant financial risk. Always audit contracts, test on testnets, and consult qualified auditors before deploying.
