smart-contracts/backlog/docs/CoW_Implementation_Checklis...

19 KiB

CoW Protocol Integration: Implementation Checklist & Testing Guide

Phase 1: Contract Setup & Verification (3-5 days)

1.1 Bonding Curve Validation

  • MycoBondingCurve.sol

    • Verify polynomial pricing formula (basePrice + coeff * supply^exponent)
    • Test price calculation at various supply levels
    • Confirm buy/sell mechanics preserve reserve invariant
    • Validate fee calculation (grossAmount - (grossAmount * feePercentage / 10000))
    • Check that calculateBuyReturn() and calculateSellReturn() are accurate
    • Gas optimization: profile buy/sell gas usage
  • MycoToken.sol

    • Verify mint() only callable by bonding curve
    • Verify burnFrom() works with and without allowance
    • Test that total supply updates correctly
    • Confirm token decimals = 18

1.2 Adapter Validation

  • BondingCurveAdapter.sol

    • Verify onlySettlement modifier protection
    • Test executeBuyForCoW():
      • Receives USDC correctly from settlement
      • Mints MYCO to adapter
      • Reverts if insufficient balance
      • Respects minMycoOut slippage protection
    • Test executeSellForCoW():
      • Receives MYCO correctly from settlement
      • Burns MYCO correctly
      • Respects minUsdcOut slippage protection
      • Updates adapter balance after sale
    • Test quoteBuy() and quoteSell():
      • Returns expected values
      • Matches calculateBuyReturn/calculateSellReturn
      • No side effects (view function)
  • BondingCurveAdapter Pre-approvals

    • Verify pre-approval to bonding curve (USDC & MYCO)
    • Verify pre-approval to vault relayer (USDC & MYCO)
    • Test that settlements can pull tokens via vault relayer

1.3 Conditional Order Validation

  • MycoConditionalOrder.sol
    • Verify IConditionalOrder interface implementation
    • Test getTradeableOrder() with:
      • Valid staticInput (buy order)
      • Valid staticInput (sell order)
      • Expired quote (should revert OrderNotValid)
      • Quote below minimum (should revert OrderNotValid)
      • Insufficient user balance (should revert PollTryNextBlock)
    • Test verify() function:
      • Calls getTradeableOrder internally
      • Doesn't modify state
      • Reverts if order invalid
    • Verify APP_DATA constant uniqueness

1.4 Interface Compliance

  • IConditionalOrder.sol

    • All error types defined
    • Function signatures match standard
    • Documentation complete
  • GPv2Order.sol

    • Order struct correct
    • KIND_SELL, KIND_BUY constants defined
    • BALANCE_ERC20 constant defined
    • hash() function correct for EIP-712

Phase 2: Unit Testing (5-7 days)

2.1 Bonding Curve Tests

// File: contracts/tests/MycoBondingCurve.test.ts

test("buy increases price correctly", async () => {
    const initialPrice = await curve.getCurrentPrice();
    const supply = await mycoToken.totalSupply();

    await usdc.transfer(user.address, ethers.parseUnits("1000", 6));
    await usdc.connect(user).approve(curve.address, ethers.parseUnits("1000", 6));

    const receipt = await curve.connect(user).buy(
        ethers.parseUnits("500", 6),    // 500 USDC
        ethers.parseUnits("900", 18)    // 900 MYCO min
    );

    const newPrice = await curve.getCurrentPrice();
    expect(newPrice).to.be.gt(initialPrice);

    // Verify: price(newSupply) > price(oldSupply)
});

