惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Register - Security
The Register - Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security @ Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
H
Hacker News: Front Page
L
Lohrmann on Cybersecurity
T
Troy Hunt's Blog
T
Threat Research - Cisco Blogs
A
About on SuperTechFans
T
Threatpost
AWS News Blog
AWS News Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Tor Project blog
Google Online Security Blog
Google Online Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
W
WeLiveSecurity
博客园 - 叶小钗
K
Kaspersky official blog
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
Hugging Face - Blog
Hugging Face - Blog
M
MIT News - Artificial intelligence
Hacker News - Newest:
Hacker News - Newest: "LLM"
Engineering at Meta
Engineering at Meta
有赞技术团队
有赞技术团队
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
小众软件
小众软件
D
Docker
爱范儿
爱范儿
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net

smallyu的博客

为什么买的加密货币一定要去中心化 RISC-V 虚拟机无法代替 EVM 虚拟机 Polkadot 的 Existential Deposit 机制错在哪里 利息,套利,交易策略,金融市场 LayerZero 从来不挑战比特币的地位 默认参数引起的以太坊节点运行错误 硬件钱包与资产安全 预测市场比币圈更赌场 为什么去中心化的跨链桥不可能实现 我对于 AI 时代的答案 不要投资任何隐私币 区块链技术世界的三大真理 以太坊 AA 钱包的致命问题是什么 基于 AI 语义执行的 MCP 区块链的设计 一个集成 Geth 和 CometBFT 的兼容层 我的加密货币定投策略(二) Go 语言 GMP 调度器的原理是什么 Web3 项目分析计划 对 Psyche Network 项目的分析 continuation 教程: 理解 CPS 如何开发一个比特币符文(Runes)协议 比特币脚本开发教程 我从王垠的计算机科学视频班学到了什么 区块链技术面试题(2025年版) Rust 语言容易让新手困惑的一个“过度优化” Solana 智能合约开发教程 (1) 尝试开发一个最小 EVM 虚拟机 基于 ZK 的链上身份系统设计 一个 Web3 打赏系统的设计 鼓吹 Cursor 的人技术能力都差 关于 Code Review 的礼节 假如启动一个新的以太坊 PoS 网络 发行加密货币的最好方式 所有 BFT 共识的区块链都是中心化的 对 2025 年区块链行业的预测 Restaking 项目的经济难题 如何看懂任意区块链项目的技术架构 为什么不要做以太坊的二次开发 为什么不要做智能合约开发和 DeFi 开发 我的加密货币定投策略(一) PoS 类型的区块链如何处理分叉 Ethereum Casper 为什么需要 EIP-7251 区块链中的 PBFT 不需要第二次投票 开发者的思维方式 发币的核心要点 炒币投资的小 tips 为什么说 PoW 比 PoS 更加去中心化 牛市对普通人来说意味着什么 如何健康地远程工作 为什么比特币不用概率加密函数 程序员的 “服从权威” 心理 区块链技术面试题 如何区分公有链和联盟链 对 Layer 2 项目创业想法的回复 对区块链共识机制的理解 Pebbling Game 鹅卵石游戏 PDP 文件证明的局限性 不要小瞧 ChatGPT 为什么炒币不是一个好主意 一种在区块链上生成随机数的机制 为什么以太坊的私钥计算不可逆 关于以太坊的私钥碰撞 “猜均值的2/3” dApp 游戏设计 Proof of Storage/Space/Replication 的区别 Proofs of Retrievability 文件证明的含义 对 S-PDP 文件证明的示例和解释 我的加密货币交易机器人 对区块链行业的见闻 随机确认块的共识机制 VRF + BFT 共识引起交易失败的问题 为什么要重视编程思想 对 Web 3.0 的理解 GitBook 好用吗? 一种区块链节点存储扩容的方式 一种基于“自我中心主义”的共识机制 链表常见算法题及解析 基于 Multi-Linked List 的区块链设想 理解哈希函数与序列化 联盟链比公有链差在哪儿 在 Dijkstra 算法中保存路径 为什么数字货币使用区块链是政治问题 区块链:下一代数字身份认证体系的基石 网页技术能实现 3D 建模吗? 给区块链一个定义 从 Erlang 开始了解 Actor 模型 一种侧边导航栏的交互方式 Rust 的 ownership 是什么? Haskell 中的 Monad 是什么? 浅析 Libra 背后的区块链技术 对区块链的理性认识 Rust 基础语法概述 基于 Java 的爬虫框架 WebCollector Kotlin:简化版的 Scala JavaScript 有关联数组吗? 主流编程语言的异常处理机制 Go 语言基本语法 Scala 语法基础 用 Scala 改写 Java 浅度实践 Python 获取海贼王更新信息 HTML5 音乐可视化
DeFi 基础: 理解 AMM 定价机制
2025-08-20 · via smallyu的博客

