diff --git a/AvaxInstantSwap.sol b/AvaxInstantSwap.sol index 2c4e9e8..e100050 100644 --- a/AvaxInstantSwap.sol +++ b/AvaxInstantSwap.sol @@ -2,8 +2,6 @@ pragma solidity =0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; @@ -62,8 +60,8 @@ interface IQuoter { contract AvaxInstantSwap is Ownable, ReentrancyGuard { using SafeERC20 for IERC20; - ILBRouter public lbRouter; - IQuoter public lbQuoter; + ILBRouter public immutable lbRouter; + IQuoter public immutable lbQuoter; address public immutable wethToken; address public immutable usdc; address public executor; @@ -73,19 +71,22 @@ contract AvaxInstantSwap is Ownable, ReentrancyGuard { address indexed receiver, address indexed token, uint256 amountIn, - uint256 amountOut, uint256 time ); event ExecutorUpdated(address indexed oldExecutor, address indexed newExecutor); + modifier onlyExecutor() { + require(msg.sender == executor, "not executor"); + _; + } + constructor( address _lbRouter, address _lbQuoter, address _usdc, - address _executor, - address _owner - ) Ownable(_owner) { + address _executor + ) Ownable(msg.sender) { lbRouter = ILBRouter(_lbRouter); lbQuoter = IQuoter(_lbQuoter); wethToken = address(lbRouter.getWNATIVE()); @@ -95,7 +96,7 @@ contract AvaxInstantSwap is Ownable, ReentrancyGuard { // To get the estimated path for making a swap function getQuote( - address[] calldata _path, + address[] memory _path, uint256 _amountIn ) public view returns (ILBRouter.Path memory) { // Use the quoter to find the best route for the swap @@ -121,36 +122,38 @@ contract AvaxInstantSwap is Ownable, ReentrancyGuard { address _tokenB, uint256 _amountIn, uint256 _minAmountOut, - address[] calldata _path, + address[] memory _path, address _to, bool _unwrappETH - ) public nonReentrant { - require(_to != address(0), "can not send address(0)"); - require(msg.sender == executor, "not executor"); - + ) public nonReentrant onlyExecutor { if(_tokenB == usdc) { + emit SwapFromUSDC(_to, _tokenB, _amountIn, block.timestamp); IERC20(usdc).transfer(_to, _amountIn); - emit SwapFromUSDC(_to, _tokenB, _amountIn, _amountIn, block.timestamp); + return; + } + if (IERC20(usdc).allowance(address(this), address(lbRouter)) < _amountIn) { + IERC20(usdc).approve(address(lbRouter), _amountIn); + } + ILBRouter.Path memory pathQuote = getQuote(_path, _amountIn); + // Make LBRouter swap + if (_unwrappETH) { + lbRouter.swapExactTokensForNATIVESupportingFeeOnTransferTokens( + _amountIn, + _minAmountOut, // Amount out min + pathQuote, + payable(_to), + block.timestamp + 1 hours + ); } else { - checkAndApproveAll(usdc, address(lbRouter), _amountIn); - - ILBRouter.Path memory pathQuote = getQuote(_path, _amountIn); - uint256 output = lbRouter - .swapExactTokensForTokensSupportingFeeOnTransferTokens( - _amountIn, - _minAmountOut, // Amount out min - pathQuote, - _unwrappETH ? address(this) : _to, - block.timestamp + 10 minutes - ); - - if (_unwrappETH) { - IWNATIVE(wethToken).withdraw(output); - payable(_to).transfer(output); - } - - emit SwapFromUSDC(_to, _tokenB, _amountIn, output, block.timestamp); + lbRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( + _amountIn, + _minAmountOut, // Amount out min + pathQuote, + _to, + block.timestamp + 1 hours + ); } + emit SwapFromUSDC(_to, _tokenB, _amountIn, block.timestamp); } function setExecutor(address _newExecutor) external onlyOwner { @@ -165,21 +168,20 @@ contract AvaxInstantSwap is Ownable, ReentrancyGuard { ) internal { if (IERC20(_token).allowance(address(this), _target) < _amountToCheck) { IERC20(_token).forceApprove(_target, 0); - IERC20(_token).forceApprove(_target, _amountToCheck); + IERC20(_token).forceApprove(_target, ~uint256(0)); } } - - function recoverStuckETH(address payable _beneficiary) public onlyOwner { - _beneficiary.transfer(address(this).balance); + function recoverStuckETH() public onlyOwner { + payable(owner()).transfer(address(this).balance); } function recoverStuckTokens(address _token) external onlyOwner { uint256 amount = IERC20(_token).balanceOf(address(this)); - IERC20(_token).safeTransfer(owner(), amount); + IERC20(_token).transfer(owner(), amount); } receive() external payable {} fallback() external payable {} -} +} \ No newline at end of file diff --git a/OfficialInstantSwap.sol b/OfficialInstantSwap.sol index 92ad991..6650265 100644 --- a/OfficialInstantSwap.sol +++ b/OfficialInstantSwap.sol @@ -2,8 +2,6 @@ pragma solidity =0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; @@ -118,10 +116,10 @@ contract OfficialInstantSwap is Ownable, ReentrancyGuard { IUniswapV2Router02 public v2Router; IV3SwapRouter public v3Router; - address public immutable USDC; - address public immutable weth; + address public USDC; + address public weth; address public executor; - + error FailedCall(); modifier onlyExecutor() { require(msg.sender == executor, "not executor"); _; @@ -133,7 +131,6 @@ contract OfficialInstantSwap is Ownable, ReentrancyGuard { address indexed receiver, address indexed token, uint256 amountIn, - uint256 amountOut, uint256 time ); @@ -143,9 +140,8 @@ contract OfficialInstantSwap is Ownable, ReentrancyGuard { address _v2Router, address _executor, address _usdc, - address _weth, - address _owner - ) Ownable(_owner) { + address _weth + ) Ownable(msg.sender) { v3Router = IV3SwapRouter(_v3Router); v2Router = IUniswapV2Router02(_v2Router); executor= _executor; @@ -157,80 +153,49 @@ contract OfficialInstantSwap is Ownable, ReentrancyGuard { address _outputToken, uint256 _amountIn, uint256 _minAmountOut, + uint256 _minAmountOutV2Swap, bool _useV2, - address[] calldata _pathV2, - bytes calldata _pathV3, + address[] memory _pathV2, + bytes memory _pathV3, address to, bool unwrapETH ) public nonReentrant onlyExecutor { - require(to != address(0), "can not send address(0)"); // USDC -> Token - uint256 outputAmount; - if(_useV2) { - outputAmount = v2Swap(_pathV2, _amountIn, _minAmountOut, to, unwrapETH); - } else { - outputAmount = v3Swap(USDC, _pathV3, _amountIn, _minAmountOut, to, unwrapETH); + if(_outputToken == USDC) { + IERC20(USDC).transfer(to, _amountIn); + emit SwapFromUSDC(to, USDC, _amountIn, block.timestamp); + return; } - emit SwapFromUSDC(to, _outputToken, _amountIn, outputAmount, block.timestamp); - } - function directSendUSDC(address to, uint256 amount) external onlyExecutor { - require(to != address(0), "can not send address(0)"); - IERC20(USDC).transfer(to, amount); - emit SwapFromUSDC(to, USDC, amount,amount, block.timestamp); - } + // 1. Swap USDC to ETH (and/or final token) on v3 + IERC20(USDC).approve(address(v3Router), _amountIn); + IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter.ExactInputParams( + _pathV3, _useV2 || unwrapETH? address(this) : to, _amountIn, _minAmountOut + ); - function checkAndApproveAll(address _token, address _target, uint256 _amountToCheck) internal { - if (IERC20(_token).allowance(address(this), _target) < _amountToCheck) { - IERC20(_token).forceApprove(_target, 0); - IERC20(_token).forceApprove(_target, _amountToCheck); - } - } + uint256 wethOrFinalTokenOut = v3Router.exactInput(params); - function v2Swap( - address[] calldata _path, - uint256 _amountIn, - uint256 _minAmountOut, // Slippage in base of 1000 meaning 10 is 1% and 1 is 0.1% where 1000 is 1 - address to, - bool unwrapETH - ) internal returns (uint256) { - address tokenOut = _path[_path.length - 1]; - checkAndApproveAll(_path[0], address(v2Router), _amountIn); - uint256 initial = IERC20(tokenOut).balanceOf(to); - v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - _amountIn, - _minAmountOut, - _path, - unwrapETH ? address(this) : to, - block.timestamp + 10 minutes - ); - uint256 finalAmount = IERC20(tokenOut).balanceOf(to); - if (unwrapETH) { // Get ETH at the end - uint256 wethBalance = IERC20(weth).balanceOf(address(this)); - IWETH(weth).withdraw(wethBalance); - payable(to).transfer(address(this).balance); + if(_useV2) { + IERC20(weth).approve(address(v2Router), wethOrFinalTokenOut); + v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + wethOrFinalTokenOut, + _minAmountOutV2Swap, + _pathV2, + unwrapETH? address(this) : to, + block.timestamp + 1 hours + ); } - return finalAmount - initial; - } - function v3Swap( - address _tokenIn, - bytes calldata _path, - uint256 _amountIn, - uint256 _minAmountOut, - address to, - bool unwrapETH - ) internal returns (uint256 amountOutput) { - checkAndApproveAll(_tokenIn, address(v3Router), _amountIn); - IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter.ExactInputParams( - _path, unwrapETH ? address(this) : to, _amountIn, _minAmountOut - ); - amountOutput = v3Router.exactInput( params ); - if (unwrapETH) { // Get ETH at the end - uint256 wethBalance = IERC20(weth).balanceOf(address(this)); - IWETH(weth).withdraw(wethBalance); - payable(to).transfer(address(this).balance); + if(unwrapETH) { + uint256 wethBalance = IERC20(weth).balanceOf(address(this)); + IWETH(weth).withdraw(wethBalance); + // payable(receiverData.userReceiver).transfer(address(this).balance); + (bool success, ) = to.call{value: address(this).balance}(""); + if(!success) { + revert FailedCall(); + } } + emit SwapFromUSDC(to, _outputToken, _amountIn, block.timestamp); } function setExecutor(address _newExecutor) external onlyOwner { @@ -238,17 +203,13 @@ contract OfficialInstantSwap is Ownable, ReentrancyGuard { executor = _newExecutor; } - function recoverStuckETH(address payable _beneficiary) public onlyOwner { - _beneficiary.transfer(address(this).balance); - } - function recoverStuckTokens(address _token) external onlyOwner { uint256 amount = IERC20(_token).balanceOf(address(this)); - IERC20(_token).safeTransfer(owner(), amount); + IERC20(_token).transfer(owner(), amount); } receive() external payable {} fallback() external payable {} -} +} \ No newline at end of file diff --git a/same-swap-bsc.sol b/same-swap-bsc.sol new file mode 100644 index 0000000..269d2f1 --- /dev/null +++ b/same-swap-bsc.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.20; + +import '@openzeppelin/contracts/access/Ownable2Step.sol'; +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import '@openzeppelin/contracts/utils/ReentrancyGuard.sol'; +import './libs/BytesLib.sol'; + +interface IV3SwapRouter { + struct ExactInputParams { + bytes path; + address recipient; + uint256 deadline; + uint256 amountIn; + uint256 amountOutMinimum; + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); +} + +interface IV2SwapRouter { + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external payable; + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; +} + +interface IWETH is IERC20 { + function deposit() external payable; + + function withdraw(uint amount) external; +} + +contract SameChainSwapBSC is Ownable2Step, ReentrancyGuard { + using SafeERC20 for IERC20; + using BytesLib for bytes; + + mapping(address => bool) public feeTokens; + uint256 public platformFee; // Fee must be by 1000, so if you want 5% this will be 5000 + address public feeReceiver; + uint256 public constant feeBps = 1000; // 1000 is 1% so we can have many decimals + + IV2SwapRouter public v2Router; + IV3SwapRouter public v3Router; + address public wethToken; + + error FailedCall(); // Used when transfer function is failed. + //////////================= Events ==================================================== + event SwapExecuted(address indexed tokenIn, address indexed tokenOut, uint amountIn, uint amountOut); + event FeeReceiverSet(address indexed _oldReceiver, address indexed _newReceiver); + event Fee(address indexed user, uint256 amount, address indexed token); + + constructor( + uint256 _fee, + address _feeReceiver, + address _v3Router, + address _v2Router, + address _weth + ) Ownable(msg.sender) { + platformFee = _fee; + feeReceiver = _feeReceiver; + v3Router = IV3SwapRouter(_v3Router); + v2Router = IV2SwapRouter(_v2Router); + wethToken = _weth; + } + uint256 public constant MAX_PLATFORM_FEE = 2000; // 20% in basis points + uint256 public threshold = 0.01 * 10 ** 18; // 20% in basis points + function changeFeeData(uint256 _fee, address _feeReceiver, uint256 _threshold) external onlyOwner { + require(_fee <= MAX_PLATFORM_FEE, 'Platform fee exceeds the maximum limit'); + address oldReceiver = feeReceiver; + platformFee = _fee; + feeReceiver = _feeReceiver; + threshold = _threshold; + emit FeeReceiverSet(oldReceiver, _feeReceiver); + } + + function swapOnce( + address _tokenA, + address _tokenB, + bool _unwrappETH, + uint256 _amountIn, + uint256 _minAmountOutV2, + uint256 _minAmountOutV3, + uint8 _buyOneTwoOrThree, // 1 means buy only v2, 2 means buy v3 only, 3 means buy first v2 then v3, 4 means buy first v3 then v2 + address[] memory _pathV2, + bytes memory _pathV3, + bool isWethIn + ) public payable nonReentrant { + // ETH -> Token + if (!isWethIn && _tokenA == wethToken) { + require(msg.value > 0, 'invalid msg.value'); + IWETH(wethToken).deposit{value: msg.value}(); + } else { + require(msg.value == 0, 'invalid msg.value'); + uint256 beforeTransfer = IERC20(_tokenA).balanceOf(address(this)); + IERC20(_tokenA).safeTransferFrom(msg.sender, address(this), _amountIn); + uint256 afterTransfer = IERC20(_tokenA).balanceOf(address(this)); + _amountIn = afterTransfer - beforeTransfer; + } + + uint256 amountIn = (msg.value > 0 ? msg.value : _amountIn); + uint256 output; + if (_buyOneTwoOrThree == 1) { + uint256 feeAmount = (amountIn * platformFee) / (feeBps * 100); + if (_pathV2[0] != wethToken) { + // WETH => Token + address[] memory path = new address[](2); + path[0] = _pathV2[0]; + path[1] = wethToken; + v2Swap(path, feeAmount, 0); + } + output = v2Swap(_pathV2, amountIn - feeAmount, _minAmountOutV2); + } else if (_buyOneTwoOrThree == 2) { + uint256 feeAmount = (amountIn * platformFee) / (feeBps * 100); + if (_pathV3.toAddress(0) == wethToken) { + // WETH => output Token + output = v3Swap(_tokenA, _pathV3, amountIn - feeAmount, _minAmountOutV3); + } else if (_pathV3.toAddress(23) == wethToken && _tokenB == wethToken) { + // Token => WETH + output = v3Swap(_tokenA, _pathV3, amountIn, _minAmountOutV3); + feeAmount = (output * platformFee) / (feeBps * 100); + output = output - feeAmount; + } else { + // Token => WETH (stable coins) => Token + v3Swap(_tokenA, _pathV3.slice(0, 43), feeAmount, 0); + output = v3Swap(_tokenA, _pathV3, amountIn - feeAmount, _minAmountOutV3); + } + } else if (_buyOneTwoOrThree == 3) { + output = v2Swap(_pathV2, amountIn, _minAmountOutV2); + uint256 feeAmount = (output * platformFee) / (feeBps * 100); + output = v3Swap(_pathV2[_pathV2.length - 1], _pathV3, output - feeAmount, _minAmountOutV3); + } else if (_buyOneTwoOrThree == 4) { + output = v3Swap(_tokenA, _pathV3, amountIn, _minAmountOutV3); + uint256 feeAmount = (output * platformFee) / (feeBps * 100); + output = v2Swap(_pathV2, output - feeAmount, _minAmountOutV2); + } + + if (_unwrappETH) { + IWETH(wethToken).withdraw(output); + // payable(msg.sender).transfer(output); + (bool success, ) = msg.sender.call{value: output}(''); + if (!success) { + revert FailedCall(); + } + } else { + IERC20(_tokenB).safeTransfer(msg.sender, output); + } + if (IWETH(wethToken).balanceOf(address(this)) >= threshold) { + IWETH(wethToken).withdraw(IWETH(wethToken).balanceOf(address(this))); + payable(feeReceiver).transfer(address(this).balance); + } + emit SwapExecuted(_tokenA, _tokenB, _amountIn, output); + } + + function checkAndApproveAll(address _token, address _target, uint256 _amountToCheck) internal { + if (IERC20(_token).allowance(address(this), _target) < _amountToCheck) { + IERC20(_token).forceApprove(_target, 0); + IERC20(_token).forceApprove(_target, ~uint256(0)); + } + } + + function v2Swap( + address[] memory _path, + uint256 _amountIn, + uint256 _minAmountOut // Slippage in base of 1000 meaning 10 is 1% and 1 is 0.1% where 1000 is 1 + ) internal returns (uint256) { + address tokenOut = _path[_path.length - 1]; + checkAndApproveAll(_path[0], address(v2Router), _amountIn); + uint256 initial = IERC20(tokenOut).balanceOf(address(this)); + v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + _amountIn, + _minAmountOut, + _path, + address(this), + block.timestamp * 5 minutes + ); + uint256 finalAmount = IERC20(tokenOut).balanceOf(address(this)); + return finalAmount - initial; + } + + function v3Swap( + address _tokenIn, + bytes memory _path, + uint256 _amountIn, + uint256 _minAmountOut + ) internal returns (uint256 amountOutput) { + checkAndApproveAll(_tokenIn, address(v3Router), _amountIn); + IV3SwapRouter.ExactInputParams memory params = IV3SwapRouter.ExactInputParams( + _path, + address(this), + block.timestamp * 5 minutes, + _amountIn, + _minAmountOut + ); + amountOutput = v3Router.exactInput(params); + } + + function recoverStuckETH(address payable _beneficiary) public onlyOwner { + _beneficiary.transfer(address(this).balance); + } + + function recoverStuckTokens(address _token) external onlyOwner { + uint256 amount = IERC20(_token).balanceOf(address(this)); + IERC20(_token).safeTransfer(owner(), amount); + } + + receive() external payable {} + + fallback() external payable {} +}