test("sell decreases price correctly", async () => {
    // Setup: first buy some tokens
    await curve.connect(user).buy(
        ethers.parseUnits("500", 6),
        ethers.parseUnits("900", 18)
    );

    const beforePrice = await curve.getCurrentPrice();
    const beforeSupply = await mycoToken.totalSupply();

    // Now sell
    await mycoToken.connect(user).approve(curve.address, ethers.parseUnits("100", 18));
    await curve.connect(user).sell(
        ethers.parseUnits("100", 18),   // 100 MYCO
        ethers.parseUnits("50", 6)      // 50 USDC min
    );

    const afterPrice = await curve.getCurrentPrice();
    const afterSupply = await mycoToken.totalSupply();

    expect(afterPrice).to.be.lt(beforePrice);
    expect(afterSupply).to.be.eq(beforeSupply - ethers.parseUnits("100", 18));
});

test("slippage protection works", async () => {
    await usdc.transfer(user.address, ethers.parseUnits("1000", 6));
    await usdc.connect(user).approve(curve.address, ethers.parseUnits("1000", 6));

    const quote = await curve.calculateBuyReturn(ethers.parseUnits("500", 6));
    const tooHigh = quote + ethers.parseUnits("1", 18);

    await expect(
        curve.connect(user).buy(
            ethers.parseUnits("500", 6),
            tooHigh  // Request more than possible
        )
    ).to.be.revertedWithCustomError(curve, "SlippageExceeded");
});

test("reserve balance tracks USDC correctly", async () => {
    const initialReserve = await curve.reserveBalance();

    await usdc.transfer(user.address, ethers.parseUnits("500", 6));
    await usdc.connect(user).approve(curve.address, ethers.parseUnits("500", 6));

    await curve.connect(user).buy(
        ethers.parseUnits("500", 6),
        ethers.parseUnits("900", 18)
    );

    const newReserve = await curve.reserveBalance();
    expect(newReserve).to.equal(initialReserve + ethers.parseUnits("500", 6));
});

test("fee calculation correct", async () => {
    // Setup: create positions
    await curve.updateFeePercentage(500); // 5% fee

    const quote = await curve.calculateSellReturn(ethers.parseUnits("1000", 18));

    // quote = (grossAmount * (10000 - 500)) / 10000
    // Verify: quote < grossAmount
});

2.2 Adapter Tests

// File: contracts/tests/BondingCurveAdapter.test.ts

test("executeBuyForCoW receives USDC and mints MYCO", async () => {
    // Simulate settlement sending USDC
    await usdc.transfer(adapter.address, ethers.parseUnits("500", 6));

    // Call as if from settlement
    const tx = await adapter.connect(settlement).executeBuyForCoW(
        ethers.parseUnits("500", 6),
        ethers.parseUnits("900", 18)
    );

    const receipt = await tx.wait();
    expect(receipt?.logs.length).to.be.gt(0);

    const mycoBalance = await mycoToken.balanceOf(adapter.address);
    expect(mycoBalance).to.be.gte(ethers.parseUnits("900", 18));
});

test("executeBuyForCoW reverts if not called by settlement", async () => {
    await usdc.transfer(adapter.address, ethers.parseUnits("500", 6));

    await expect(
        adapter.connect(user).executeBuyForCoW(
            ethers.parseUnits("500", 6),
            ethers.parseUnits("900", 18)
        )
    ).to.be.revertedWithCustomError(adapter, "OnlySettlement");
});

test("quoteBuy returns correct value", async () => {
    const quote = await adapter.quoteBuy(ethers.parseUnits("500", 6));
    const expected = await curve.calculateBuyReturn(ethers.parseUnits("500", 6));

    expect(quote).to.equal(expected);
});

test("quoteSell returns correct value", async () => {
    // Create some supply first
    await curve.connect(user).buy(
        ethers.parseUnits("500", 6),
        ethers.parseUnits("900", 18)
    );

    const quote = await adapter.quoteSell(ethers.parseUnits("100", 18));
    const expected = await curve.calculateSellReturn(ethers.parseUnits("100", 18));

    expect(quote).to.equal(expected);
});

test("reentrancy guard prevents reentry", async () => {
    // Deploy malicious contract that tries to reenter
    // Verify revert
});