2025-08-20

这是一个 DeFi 系列教程,在动手实践的过程中,学习和理解 DeFi 相关的概念与原理:

  1. DeFi 基础: 理解 AMM 定价机制
  2. DeFi 基础: 预言机与报价
  3. DeFi 基础: 借贷与清算
  4. DeFi 进阶: 闪电贷与套利

AMM 的全称是 Automated Market Maker,自动做市商,作用是不需要订单簿撮合交易,就可以自动完成定价与交易。

这篇文章解释了 Uniswap V2 的核心定价逻辑,并且提供了完整的合约代码示例、命令行操作步骤、实际的链上交易现场等,作为理解 AMM 的配套参考。

AMM 计算公式

基本逻辑

Uniswap V2 用的定价逻辑是恒定乘积做市商(Constant Product Market Maker, CPMM),也是我们的示例 AMM 合约在用的方法。这里有一个恒等公式:

x * y = k

意味着池子里有两种资产 xy,当 x 增多的时候,y 就应该减少,y 增多的时候,x 应该减少,k 总是保持不变。

在添加初始流动性的时候,我们第一次确定下来这个 k 的值,比如我们按照 2000 USDC / 1 WETH 的价格注入初始流动性,会得到(不考虑精度):

k = 2000

当我们想要用 USDC 换出 WETH 的时候,池子里的 USDC 增多,为了保持 k 不变,合约会计算应该保留多少 WETH,然后把相应数量的 WETH 转给我们。

第一次兑换

当我们想要用 USDC 换出 WETH 的时候,池子里的 USDC 增多,为了保持 k 不变,合约就会把相应数量的 WETH 转给我们了。

例如,我们试图用 500 USDC 换出 WETH,此时加上初始流动性的 2000 USDC,池子里一共 2500 USDC,那么:

x = 2500
y = k/x = 2000/2500 = 0.8

这个 0.8 意味着,为了保证 AMM 池子里的 k 值恒定为 2000,池子需要转出 0.2 WETH。也就是说,我们会得到 0.2 个WETH。

第二次兑换

我们再来用 500 USDC 买一次,此时池子里一共有 2500+500=3000 USDC,则:

x = 3000
y = k/x = 2000/3000 = 0.667

这个恒定乘积公式计算得出池子里应该保留 0.667 个 WETH,上一轮交换后还剩 0.8 WETH,所以这一轮我们实际得到 0.8-0.667 = 0.133 WETH。

对比来看,第一次用 500 USDC 可以换出 0.2 WETH,第二次用 500 USDC 就只能换出 0.133 WETH 了。随着池子里流动性的减少,WETH 的价格涨了。

价格曲线

这就是自动做市商的核心逻辑,价格不是写死的,而是根据池子中剩余的流动性算出来的。要注意 x 和 y 的乘积是一条曲线,因为 y=k/x,画成图是这样:

接下来会用实际的操作步骤与链上交互,来体验 AMM 的运作。

示例合约

合约代码源文件在仓库:smallyunet/[email protected]

首先准备两个合约,一个是 TestERC20.sol,比起标准的 ERC-20 合约,支持自定义代币精度,以及随意 mint 一些代币。

第二个要准备的合约是 SimpleAMM.sol,提供了对代币增加流动性、代币兑换等功能。合约代码不算很简单,我们会在接下来实际的操作用,逐步体会和理解这个合约的功能,以及解读源代码。

