Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flashloan 5.1 #43

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ number_underscore = 'thousands'
multiline_func_header = 'params_first'

[profile.default]
solc = '0.8.19'
solc = '0.8.20'
evm_version = 'paris'
src = 'solidity'
test = 'solidity/test'
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
"package.json": "sort-package-json"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.0",
"ds-test": "github:dapphub/ds-test#e282159",
"forge-std": "github:foundry-rs/forge-std#v1.5.6",
"isolmate": "github:defi-wonderland/isolmate#59e1804",
"prb/test": "github:paulrberg/prb-test#a245c71"
"prb/test": "github:paulrberg/prb-test#a245c71",
"uniswap": "^0.0.1"
},
"devDependencies": {
"@commitlint/cli": "17.0.3",
Expand Down
4 changes: 3 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ isolmate/=node_modules/isolmate/src

contracts/=solidity/contracts
interfaces/=solidity/interfaces
test/=solidity/test
test/=solidity/test
@openzeppelin/=node_modules/@openzeppelin
@uniswap/=node_modules/uniswap/uniswap/contracts
75 changes: 75 additions & 0 deletions solidity/contracts/Flashloan/Flashloan.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
pragma solidity ^0.8.19;

import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import {IReceiver} from '../../interfaces/Flashloan/IReceiver.sol';
import {IFlashloan} from '../../interfaces/Flashloan/IFlashloan.sol';
import {ReentrancyGuard} from '@openzeppelin/contracts/utils/ReentrancyGuard.sol';

// Without reentrancy guard, user can make a flashloan attack by using his flashloan to deposit into the pool
contract Flashloan is IFlashloan, ReentrancyGuard {
using SafeERC20 for IERC20;

mapping(address _user => mapping(address _token => uint256 _amount)) tokenBalances;
mapping(address _user => uint256 _amount) ethBalances;

constructor() {}

/// @dev allows users to flashloan any tokens or eth deposited in the pool
/// @dev If address(0) is passed as _token, this is treated as ETH
/// @param _amount flashloan total desired, must not exceed pool balance
/// @param _token token (or eth) to be flashloaned
function flashloan(uint256 _amount, address _token) external nonReentrant {
if (address(_token) == address(0)) {
uint256 poolBalance = address(this).balance;
if (_amount > poolBalance) revert InsufficientPoolBalance();

IReceiver(msg.sender).getETH{value: _amount}();

uint256 balanceAfter = address(this).balance;

if (balanceAfter < poolBalance + (poolBalance * 1e13) / 1e18) revert InsufficientRepayment();
} else {
uint256 poolBalance = IERC20(_token).balanceOf(address(this));
if (_amount > poolBalance) revert InsufficientPoolBalance();

IERC20(_token).safeTransfer(msg.sender, _amount);
IReceiver(msg.sender).getTokens(_token, _amount);

uint256 balanceAfter = IERC20(_token).balanceOf(address(this));

if (balanceAfter < poolBalance + (poolBalance * 1e13) / 1e18) revert InsufficientRepayment();
}
}

/// @dev Deposit function for erc20 tokens or eth
/// @dev if _token address is address(0), this is an eth deposit
/// @param _amount Amount of token to deposit
/// @param _token Address of token to deposit
function deposit(uint256 _amount, address _token) external payable nonReentrant {
if (_token == address(0)) {
ethBalances[msg.sender] += msg.value;
} else {
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
tokenBalances[msg.sender][_token] += _amount;
}
}

/// @dev Withdraw function for erc20 tokens or eth
/// @dev if _token address is address(0), this is an eth withdrawal
/// @param _amount Amount of token to withdraw
/// @param _token Address of token to withdraw
function withdraw(uint256 _amount, address _token) external nonReentrant {
if (_token == address(0)) {
ethBalances[msg.sender] -= _amount;
(bool success,) = payable(msg.sender).call{value: _amount}('');
require(success, 'Failed to send ETH');
} else {
tokenBalances[msg.sender][_token] -= _amount;
IERC20(_token).safeTransfer(msg.sender, _amount);
}
}

/// @dev users can deposit eth by sending it directly to the contract
receive() external payable {}
}
30 changes: 30 additions & 0 deletions solidity/contracts/Flashloan/GreedyReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pragma solidity ^0.8.19;

import {IFlashloan} from '../../interfaces/Flashloan/IFlashloan.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';

error OnlyPool();

contract GreedyReceiver {
IFlashloan public pool;

using SafeERC20 for IERC20;

modifier onlyPool() {
if (msg.sender != address(pool)) revert OnlyPool();
_;
}

constructor(address _poolAddress) {
pool = IFlashloan(_poolAddress);
}

function flashLoan(uint256 _amount, address _token) external {
pool.flashloan(_amount, _token);
}

function getETH() external payable onlyPool {}

function getTokens(address _token, uint256 _amount) external onlyPool {}
}
35 changes: 35 additions & 0 deletions solidity/contracts/Flashloan/Receiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pragma solidity ^0.8.19;

import {IFlashloan} from '../../interfaces/Flashloan/IFlashloan.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';

error OnlyPool();

contract Receiver {
IFlashloan public pool;

using SafeERC20 for IERC20;

modifier onlyPool() {
if (msg.sender != address(pool)) revert OnlyPool();
_;
}

constructor(address _poolAddress) {
pool = IFlashloan(_poolAddress);
}

function flashLoan(uint256 _amount, address _token) external {
pool.flashloan(_amount, _token);
}

function getETH() external payable onlyPool {
(bool success,) = payable(msg.sender).call{value: msg.value + (msg.value * 1e13) / 1e18}('');
require(success, 'failed to send ETH');
}

function getTokens(address _token, uint256 _amount) external onlyPool {
IERC20(_token).safeTransfer(msg.sender, _amount + (_amount * 1e13) / 1e18);
}
}
59 changes: 0 additions & 59 deletions solidity/contracts/Greeter.sol

This file was deleted.

73 changes: 73 additions & 0 deletions solidity/contracts/Oracle/Oracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
pragma solidity ^0.8.19;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IWrapper} from '../../interfaces/Oracle/IWrapper.sol';
import {IOracle} from '../../interfaces/Oracle/IOracle.sol';