2.3 Conditional Order Tests

// File: contracts/tests/MycoConditionalOrder.test.ts

test("getTradeableOrder returns valid GPv2Order for buy", async () => {
    const staticInput = abi.encode(
        ['tuple(uint8,uint256,uint256,address,uint256)'],
        [[
            0,  // BUY direction
            ethers.parseUnits("500", 6),      // 500 USDC
            ethers.parseUnits("900", 18),     // 900 MYCO min
            user.address,                     // receiver
            86400                             // 24 hour validity
        ]]
    );

    const offchainInput = abi.encode(
        ['tuple(uint256,uint32)'],
        [[
            ethers.parseUnits("1000", 18),    // 1000 MYCO quote
            Math.floor(Date.now() / 1000) + 3600  // expires in 1h
        ]]
    );

    const order = await handler.getTradeableOrder(
        owner.address,
        watchtower.address,
        staticInput,
        offchainInput
    );

    expect(order.sellToken).to.equal(usdc.address);
    expect(order.buyToken).to.equal(myco.address);
    expect(order.sellAmount).to.equal(ethers.parseUnits("500", 6));
    expect(order.buyAmount).to.equal(ethers.parseUnits("1000", 18));
    expect(order.receiver).to.equal(user.address);
});

test("getTradeableOrder reverts if quote expired", async () => {
    const staticInput = abi.encode(
        ['tuple(uint8,uint256,uint256,address,uint256)'],
        [[0, ethers.parseUnits("500", 6), ethers.parseUnits("900", 18), user.address, 86400]]
    );

    const expiredQuote = abi.encode(
        ['tuple(uint256,uint32)'],
        [[ethers.parseUnits("1000", 18), Math.floor(Date.now() / 1000) - 1]]  // Already expired
    );

    await expect(
        handler.getTradeableOrder(owner.address, watchtower.address, staticInput, expiredQuote)
    ).to.be.revertedWithCustomError(handler, "OrderNotValid");
});

test("getTradeableOrder reverts if output below minimum", async () => {
    const staticInput = abi.encode(
        ['tuple(uint8,uint256,uint256,address,uint256)'],
        [[0, ethers.parseUnits("500", 6), ethers.parseUnits("2000", 18), user.address, 86400]]  // Want 2000 MYCO min
    );

    const lowQuote = abi.encode(
        ['tuple(uint256,uint32)'],
        [[ethers.parseUnits("1000", 18), Math.floor(Date.now() / 1000) + 3600]]  // Only 1000 available
    );

    await expect(
        handler.getTradeableOrder(owner.address, watchtower.address, staticInput, lowQuote)
    ).to.be.revertedWithCustomError(handler, "OrderNotValid");
});

test("verify function validates order", async () => {
    const staticInput = abi.encode(['tuple(...)'], [/*...*/]);
    const offchainInput = abi.encode(['tuple(...)'], [/*...*/]);
    const order = await handler.getTradeableOrder(/*...*/);

    // Should not revert
    await handler.verify(
        owner.address,
        watchtower.address,
        ethers.ZeroHash,
        ethers.ZeroHash,
        staticInput,
        offchainInput,
        order
    );
});

Phase 3: Integration Testing (7-10 days)

3.1 Local Fork Testing

# Test against Base mainnet state
forge test --fork-url $BASE_RPC --fork-block-number $(cast block-number --rpc-url $BASE_RPC)

Key tests:

  • Real GPv2Settlement at 0x9008D19f...
  • Real VaultRelayer interaction
  • Real Balancer Vault integration
  • Gas usage benchmarks

3.2 Mock Settlement Testing

  • Deploy mock GPv2Settlement
  • Test full settlement flow:
    • User approves Balancer Vault
    • Settlement pulls USDC via VaultRelayer
    • Calls BondingCurveAdapter.executeBuyForCoW()
    • Transfers output MYCO to user
    • Verify all balances updated correctly

