AMM自动做市商核心原理深度解析:从数学公式到合约实现

蓝紫渐变色调的XY=K双曲线视觉,展示AMM恒定乘积做市商核心原理

为什么需要AMM

在说AMM之前,先理解传统做市商的问题。

在股票、外汇这些传统金融市场,撮合买卖双方的是订单簿模式:买方出价、卖方报价,系统匹配后成交。这个模式需要专业的做市商持续报价、撤单、调价,对流动性要求很高。

区块链的匿名性和交易延迟让订单簿模式很难直接套用。 于是有人提出了一个天才的想法:与其让人工做市,不如让算法做市

AMM(Automated Market Maker,自动做市商)的核心思想很简单:用数学公式替代订单簿,买卖价格由算法自动计算。用户不需要等待对手方,只需要和一个”资金池”交易即可。

无常损失数据表格,展示不同价格波动下的损失比例从2%到42.7%

恒定乘积做市商( CPMM )的数学原理

Uniswap采用的核心算法叫做恒定乘积做市商(Constant Product Market Maker),公式非常简洁:

plaintext

x * y = k

其中:

  • x = 资金池中Token A的数量
  • y = 资金池中Token B的数量
  • k = 恒定乘积(交易前后不变)

交易机制推导

假设交易前资金池有 (x₁, y₁),满足 x₁ * y₁ = k

用户想用 Δx 个Token A换取Token B。交易后资金池变为 (x₂, y₂),需要满足:

plaintext

x₂ = x₁ + Δx  (资金池多了Δx的Token A)
x₂ * y₂ = k   (恒定乘积不变)
y₂ = k / x₂

因此用户获得的Token B数量为:

plaintext

Δy = y₁ - y₂
    = y₁ - k / (x₁ + Δx)
    = y₁ - (x₁ * y₁) / (x₁ + Δx)
    = y₁ * (1 - x₁ / (x₁ + Δx))
    = y₁ * Δx / (x₁ + Δx)

这就是AMM的定价公式。让我用一个具体例子验证:

假设资金池初始状态:x₁ = 1000 ETHy₁ = 2000000 USDC,则 k = 2,000,000,000

用户想用 Δx = 10 ETH 购买USDC:

plaintext

Δy = 2000000 * 10 / (1000 + 10) = 20000000 / 1010 ≈ 19801.98 USDC

即用户用10 ETH换到了约19802 USDC。价格约为 1 ETH = 1980.2 USDC,与当前市场价格基本一致。

大额交易的影响:滑点

继续上面的例子,如果用户要用 100 ETH 购买USDC呢?

plaintext

Δy = 2000000 * 100 / (1000 + 100) = 200000000 / 1100 ≈ 181818.18 USDC

用户得到了181818 USDC,但按市场价应该得到约198000 USDC(假设1 ETH = 1980 USDC)。差了约16000 USDC,这就是滑点。

滑点随着交易规模增大而急剧增加,这是AMM的核心限制之一。资金池越深(x和y越大),相同交易量的滑点越小。

无常损失:LP的核心风险

作为流动性提供者(LP),你需要理解一个关键概念:无常损失(Impermanent Loss)

什么是无常损失

假设ETH价格是2000 USDC。你作为LP,向资金池投入了:

  • 1 ETH(价值2000 USDC)
  • 2000 USDC

资金池总价值:4000 USDC。你占总池子的0.1%(假设总池子价值400万USDC)。

价格变化后

假设ETH涨到4000 USDC。在AMM机制下,资金池会自动调整:

plaintext

x * y = k
x * y = 1000 * 2000000 = 2,000,000,000

当 x * 4000 = 2,000,000,000 时:
x = 500 ETH
y = 500,000 USDC

池子总价值 = 500 * 4000 + 500,000 = 2,500,000 USDC

如果你没有提供流动性,而是持有原来的1 ETH + 2000 USDC:
总价值 = 1 * 4000 + 2000 = 6000 USDC

无常损失 = 6000 – 5500 = 500 USDC(约8.3%)

数学公式

无常损失可以用一个简洁的公式表达:

plaintext

