-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ swapper: swap
ETH
for EXA
on velodrome
Co-authored-by: Santiago Sanchez Avalos <santisanchezavalos@gmail.com>
- Loading branch information
1 parent
1189ce5
commit f5eadf5
Showing
4 changed files
with
149 additions
and
1 deletion.
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,5 @@ | ||
--- | ||
"@exactly/protocol": patch | ||
--- | ||
|
||
✨ swapper: swap `ETH` for `EXA` on velodrome |
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
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,57 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.17; | ||
|
||
import { WETH, ERC20 } from "solmate/src/tokens/WETH.sol"; | ||
import { SafeTransferLib } from "solmate/src/utils/SafeTransferLib.sol"; | ||
|
||
contract Swapper { | ||
using SafeTransferLib for address payable; | ||
using SafeTransferLib for WETH; | ||
|
||
/// @notice The EXA asset. | ||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable | ||
ERC20 public immutable exa; | ||
/// @notice The WETH asset. | ||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable | ||
WETH public immutable weth; | ||
/// @notice The liquidity pool. | ||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable | ||
IPool public immutable pool; | ||
|
||
/// @custom:oz-upgrades-unsafe-allow constructor | ||
constructor(ERC20 exa_, WETH weth_, IPool pool_) { | ||
exa = exa_; | ||
weth = weth_; | ||
pool = pool_; | ||
} | ||
|
||
/// @notice Swaps `msg.value` ETH for EXA and sends it to `account`. | ||
/// @param account The account to send the EXA to. | ||
/// @param minEXA The minimum amount of EXA to receive. | ||
/// @param keepETH The amount of ETH to send to `account` (ex: for gas). | ||
function swap(address payable account, uint256 minEXA, uint256 keepETH) external payable { | ||
if (keepETH > msg.value) return account.safeTransferETH(msg.value); | ||
|
||
uint256 inETH = msg.value - keepETH; | ||
uint256 outEXA = pool.getAmountOut(inETH, weth); | ||
if (outEXA < minEXA) return account.safeTransferETH(msg.value); | ||
|
||
weth.deposit{ value: inETH }(); | ||
weth.safeTransfer(address(pool), inETH); | ||
|
||
(uint256 amount0Out, uint256 amount1Out) = address(exa) < address(weth) | ||
? (outEXA, uint256(0)) | ||
: (uint256(0), outEXA); | ||
try pool.swap(amount0Out, amount1Out, account, "") { | ||
account.safeTransferETH(keepETH); | ||
} catch { | ||
account.safeTransferETH(msg.value); | ||
} | ||
} | ||
} | ||
|
||
interface IPool { | ||
function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; | ||
|
||
function getAmountOut(uint256 amountIn, WETH tokenIn) external view returns (uint256); | ||
} |
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,81 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.17; | ||
|
||
import { ForkTest } from "./Fork.t.sol"; | ||
import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol"; | ||
import { Swapper, IPool, ERC20, WETH } from "../../contracts/periphery/Swapper.sol"; | ||
|
||
contract SwapperTest is ForkTest { | ||
using FixedPointMathLib for uint256; | ||
|
||
ERC20 internal exa; | ||
WETH internal weth; | ||
IPool internal pool; | ||
Swapper internal swapper; | ||
|
||
function setUp() external { | ||
vm.createSelectFork(vm.envString("OPTIMISM_NODE"), 107_385_046); | ||
|
||
exa = ERC20(deployment("EXA")); | ||
weth = WETH(payable(deployment("WETH"))); | ||
pool = IPool(deployment("EXAPool")); | ||
swapper = new Swapper(exa, weth, pool); | ||
|
||
deal(address(weth), address(this), 500 ether); | ||
} | ||
|
||
function testSwapBasic() external _checkBalance { | ||
uint256 balanceETH = address(this).balance; | ||
uint256 amountEXA = pool.getAmountOut(1 ether, weth); | ||
swapper.swap{ value: 1 ether }(payable(address(this)), 0, 0); | ||
|
||
assertEq(address(this).balance, balanceETH - 1 ether, "eth spent"); | ||
assertEq(exa.balanceOf(address(this)), amountEXA, "exa received"); | ||
} | ||
|
||
function testSwapWithKeepAmount() external _checkBalance { | ||
uint256 balanceETH = address(this).balance; | ||
uint256 amountEXA = pool.getAmountOut(0.9 ether, weth); | ||
swapper.swap{ value: 1 ether }(payable(address(this)), 0, 0.1 ether); | ||
|
||
assertEq(address(this).balance, balanceETH - 0.9 ether, "eth spent"); | ||
assertEq(exa.balanceOf(address(this)), amountEXA, "exa received"); | ||
} | ||
|
||
function testSwapWithKeepEqualToValue() external _checkBalance { | ||
uint256 balanceETH = address(this).balance; | ||
swapper.swap{ value: 1 ether }(payable(address(this)), 0, 1 ether); | ||
|
||
assertEq(address(this).balance, balanceETH, "eth spent"); | ||
assertEq(exa.balanceOf(address(this)), 0, "exa received"); | ||
} | ||
|
||
function testSwapWithKeepHigherThanValue() external _checkBalance { | ||
uint256 balanceETH = address(this).balance; | ||
swapper.swap{ value: 1 ether }(payable(address(this)), 0, 2 ether); | ||
|
||
assertEq(address(this).balance, balanceETH, "eth spent"); | ||
assertEq(exa.balanceOf(address(this)), 0, "exa received"); | ||
} | ||
|
||
function testSwapWithInaccurateSlippageSendsETHToAccount() external _checkBalance { | ||
uint256 balanceETH = address(this).balance; | ||
uint256 amountEXA = pool.getAmountOut(1 ether, weth); | ||
|
||
swapper.swap{ value: 1 ether }(payable(address(this)), amountEXA * 5, 0); | ||
assertEq(address(this).balance, balanceETH, "eth spent"); | ||
assertEq(exa.balanceOf(address(this)), 0, "exa received"); | ||
|
||
swapper.swap{ value: 1 ether }(payable(address(this)), amountEXA - 10e18, 0); | ||
assertEq(address(this).balance, balanceETH - 1 ether, "eth spent"); | ||
assertEq(exa.balanceOf(address(this)), amountEXA, "exa received"); | ||
} | ||
|
||
modifier _checkBalance() { | ||
_; | ||
assertEq(address(swapper).balance, 0); | ||
} | ||
|
||
// solhint-disable-next-line no-empty-blocks | ||
receive() external payable {} | ||
} |