3.3 End-to-End Scenario Tests

Scenario 1: Simple Buy

1. User balance: 5000 USDC, 100 MYCO
2. User creates conditional order: Buy 1000 MYCO with 500 USDC
3. Watch Tower polls, gets quote: 1000 MYCO available
4. Solver submits settlement
5. Expected result:
   - User: 4500 USDC, 1100 MYCO
   - Curve reserve: +500 USDC
   - Curve supply: -1000 MYCO

Scenario 2: Multiple Orders in Batch

1. Alice: Buy 1000 MYCO with 500 USDC
2. Bob: Buy 600 MYCO with 300 USDC
3. Both orders in same batch
4. Solver settles both atomically
5. Verify uniform clearing price applied

Scenario 3: Slippage Protection

1. User orders: Buy at least 1000 MYCO for 500 USDC
2. Quote drops to 900 MYCO between poll and execution
3. Watch Tower retries on next block
4. Quote recovers to 1200 MYCO
5. Order executes successfully

Phase 4: Watch Tower Integration (5-7 days)

4.1 Testnet Deployment (Base Sepolia)

  • Deploy MycoBondingCurve on Sepolia
  • Deploy MycoToken on Sepolia
  • Deploy BondingCurveAdapter on Sepolia
  • Deploy MycoConditionalOrder on Sepolia
  • Register handler in test ComposableCoW

4.2 Request Watch Tower Access

  • Contact CoW Protocol team
  • Provide:
    • Handler contract address
    • Network (Sepolia)
    • Expected order volume (orders/hour)
    • Handler metadata (name, description, quote endpoints)

4.3 Monitor Watch Tower Polling

  • Instrument handler to log polling calls
  • Track:
    • Number of polls per block
    • Quote latency
    • Error rates
    • Poll frequency distribution

4.4 Test Real Order Submission

  • Submit test conditional order via API
  • Verify Watch Tower picks it up
  • Monitor solver network reception
  • Check execution in settlement

Phase 5: Security Audit & Hardening (5-7 days)

5.1 Code Review Checklist

  • Arithmetic Safety

    • No overflow/underflow (using uint256 carefully)
    • Price calculations avoid division before multiplication
    • Fee calculation doesn't truncate incorrectly
  • Authorization

    • onlySettlement on execution functions ✓
    • No backdoor mint/burn mechanisms
    • Adapter can't be called directly by users
  • State Consistency

    • reserveBalance always accurate
    • accumulatedFees tracked correctly
    • totalSupply matches bonding curve state
  • Reentrancy

    • ReentrancyGuard on BondingCurveAdapter ✓
    • ReentrancyGuard on executeBuyForCoW/executeSellForCoW ✓
    • No external calls before state updates
  • Token Handling

    • SafeERC20 used everywhere ✓
    • Approval amounts reasonable
    • No unprotected transfer() calls

5.2 Fuzzing

// Use Foundry fuzzing for property testing

function testFuzz_buyAndSellPreservesReserve(
    uint256 buyAmount,
    uint256 sellAmount
) public {
    // Buy then sell should roughly preserve reserve (minus fees)
    // Fuzz over various amounts and check invariant
}

function testFuzz_priceMonotonicity(uint256 supply1, uint256 supply2) public {
    // Larger supply should have larger price
    if (supply1 < supply2) {
        assertTrue(curve.getPrice(supply1) <= curve.getPrice(supply2));
    }
}

5.3 Security Considerations

  • Test malicious input:

    • Zero amounts
    • MAX_UINT256 amounts
    • Mismatched decimals
    • Expired quotes
    • Invalid order kinds
  • Test edge cases:

    • First trade (supply = 0)
    • Trade with no liquidity
    • Very small trades (dust amounts)
    • Very large trades (supply exhaustion)
  • External security review recommended:

    • Smart contract audit firm
    • Focus: adapter integration with Settlement