无常损失 = 2 * sqrt(价格比率) / (1 + 价格比率) - 1

当价格比率变为原来的k倍时:

价格变化无常损失
±50%-2.0%
±100%-5.7%
±200%-13.4%
±400%-25.5%
±800%-42.7%

价格波动越大,无常损失越严重。 这也是为什么Uniswap V3允许LP集中流动性——他们希望用更高的手续费收益来对冲无常损失。

简化版AMM合约实现

现在来动手实现一个简化版的Uniswap AMM合约:

solidity

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

/**
 * @title SimpleAMM
 * @notice 简化版恒定乘积做市商合约
 */
contract SimpleAMM {
    // 代币A的储备量
    uint256 public reserveA;
    // 代币B的储备量
    uint256 public reserveB;
    
    // 两种代币的接口
    IERC20 public tokenA;
    IERC20 public tokenB;
    
    // 手续费比例(基点,100 = 1%)
    uint256 public fee = 30;  // 0.3% 手续费
    
    event Swap(
        address indexed user,
        address indexed tokenIn,
        address indexed tokenOut,
        uint256 amountIn,
        uint256 amountOut
    );
    event AddLiquidity(
        address indexed provider,
        uint256 amountA,
        uint256 amountB
    );
    event RemoveLiquidity(
        address indexed provider,
        uint256 amountA,
        uint256 amountB
    );
    
    constructor(address _tokenA, address _tokenB) {
        tokenA = IERC20(_tokenA);
        tokenB = IERC20(_tokenB);
    }
    
    /**
     * @notice 添加流动性(首次添加时初始化资金池)
     */
    function addLiquidity(uint256 amountA, uint256 amountB) external returns (uint256 liquidity) {
        require(amountA > 0 && amountB > 0, "Invalid amounts");
        
        // 首次添加:初始化储备量
        if (reserveA == 0 && reserveB == 0) {
            tokenA.transferFrom(msg.sender, address(this), amountA);
            tokenB.transferFrom(msg.sender, address(this), amountB);
            
            reserveA = amountA;
            reserveB = amountB;
            liquidity = amountA;  // 首次添加的liquidity代币数量
        } else {
            // 检查比例是否正确
            require(
                amountA * reserveB == amountB * reserveA,
                "Invalid ratio"
            );
            
            tokenA.transferFrom(msg.sender, address(this), amountA);
            tokenB.transferFrom(msg.sender, address(this), amountB);
            
            // 按比例计算新增的liquidity
            uint256 totalLiquidity = reserveA;  // 简化:用reserveA代表总liquidity
            liquidity = (amountA * totalLiquidity) / reserveA;
            
            reserveA += amountA;
            reserveB += amountB;
        }
        
        emit AddLiquidity(msg.sender, amountA, amountB);
    }
    
    /**
     * @notice 移除流动性
     */
    function removeLiquidity(uint256 liquidity) external returns (uint256 amountA, uint256 amountB) {
        require(liquidity > 0, "Invalid liquidity");
        
        uint256 totalLiquidity = reserveA;  // 简化
        
        amountA = (liquidity * reserveA) / totalLiquidity;
        amountB = (liquidity * reserveB) / totalLiquidity;
        
        reserveA -= amountA;
        reserveB -= amountB;
        
        tokenA.transfer(msg.sender, amountA);
        tokenB.transfer(msg.sender, amountB);
        
        emit RemoveLiquidity(msg.sender, amountA, amountB);
    }
    
    /**
     * @notice 交换代币
     * @param tokenIn 输入代币地址
     * @param amountIn 输入数量
     * @param amountOutMin 最小输出数量(防止大滑点)
     */
    function swap(
        address tokenIn,
        uint256 amountIn,
        uint256 amountOutMin
    ) external returns (uint256 amountOut) {
        require(tokenIn == address(tokenA) || tokenIn == address(tokenB), "Invalid token");
        require(amountIn > 0, "Invalid amount");
        
        (uint256 reserveIn, uint256 reserveOut, address tokenOut) = 
            tokenIn == address(tokenA) 
                ? (reserveA, reserveB, address(tokenB))
                : (reserveB, reserveA, address(tokenA));
        
        // 计算手续费(扣除0.3%)
        uint256 amountInWithFee = amountIn * (1000 - fee);
        
        // 恒定乘积公式计算输出
        // Δy = (Δx * y) / (x + Δx)
        amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);
        
        require(amountOut >= amountOutMin, "Slippage exceeded");
        require(amountOut < reserveOut, "Insufficient reserve");
        
        // 执行转账
        IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenOut).transfer(msg.sender, amountOut);
        
        // 更新储备量
        if (tokenIn == address(tokenA)) {
            reserveA += amountIn;
            reserveB -= amountOut;
        } else {
            reserveB += amountIn;
            reserveA -= amountOut;
        }
        
        emit Swap(msg.sender, tokenIn, tokenOut, amountIn, amountOut);
    }
    
    /**
     * @notice 获取当前价格(1个A能换多少B)
     */
    function getPrice(address tokenIn) public view returns (uint256) {
        require(tokenIn == address(tokenA) || tokenIn == address(tokenB), "Invalid token");
        
        uint256 reserveIn = tokenIn == address(tokenA) ? reserveA : reserveB;
        uint256 reserveOut = tokenIn == address(tokenA) ? reserveB : reserveA;
        
        // 边际价格:dy/dx = y/x
        return (reserveOut * 1e18) / reserveIn;
    }
}