以下所有操作都在以太坊的测试网 Sepolia 上进行。

环境准备

准备好命令行工具,以及设置两个环境变量:

foundryup

export RPC_URL="https://ethereum-sepolia-rpc.publicnode.com"
export PK_HEX="<YOUR_PRIVATE_KEY_HEX>"

下载合约仓库、进入到仓库根目录:

git clone https://github.com/smallyunet/defi-invariant-lab/
git switch v0.0.1
cd defi-invariant-lab

部署代币合约

部署合约

部署两个测试版本的 ERC-20 代币,一个叫 USDC,一个叫 WETH:

forge create \
  --rpc-url $RPC_URL \
  --private-key $PK_HEX \
  --broadcast \
  contracts/libs/TestERC20.sol:TestERC20 \
  --constructor-args "USD Coin" "USDC6" 6

部署的合约地址是:0x84637EaB3d14d481E7242D124e5567B72213D7F2

forge create \
  --rpc-url $RPC_URL \
  --private-key $PK_HEX \
  --broadcast \
  contracts/libs/TestERC20.sol:TestERC20 \
  --constructor-args "Wrapped Ether" "WETH18" 18

部署的合约地址是:0xD1d071cBfce9532C1D3c372f3962001A8aa332b7

验证合约

如果愿意,可以这样验证下合约:

export ETHERSCAN_API_KEY=你的key

cast abi-encode "constructor(string,string,uint8)" "USD Coin" "USDC6" 6

forge verify-contract \
  --chain-id 11155111 \
  0x84637EaB3d14d481E7242D124e5567B72213D7F2 \
  contracts/libs/TestERC20.sol:TestERC20 \
  --constructor-args "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000855534420436f696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055553444336000000000000000000000000000000000000000000000000000000" \
  --etherscan-api-key $ETHERSCAN_API_KEY

forge verify-contract \
  --chain-id 11155111 \
  0xD1d071cBfce9532C1D3c372f3962001A8aa332b7 \
  contracts/libs/TestERC20.sol:TestERC20 \
  --constructor-args $(cast abi-encode "constructor(string,string,uint8)" "Wrapped Ether" "WETH18" 18) \
  --etherscan-api-key $ETHERSCAN_API_KEY

部署 AMM 合约

部署合约

这里的参数 30 指收取 0.3% 的手续费:

forge create \
  --rpc-url $RPC_URL \
  --private-key $PK_HEX \
  --broadcast \
  contracts/amm/SimpleAMM.sol:SimpleAMM \
  --constructor-args $USDC_ADDR $WETH_ADDR 30

