2. 恒定和做市商实现

任务目标

实现一个恒定和做市商(Constant Sum Market Maker, CSMM)的Solidity合约,验证其核心特性——交易价格恒定不变,并通过模拟交易观察其局限性:当储备比例失衡时,交易者可通过套利迅速耗尽某一侧流动性。

核心实现

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract ConstantSumMM {
    uint256 public reserve0;
    uint256 public reserve1;
    uint256 public constant K = 1000; // 恒定和:reserve0 + reserve1 = K

    event Swap(address indexed trader, uint256 amountIn, uint256 amountOut, bool isToken0In);
    event LiquidityAdded(uint256 amount0, uint256 amount1);
    event LiquidityRemoved(uint256 amount0, uint256 amount1);

    constructor(uint256 initial0, uint256 initial1) {
        require(initial0 + initial1 == K, "initial sum must equal K");
        reserve0 = initial0;
        reserve1 = initial1;
    }

    // 恒定和公式:reserve0 + reserve1 = K
    // 交易后仍须满足:reserve0' + reserve1' = K
    // 因此:amountOut = amountIn(不考虑手续费时价格恒为1)
    function swap(address tokenIn, uint256 amountIn) external returns (uint256 amountOut) {
        require(amountIn > 0, "amountIn must be positive");
        bool isToken0In = (tokenIn == address(this)); // 简化:token0用合约地址表示

        if (isToken0In) {
            require(reserve1 > 0, "reserve1 depleted");
            amountOut = amountIn; // 恒定和:1:1 兑换
            require(amountOut <= reserve1, "insufficient reserve1");
            reserve0 += amountIn;
            reserve1 -= amountOut;
        } else {
            require(reserve0 > 0, "reserve0 depleted");
            amountOut = amountIn;
            require(amountOut <= reserve0, "insufficient reserve0");
            reserve1 += amountIn;
            reserve0 -= amountOut;
        }

        emit Swap(msg.sender, amountIn, amountOut, isToken0In);
    }

    function addLiquidity(uint256 amount0, uint256 amount1) external {
        require(amount0 + amount1 > 0, "zero liquidity");
        reserve0 += amount0;
        reserve1 += amount1;
        emit LiquidityAdded(amount0, amount1);
    }

    function removeLiquidity(uint256 amount0, uint256 amount1) external {
        require(amount0 <= reserve0 && amount1 <= reserve1, "exceeds reserve");
        reserve0 -= amount0;
        reserve1 -= amount1;
        emit LiquidityRemoved(amount0, amount1);
    }

    function getReserves() external view returns (uint256, uint256) {
        return (reserve0, reserve1);
    }
}

运行与验证

环境依赖:Remix IDE(Solidity 0.8.19+)或Foundry/Hardhat。无需额外库。

部署与模拟交易步骤:

  1. 部署合约,构造函数传入 (600, 400),满足 600+400=1000。
  2. 调用 swap(address(this), 100)——用token0兑换token1,观察返回的 amountOut = 100,储备变为 (700, 300)。
  3. 再次调用 swap(address(this), 100),返回仍为 100,储备变为 (800, 200)。
  4. 继续调用 swap(address(this), 200),返回 200,储备变为 (1000, 0)。
  5. 此时再调用 swap(address(this), 1),会因 reserve1 = 0 而revert——token1流动性已耗尽。

验证方法与通过条件:

  • 每次swap的 amountOut 严格等于 amountIn,证明价格恒为1(恒定和特性)。
  • 观察储备变化:当一侧储备被持续兑换后归零,交易失败,证明该模型无法维持双边流动性,存在被套利者单边抽干的局限性。
  • 事件日志中 Swap 的 amountIn 与 amountOut 始终相等。
恒定和做市商(CSMM)储备变化与局限性示意 reserve0 → reserve1 → reserve0 + reserve1 = 1000 初始 (600, 400) swap 100 → (700, 300) swap 100 → (800, 200) swap 200 → (1000, 0) 流动性耗尽 每次交易 amountOut = amountIn 价格恒为 1,无滑点 ⚠ 局限性: 套利者可单边兑换 直至一侧储备归零

常见问题:若构造函数传入的初始值之和不等于1000,合约将拒绝部署。实际部署中应使用真实ERC20代币地址替代示例中的 address(this) 简化写法,并接入代币转账逻辑。