关卡链接: Ethernaut Level 22 - Dex
攻击类型: 价格操纵 / 整数舍入漏洞
难度: ⭐⭐⭐☆☆
你和 Dex
合约最初都拥有 Token1
和 Token2
。你的目标是耗尽 Dex
合约中 Token1
或 Token2
的全部流动性。
这个 Dex
合约是一个极简的去中心化交易所,其核心漏洞在于它的价格计算函数 getSwapPrice()
:
1 | function getSwapPrice(address from, address to, uint amount) public view returns(uint){ |
这个函数通过两种代币在池中余额的比例来计算交换价格。问题出在 Solidity 的整数除法上。整数除法会向下舍入到最接近的整数,任何小数部分都会被丢弃。例如,7 / 2
的结果是 3
,而不是 3.5
。
我们可以利用这个舍入误差来获利。通过精心设计的交换顺序,我们可以在每次交换中获得比预期“公平”价格更多的代币,从而逐渐耗尽池中的资金。
我们的策略是通过在两种代币之间反复交换我们的全部余额来放大这个舍入误差。
初始状态: Player (10 TKN1, 10 TKN2), Dex (100 TKN1, 100 TKN2). 价格比为 1:1。
第一次交换 (10 TKN1 -> TKN2):
amountOut = (10 * 100) / 100 = 10
第二次交换 (20 TKN2 -> TKN1):
amountOut = (20 * 110) / 90 = 24.44...
-> 舍入后为 24第三次交换 (24 TKN1 -> TKN2):
amountOut = (24 * 110) / 86 = 30.69...
-> 舍入后为 30第四次交换 (30 TKN2 -> TKN1):
amountOut = (30 * 110) / 80 = 41.25
-> 舍入后为 41第五次交换 (41 TKN1 -> TKN2):
amountOut = (41 * 110) / 69 = 65.36...
-> 舍入后为 65现在,我们手上有65个TKN2,而Dex池中只剩下110个TKN1和45个TKN2。我们只需要用45个TKN2就可以换走池里所有的110个TKN1。
amountOut = (45 * 110) / 45 = 110
通过这种方式,我们利用了整数除法的舍入误差,在每次交易中都获得了微小的优势,并最终将这种优势累积到足以清空整个池子。
测试代码将模拟上述的交换流程。
1 | // SPDX-License-Identifier: Unlicense |
Dex
合约可以从你的地址转移 Token1
和 Token2
。Token1
和 Token2
之间来回交换你的全部余额。balanceOf(A) / balanceOf(B)
价格公式极易受到操纵。现代DEX(如Uniswap V2)使用 x * y = k
的恒定乘积公式,这使得操纵价格的成本要高得多。SafeMath
或专门的定点数库。核心概念:
攻击向量:
防御策略:
在智能合约的世界中,最简单的漏洞往往隐藏着最深刻的安全教训。 🎓