实际部署需要注意的问题

上面的简化版合约存在几个问题,生产环境需要额外处理:

1. 重入攻击防护

AMM合约涉及大量ETH/代币转账,必须加锁:

solidity

bool private locked;

modifier noReentrant() {
    require(!locked, "Reentrancy detected");
    locked = true;
    _;
    locked = false;
}

2. 闪电贷风险

简化版AMM无法防止闪电贷攻击。攻击者可以:

  1. 从池子借出大额资金
  2. 在其他交易所搬砖
  3. 把钱还回来

Uniswap V2通过k = x * y必须在交易后不变来防止这个问题:

solidity

function _update(uint256 balance0, uint256 balance1) private {
    require(balance0 != 0 || balance1 != 0, "K invariant not satisfied");
    // 验证k不变(允许小误差)
}

3. 精度问题

代币通常有不同的小数位数。在计算时必须考虑精度转换:

solidity

// 假设 tokenA 是 18 位小数,tokenB 是 6 位小数
// 存储时统一转为 18 位精度
uint256 public reserveA;  // 18位精度
uint256 public reserveB;  // 转换为18位精度存储

4. 权限控制

addLiquidityremoveLiquidity函数需要考虑谁有权调用。完整的AMM合约通常会引入工厂合约模式,由工厂创建交易对:

solidity

// 工厂合约创建交易对
function createPair(address tokenA, address tokenB) external returns (address pair) {
    // ...
}

进阶话题:Uniswap V2 vs V3

Uniswap V3最大的创新是集中流动性(Concentrated Liquidity)

传统V2中,LP的流动性均匀分布在整个价格曲线 [0, ∞) 上。但实际上,大多数交易都发生在某个特定价格区间。

V3允许LP将流动性集中在特定价格段,比如 [1800, 2200]。这意味着相同资金量可以获得更高的手续费收益,但代价是当价格偏离该区间时,你的流动性就不再被使用——需要承担更高的无常损失风险。

V3的数学公式变为:

plaintext

(x + Δx) * (y + Δy) = k  // 只在这个价格段内成立

超出价格段时,该LP的资金会被完全换成一侧的资产,相当于”退出”了流动性池。

总结

AMM的核心原理其实很简洁:

  1. XY=K公式:交易前后恒定乘积不变
  2. 价格由池子深度决定:池子越大,滑点越小
  3. 无常损失是LP的主要风险:价格波动越大,损失越严重

理解这些基础概念后,你可以进一步研究:

  • Uniswap V3的集中流动性机制
  • Curve Finance的StableSwap公式(适合稳定币交易对)
  • 自动做市商的安全漏洞和攻击向量

DeFi的世界很精彩,数学和代码的结合创造出了无限可能。希望这篇文章能成为你深入学习的起点。

相关推荐阅读:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注