Phase 6: Mainnet Preparation (3-5 days)

6.1 Deployment Configuration

  • Contract initialization parameters:

    • Base price (in USDC, 6 decimals)
    • Coefficient
    • Exponent
    • Fee percentage (basis points)
    • Treasury address
  • Token addresses on Base:

    • USDC: 0x833589fC4D06F649c466dB920d0135aa6Df1cDEA
    • MYCO: (your token)
    • Settlement: 0x9008D19f58AAbD9eD0D60971565AA8510560ab41

6.2 Deployment Scripts

# Script: deploy-base.sh

# 1. Deploy MycoToken
npx hardhat run scripts/deployToken.ts --network base

# 2. Deploy MycoBondingCurve
npx hardhat run scripts/deployCurve.ts --network base

# 3. Deploy BondingCurveAdapter
npx hardhat run scripts/deployAdapter.ts --network base

# 4. Deploy MycoConditionalOrder
npx hardhat run scripts/deployHandler.ts --network base

# 5. Register in ComposableCoW (if needed)
npx hardhat run scripts/registerHandler.ts --network base

# 6. Verify contracts on Basescan
npx hardhat verify --network base <address> <constructor-args>

6.3 Pre-launch Checklist

  • All contracts deployed and verified
  • Handler registered with Watch Tower
  • USDC/MYCO liquidity bootstrapped
  • Initial price parameters approved by governance
  • Treasury address set
  • Comprehensive documentation published
  • User guide created
  • Community announcement planned

Phase 7: Launch & Monitoring (Ongoing)

7.1 Launch Day

  • Enable conditional orders at T=0
  • Monitor:
    • Order flow (orders/hour)
    • Watch Tower polling health
    • Quote latency
    • Settlement success rate
    • Gas usage
    • Average execution price (vs. bonding curve)

7.2 Metrics to Track

Daily:
  - Orders submitted / executed / failed
  - Average slippage vs. quote
  - MEV savings vs. alternative DEX
  - Transaction costs
  - User count

Weekly:
  - Volume by order type
  - Solver performance
  - Watch Tower polling stats
  - Price feed stability

Monthly:
  - User retention
  - Protocol revenue (accumulated fees)
  - Governance parameter proposals

7.3 Incident Response

  • Setup alerting for:

    • Failed settlements
    • Quote expiry rate >5%
    • Unusual slippage
    • Gas spikes
    • Contract errors
  • Emergency procedures:

    • Pause conditional orders (via registry)
    • Update fee parameters
    • Upgrade handler logic (if proxy)
    • Contact CoW Protocol team if systemic issue

Testing Infrastructure

Hardhat Setup

// hardhat.config.ts
import "@nomicfoundation/hardhat-toolbox";
import "@openzeppelin/hardhat-upgrades";

export default {
  networks: {
    base: {
      url: process.env.BASE_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
      chainId: 8453,
    },
    baseSepolia: {
      url: process.env.BASE_SEPOLIA_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
      chainId: 84532,
    },
  },
  solidity: {
    version: "0.8.24",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
};

Test Framework

# Run all tests
npm run test

# Run with coverage
npm run test:coverage

# Run fork tests
npm run test:fork:base

# Gas benchmarking
npm run test:gas

Success Criteria

Technical

  • All tests passing (100% coverage for critical paths)
  • Gas usage < 500k for settlement
  • Quote latency < 100ms
  • Watch Tower polling success rate > 99%
  • Zero critical security findings in audit

Operational

  • 10+ orders settled successfully
  • Average execution price within 0.1% of quote
  • Zero failed settlements in first week
  • User documentation complete
  • Community feedback incorporated

Business

  • >$100k total volume settled
  • >50 unique users
  • >95% user satisfaction rating
  • Community approval for mainnet launch

Document Version: 1.0 Last Updated: 2026-04-03 Estimated Total Timeline: 4-6 weeks from research → mainnet launch