# 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 ```solidity // 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 ```solidity // 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 ```solidity // 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 ```bash # 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 ```solidity // 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 ```bash # 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