contract Oracle is Ownable, IOracle {
mapping(address _tokenA => mapping(address _tokenB => IWrapper _wrapper)) public pairWrappers;
mapping(address _tokenA => IWrapper _wrapper) public tokenWrappers;
IWrapper public defaultWrapper;
mapping(string _wrapperName => IWrapper _wrapper) public wrapperRegistry;

modifier checkWrapper(string memory _wrapperName) {
if (address(wrapperRegistry[_wrapperName]) == address(0)) revert WrapperNotRegistered();
_;
}

constructor() Ownable(msg.sender) {}

function registerWrapper(string memory _wrapperName, IWrapper _wrapper) public onlyOwner {
wrapperRegistry[_wrapperName] = _wrapper;

emit WrapperRegistered(_wrapperName, _wrapper);
}

/// @dev Sets wrapper for a given pair, if the pair is quoted then this price is returned first
/// @dev Order of tokens doesn't matter
/// @param _tokenA First token in the pair
/// @param _tokenB Second token in the pair
/// @param _wrapperName Name of oracle wrapper to quote
function setPairWrapper(
address _tokenA,
address _tokenB,
string memory _wrapperName
) external checkWrapper(_wrapperName) onlyOwner {
pairWrappers[_tokenA][_tokenB] = wrapperRegistry[_wrapperName];
pairWrappers[_tokenB][_tokenA] = wrapperRegistry[_wrapperName];
}

/// @dev Sets wrapper for a given token, if the token is quoted then this price is returned if no pair is set
/// @param _token token whose wrapper is set
/// @param _wrapperName Name of oracle wrapper to quote
function setTokenWrapper(address _token, string memory _wrapperName) external checkWrapper(_wrapperName) onlyOwner {
tokenWrappers[_token] = wrapperRegistry[_wrapperName];
}

/// @dev Sets default, if the token and pair aren't registered then this is quoted as a last resort
/// @param _wrapperName Name of oracle wrapper to quote
function setDefaultWrapper(string memory _wrapperName) external checkWrapper(_wrapperName) onlyOwner {
defaultWrapper = wrapperRegistry[_wrapperName];
}

/// @dev Queries on-chain price data through a shared interface
/// @dev Pairs have priority over tokens and tokens over the default, which is the last resort
/// @param _tokenIn Token to be 'swapped' out of
/// @param _amountIn Amount of input token
/// @param _tokenOut Token to be 'swapped' into
function getAmountOut(
address _tokenIn,
uint256 _amountIn,
address _tokenOut
) public view returns (uint256 _amountOut) {
if (address(pairWrappers[_tokenIn][_tokenOut]) != address(0)) {
return pairWrappers[_tokenIn][_tokenOut].getAmountOut(_tokenIn, _amountIn, _tokenOut);
} else if (address(tokenWrappers[_tokenIn]) != address(0)) {
return tokenWrappers[_tokenIn].getAmountOut(_tokenIn, _amountIn, _tokenOut);
} else if (address(defaultWrapper) != address(0)) {
return defaultWrapper.getAmountOut(_tokenIn, _amountIn, _tokenOut);
}

revert NoWrapperSet();
}
}
37 changes: 37 additions & 0 deletions solidity/contracts/Swapper/Keeper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
pragma solidity ^0.8.19;

import {IKeep3rV2} from '../../interfaces/Swapper/IKeep3rV2.sol';
import {IWETH} from '../../interfaces/Swapper/IWETH.sol';

contract Keeper {
error KeeperNotValid();
error JobNotReady();

uint256 lastWorked;
address keep3r = 0xdc02981c9C062d48a9bD54adBf51b816623dcc6E;
uint256 constant TEN_MINUTES = 600;

IWETH constant WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

modifier validateAndPayKeeper(address _keeper) {
if (!IKeep3rV2(keep3r).isKeeper(_keeper)) revert KeeperNotValid();
_;
IKeep3rV2(keep3r).directTokenPayment(address(WETH), _keeper, 1e17);
}

function work() external validateAndPayKeeper(msg.sender) {
if (!workable()) {
revert JobNotReady();
}

lastWorked = block.timestamp;

swap();
}

function workable() public view returns (bool _workable) {
return block.timestamp >= lastWorked + TEN_MINUTES;
}

function swap() public virtual {}
}
Loading
Loading