From 65faa755e322ab24bea383e9a6a470e765e33d32 Mon Sep 17 00:00:00 2001 From: deo Date: Thu, 30 Nov 2023 18:23:32 -0500 Subject: [PATCH] fix: shaito suggested fixes implemented, cleaned up repo for future projects --- solidity/contracts/Swapper/Keeper.sol | 37 +++++++++++ solidity/contracts/{ => Swapper}/Swapper.sol | 2 +- .../contracts/{ => Swapper}/SwapperV2.sol | 2 +- .../contracts/{ => Swapper}/SwapperV3.sol | 66 +++++++++---------- .../interfaces/{ => Swapper}/IKeep3rV2.sol | 0 .../interfaces/{ => Swapper}/ISwapper.sol | 0 .../interfaces/{ => Swapper}/ISwapperV2.sol | 0 .../interfaces/{ => Swapper}/ISwapperV3.sol | 3 +- solidity/interfaces/Swapper/IWETH.sol | 7 ++ .../test/unit/{ => Swapper}/Swapper.t.sol | 2 +- .../test/unit/{ => Swapper}/SwapperV2.t.sol | 2 +- .../test/unit/{ => Swapper}/SwapperV3.t.sol | 4 +- 12 files changed, 83 insertions(+), 42 deletions(-) create mode 100644 solidity/contracts/Swapper/Keeper.sol rename solidity/contracts/{ => Swapper}/Swapper.sol (97%) rename solidity/contracts/{ => Swapper}/SwapperV2.sol (98%) rename solidity/contracts/{ => Swapper}/SwapperV3.sol (70%) rename solidity/interfaces/{ => Swapper}/IKeep3rV2.sol (100%) rename solidity/interfaces/{ => Swapper}/ISwapper.sol (100%) rename solidity/interfaces/{ => Swapper}/ISwapperV2.sol (100%) rename solidity/interfaces/{ => Swapper}/ISwapperV3.sol (93%) create mode 100644 solidity/interfaces/Swapper/IWETH.sol rename solidity/test/unit/{ => Swapper}/Swapper.t.sol (98%) rename solidity/test/unit/{ => Swapper}/SwapperV2.t.sol (98%) rename solidity/test/unit/{ => Swapper}/SwapperV3.t.sol (96%) diff --git a/solidity/contracts/Swapper/Keeper.sol b/solidity/contracts/Swapper/Keeper.sol new file mode 100644 index 00000000..00883826 --- /dev/null +++ b/solidity/contracts/Swapper/Keeper.sol @@ -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 {} +} diff --git a/solidity/contracts/Swapper.sol b/solidity/contracts/Swapper/Swapper.sol similarity index 97% rename from solidity/contracts/Swapper.sol rename to solidity/contracts/Swapper/Swapper.sol index bb64d393..91b60338 100644 --- a/solidity/contracts/Swapper.sol +++ b/solidity/contracts/Swapper/Swapper.sol @@ -3,7 +3,7 @@ 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 {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; -import {ISwapper} from '../interfaces/ISwapper.sol'; +import {ISwapper} from '../../interfaces/Swapper/ISwapper.sol'; /// @title Swapper contract /// @author 0xdeo diff --git a/solidity/contracts/SwapperV2.sol b/solidity/contracts/Swapper/SwapperV2.sol similarity index 98% rename from solidity/contracts/SwapperV2.sol rename to solidity/contracts/Swapper/SwapperV2.sol index 394dedb4..e2c93029 100644 --- a/solidity/contracts/SwapperV2.sol +++ b/solidity/contracts/Swapper/SwapperV2.sol @@ -5,7 +5,7 @@ import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; import {IUniswapV2Router01} from '@uniswap/periphery/interfaces/IUniswapV2Router01.sol'; import {IUniswapV2Pair} from '@uniswap/core/interfaces/IUniswapV2Pair.sol'; -import {ISwapperV2} from '../interfaces/ISwapperV2.sol'; +import {ISwapperV2} from '../../interfaces/Swapper/ISwapperV2.sol'; /// @title Swapper contract V2 /// @author 0xdeo diff --git a/solidity/contracts/SwapperV3.sol b/solidity/contracts/Swapper/SwapperV3.sol similarity index 70% rename from solidity/contracts/SwapperV3.sol rename to solidity/contracts/Swapper/SwapperV3.sol index 380c2841..5cf755d5 100644 --- a/solidity/contracts/SwapperV3.sol +++ b/solidity/contracts/Swapper/SwapperV3.sol @@ -5,41 +5,44 @@ import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; import {IUniswapV2Router01} from '@uniswap/periphery/interfaces/IUniswapV2Router01.sol'; import {IUniswapV2Pair} from '@uniswap/core/interfaces/IUniswapV2Pair.sol'; -import {ISwapperV3} from '../interfaces/ISwapperV3.sol'; -import {IKeep3rV2} from '../interfaces/IKeep3rV2.sol'; +import {ISwapperV3} from '../../interfaces/Swapper/ISwapperV3.sol'; +import {Keeper} from './Keeper.sol'; /// @title Swapper contract V2 /// @author 0xdeo /// @notice Allows friends to pool their tokens and make a swap /// @notice Routes swaps thru Uniswap -contract SwapperV3 is ISwapperV3 { +contract SwapperV3 is ISwapperV3, Keeper { using SafeERC20 for IERC20; - IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IERC20 constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); IUniswapV2Router01 constant router = IUniswapV2Router01(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); IUniswapV2Pair constant pair = IUniswapV2Pair(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11); - mapping(uint256 => Swap) public swaps; + mapping(uint256 _swapId => Swap _swapData) public swaps; uint256 swapId; - uint256 lastWorked; - address keep3r = 0xdc02981c9C062d48a9bD54adBf51b816623dcc6E; - constructor() {} - modifier validateAndPayKeeper(address _keeper) { - if (!IKeep3rV2(keep3r).isKeeper(_keeper)) revert KeeperNotValid(); - _; - IKeep3rV2(keep3r).directTokenPayment(address(WETH), _keeper, 1e17); + /// @notice Can provide ETH when swap hasn't been done yet + /// @notice Cannot provide tokens if fromToken balance will exceed contract's toToken balance + /// @notice User's deposit gets recorded in latest epoch, and the swapId recording his deposit is then returned + function provide() public payable returns (uint256 _swapId) { + swaps[swapId].balances[msg.sender] += msg.value; + + emit Provide(msg.sender, msg.value, swapId); + return swapId; } - /// @notice Can only provide tokens when swap hasn't been done yet + /// @notice Can provide WETH when swap hasn't been done yet /// @notice Cannot provide tokens if fromToken balance will exceed contract's toToken balance /// @notice User's deposit gets recorded in latest epoch, and the swapId recording his deposit is then returned - function provide() public payable returns (uint256) { - swaps[swapId].balances[msg.sender] += msg.value; + /// @param _deposit amount of WETH the user deposited + function provide(uint256 _deposit) public payable returns (uint256 _swapId) { + swaps[swapId].balances[msg.sender] += _deposit; + + WETH.transferFrom(msg.sender, address(this), _deposit); emit Provide(msg.sender, msg.value, swapId); return swapId; @@ -47,17 +50,17 @@ contract SwapperV3 is ISwapperV3 { /// @notice Initiates swap by sealing off further deposits and allowing withdrawals of toTokens, for that swapId's epoch /// @notice Sends any deposited fromTokens to the owner of the contract who provided initial liquidity - function swap() public { + function swap() public virtual override(ISwapperV3, Keeper) { address[] memory _path = new address[](2); _path[0] = address(WETH); _path[1] = address(DAI); (uint256 _reserve0, uint256 _reserve1,) = pair.getReserves(); uint256 _amountOutMin; - uint256 _amountOut = DAI.balanceOf(address(this)); + uint256 _amountOut; uint256 _amountIn = address(this).balance; swaps[swapId].swapped = true; - swapId++; + ++swapId; if (address(WETH) == pair.token0()) { _amountOutMin = router.getAmountOut(address(this).balance, _reserve0, _reserve1); @@ -65,6 +68,10 @@ contract SwapperV3 is ISwapperV3 { _amountOutMin = router.getAmountOut(address(this).balance, _reserve1, _reserve0); } + if (WETH.balanceOf(address(this)) != 0) { + WETH.withdraw(WETH.balanceOf(address(this))); + } + // 1% slippage to prevent frontrunning _amountOut = router.swapExactETHForTokens{value: address(this).balance}( _amountOutMin * 99 / 100, _path, address(this), block.timestamp @@ -95,27 +102,18 @@ contract SwapperV3 is ISwapperV3 { DAI.safeTransfer(msg.sender, balance); } else { _refunded = true; - payable(msg.sender).transfer(balance); - } - emit Withdraw(msg.sender, balance, _swapId, _refunded); - } + (bool _success,) = payable(msg.sender).call{value: balance}(''); - function work() external validateAndPayKeeper(msg.sender) { - if (!workable()) { - revert JobNotReady(); + if (!_success) { + revert RefundFailed(); + } } - lastWorked = block.timestamp; - - swap(); + emit Withdraw(msg.sender, balance, _swapId, _refunded); } - function workable() public view returns (bool) { - if (block.timestamp >= lastWorked + 600) { - return true; - } - - return false; + receive() external payable { + provide(); } } diff --git a/solidity/interfaces/IKeep3rV2.sol b/solidity/interfaces/Swapper/IKeep3rV2.sol similarity index 100% rename from solidity/interfaces/IKeep3rV2.sol rename to solidity/interfaces/Swapper/IKeep3rV2.sol diff --git a/solidity/interfaces/ISwapper.sol b/solidity/interfaces/Swapper/ISwapper.sol similarity index 100% rename from solidity/interfaces/ISwapper.sol rename to solidity/interfaces/Swapper/ISwapper.sol diff --git a/solidity/interfaces/ISwapperV2.sol b/solidity/interfaces/Swapper/ISwapperV2.sol similarity index 100% rename from solidity/interfaces/ISwapperV2.sol rename to solidity/interfaces/Swapper/ISwapperV2.sol diff --git a/solidity/interfaces/ISwapperV3.sol b/solidity/interfaces/Swapper/ISwapperV3.sol similarity index 93% rename from solidity/interfaces/ISwapperV3.sol rename to solidity/interfaces/Swapper/ISwapperV3.sol index 4a00b158..7f4892aa 100644 --- a/solidity/interfaces/ISwapperV3.sol +++ b/solidity/interfaces/Swapper/ISwapperV3.sol @@ -9,8 +9,7 @@ interface ISwapperV3 { function withdraw(uint256 _swapId) external; error ZeroBalance(); - error KeeperNotValid(); - error JobNotReady(); + error RefundFailed(); event Provide(address _depositor, uint256 _amount, uint256 _swapId); event Swapped(uint256 _swapId); diff --git a/solidity/interfaces/Swapper/IWETH.sol b/solidity/interfaces/Swapper/IWETH.sol new file mode 100644 index 00000000..f0f2ff3b --- /dev/null +++ b/solidity/interfaces/Swapper/IWETH.sol @@ -0,0 +1,7 @@ +pragma solidity ^0.8.19; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +interface IWETH is IERC20 { + function withdraw(uint256 wad) external; +} diff --git a/solidity/test/unit/Swapper.t.sol b/solidity/test/unit/Swapper/Swapper.t.sol similarity index 98% rename from solidity/test/unit/Swapper.t.sol rename to solidity/test/unit/Swapper/Swapper.t.sol index 64f94f8f..63760972 100644 --- a/solidity/test/unit/Swapper.t.sol +++ b/solidity/test/unit/Swapper/Swapper.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {Test, console2} from 'forge-std/Test.sol'; -import {Swapper} from '../../contracts/Swapper.sol'; +import {Swapper} from '../../../contracts/Swapper/Swapper.sol'; import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; error InsufficientLiquidity(); diff --git a/solidity/test/unit/SwapperV2.t.sol b/solidity/test/unit/Swapper/SwapperV2.t.sol similarity index 98% rename from solidity/test/unit/SwapperV2.t.sol rename to solidity/test/unit/Swapper/SwapperV2.t.sol index 2251271e..eb1a6ccc 100644 --- a/solidity/test/unit/SwapperV2.t.sol +++ b/solidity/test/unit/Swapper/SwapperV2.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {Test, console2} from 'forge-std/Test.sol'; -import {SwapperV2} from '../../contracts/SwapperV2.sol'; +import {SwapperV2} from '../../../contracts/Swapper/SwapperV2.sol'; import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; error InsufficientLiquidity(); diff --git a/solidity/test/unit/SwapperV3.t.sol b/solidity/test/unit/Swapper/SwapperV3.t.sol similarity index 96% rename from solidity/test/unit/SwapperV3.t.sol rename to solidity/test/unit/Swapper/SwapperV3.t.sol index e842e4ec..68687efa 100644 --- a/solidity/test/unit/SwapperV3.t.sol +++ b/solidity/test/unit/Swapper/SwapperV3.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.19; import {Test, console2} from 'forge-std/Test.sol'; -import {SwapperV3} from '../../contracts/SwapperV3.sol'; +import {SwapperV3} from '../../../contracts/Swapper/SwapperV3.sol'; import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import {IKeep3rV2} from '../../interfaces/IKeep3rV2.sol'; +import {IKeep3rV2} from '../../../interfaces/Swapper/IKeep3rV2.sol'; error InsufficientLiquidity(); error ZeroBalance();