部署的合约地址是:0x339278aA7A09657A4674093Ab6A1A3df346EcFCF`

验证合约

forge verify-contract \
  --chain-id 11155111 \
  0x339278aA7A09657A4674093Ab6A1A3df346EcFCF \
  contracts/amm/SimpleAMM.sol:SimpleAMM \
  --constructor-args $(cast abi-encode "constructor(address,address,uint16)" $USDC_ADDR $WETH_ADDR 30) \
  --etherscan-api-key $ETHERSCAN_API_KEY

mint 代币

声明钱包地址与合约地址:

export MY_ADDR=0x44D7A0F44e6340E666ddaE70dF6eEa9b5b17a657
export AMM_ADDR=0x339278aA7A09657A4674093Ab6A1A3df346EcFCF
export USDC_ADDR=0x84637EaB3d14d481E7242D124e5567B72213D7F2
export WETH_ADDR=0xD1d071cBfce9532C1D3c372f3962001A8aa332b7

挖 100 万个 USDC,精度是 6 位数:

cast send $USDC_ADDR "mint(address,uint256)" $MY_ADDR 1000000000000 \
  --rpc-url $RPC_URL --private-key $PK_HEX

挖 1000 个 WETH,精度是 18 位数:

cast send $WETH_ADDR "mint(address,uint256)" $MY_ADDR 1000000000000000000000 \
  --rpc-url $RPC_URL --private-key $PK_HEX

铸币的交易与结果可以直接在浏览器上看到,这个 是挖 USDC 的交易,这个 是挖 WETH 的交易。

给 AMM 合约授权

给 AMM 授权是因为接下来想要给 AMM 添加流动性,添加流动性会调用 addLiquidity 函数,其中用到了 transferFrom,所以需要先给合约授权,让合约可以动用我的 USDC 和 WETH 代币:

cast send $USDC_ADDR "approve(address,uint256)" $AMM_ADDR "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" \
  --rpc-url $RPC_URL --private-key $PK_HEX

cast send $WETH_ADDR "approve(address,uint256)" $AMM_ADDR "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" \
  --rpc-url $RPC_URL --private-key $PK_HEX

交易哈希分别是 USDCWETH

添加初始流动性

添加流动性的 函数 比较简单,大概是合约里有两个变量 reserve0reserve1,调用 addLiquidity 函数的时候,会向 AMM 合约转账参数数量个代币。

先以 2000 USDC / 1 WETH 的价格,添加初始流动性:

cast send $AMM_ADDR "addLiquidity(uint256,uint256)" 200000000000 100000000000000000000 \
  --rpc-url $RPC_URL --private-key $PK_HEX

交易 完成后,可以查询到 AMM 合约剩余的代币数量:

cast call $AMM_ADDR "getReserves()(uint112,uint112)" --rpc-url $RPC_URL

# 200000000000 [2e11]
# 100000000000000000000 [1e20]

用 USDC 换 WETH

合约代码解读

我们的合约代码 swap0For1 是这样:

function swap0For1(uint256 amtIn) external returns (uint256 out) {
    require(token0.transferFrom(msg.sender, address(this), amtIn), "t0in"); // 把用户的 x 转进合约
    uint256 r0 = token0.balanceOf(address(this)); // 查询当前 x
    uint256 r1 = token1.balanceOf(address(this)); // 查询当前 y

    uint256 amtInEff = (amtIn * (10_000 - feeBps)) / 10_000; //计算扣除手续费后,用户转入了多少 x
    // x*y=k, solve out = r1 - k/(r0)
    uint256 k = (r0 - amtInEff) * r1;   // 计算 k
    out = r1 - Math.ceilDiv(k, r0);     // 计算给用户多少 y
    require(token1.transfer(msg.sender, out), "t1out");
}

函数代码体现了刚才描述的关于 x*y=k 的恒定公式。因为 AMM 合约考虑到收手续费的问题,所以有一个 amtInEff 用来表示用户实际转入了多少 x。

测试第 1 次交换

我们来实际发起交易,看看合约运行后的效果,先试着用 1000 USDC,看能换多少个 WETH 出来:

cast send $AMM_ADDR "swap0For1(uint256)" 1000000000 \
  --rpc-url $RPC_URL --private-key $PK_HEX

交易 完成后,查看一下代币余额:

cast call $USDC_ADDR "balanceOf(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL
cast call $WETH_ADDR "balanceOf(address)(uint256)" $MY_ADDR --rpc-url $RPC_URL

cast call $AMM_ADDR "getReserves()(uint112,uint112)" --rpc-url $RPC_URL

其实区块链浏览器上能很直接的看到交换的数量,交易哈希是:0xf13bd1d1602d7c106c2acdf4cb3b1ec37fa42d8871a682e32cce3f2049fff5a2

我们转出了 1000 USDC,收到了 0.496019900497512437 个 WETH。这里因为有 0.3% 的手续费,所以收到的 WETH 不是 0.5。

除了手续费,还存在一个价格的问题,按理来说,随着剩余 WETH 数量的减少,WETH 的价格会越来越高。

测试第 2 次交换

再来用 1000 USDC 兑换一次,看能换出多少 WETH:

cast send $AMM_ADDR "swap0For1(uint256)" 1000000000 \
  --rpc-url $RPC_URL --private-key $PK_HEX

这次兑换的交易哈希是:0x1ee9ceb0707d77d78669bfb6cc1179bf9d6b31c57b868f5f52ed2f01a4127481

这一次,花费了 1000 USDC,收到了 0.491116179005960297 个 WETH。与上一次兑换的结果相比,收到的 WETH 真的减少了。

用 WETH 换 USDC

可以自己测试玩一下。