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。无需额外库。
部署与模拟交易步骤:
- 部署合约,构造函数传入
(600, 400),满足600+400=1000。 - 调用
swap(address(this), 100)——用token0兑换token1,观察返回的amountOut = 100,储备变为(700, 300)。 - 再次调用
swap(address(this), 100),返回仍为100,储备变为(800, 200)。 - 继续调用
swap(address(this), 200),返回200,储备变为(1000, 0)。 - 此时再调用
swap(address(this), 1),会因reserve1 = 0而revert——token1流动性已耗尽。
验证方法与通过条件:
- 每次swap的
amountOut严格等于amountIn,证明价格恒为1(恒定和特性)。 - 观察储备变化:当一侧储备被持续兑换后归零,交易失败,证明该模型无法维持双边流动性,存在被套利者单边抽干的局限性。
- 事件日志中
Swap的amountIn与amountOut始终相等。
常见问题:若构造函数传入的初始值之和不等于1000,合约将拒绝部署。实际部署中应使用真实ERC20代币地址替代示例中的 address(this) 简化写法,并接入代币转账逻辑。