Skip to content

Commit 95b8206

Browse files
authored
Merge pull request #30 from euler-xyz/update-periphery
feat: swapExactIn() & swapExactOut() implementation in EulerSwapPeriphery
2 parents 6292995 + ae9ad30 commit 95b8206

File tree

6 files changed

+154
-10
lines changed

6 files changed

+154
-10
lines changed

src/EulerSwap.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ contract EulerSwap is IEulerSwap, EVCUtil {
151151
return (reserve0, reserve1, status);
152152
}
153153

154+
/// @notice Returns the address of the Ethereum Vault Connector (EVC) used by this contract.
155+
/// @return The address of the EVC contract.
156+
function EVC() external view override(EVCUtil, IEulerSwap) returns (address) {
157+
return address(evc);
158+
}
159+
154160
/// @inheritdoc IEulerSwap
155161
function activate() public {
156162
require(status != 2, Locked());

src/EulerSwapPeriphery.sol

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,40 @@ pragma solidity ^0.8.27;
33

44
import {IEVC} from "evc/interfaces/IEthereumVaultConnector.sol";
55
import {IEVault} from "evk/EVault/IEVault.sol";
6-
import {IEulerSwap} from "./interfaces/IEulerSwap.sol";
76
import {IEulerSwapPeriphery} from "./interfaces/IEulerSwapPeriphery.sol";
7+
import {IERC20, IEulerSwap, SafeERC20} from "./EulerSwap.sol";
88

99
contract EulerSwapPeriphery is IEulerSwapPeriphery {
10-
address private immutable evc;
11-
12-
constructor(address evc_) {
13-
evc = evc_;
14-
}
10+
using SafeERC20 for IERC20;
1511

1612
error UnsupportedPair();
1713
error OperatorNotInstalled();
1814
error InsufficientReserves();
1915
error InsufficientCash();
16+
error AmountOutLessThanMin();
17+
error AmountInMoreThanMax();
18+
19+
/// @inheritdoc IEulerSwapPeriphery
20+
function swapExactIn(address eulerSwap, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin)
21+
external
22+
{
23+
uint256 amountOut = computeQuote(IEulerSwap(eulerSwap), tokenIn, tokenOut, amountIn, true);
24+
25+
require(amountOut >= amountOutMin, AmountOutLessThanMin());
26+
27+
swap(eulerSwap, tokenIn, tokenOut, amountIn, amountOut);
28+
}
29+
30+
/// @inheritdoc IEulerSwapPeriphery
31+
function swapExactOut(address eulerSwap, address tokenIn, address tokenOut, uint256 amountOut, uint256 amountInMax)
32+
external
33+
{
34+
uint256 amountIn = computeQuote(IEulerSwap(eulerSwap), tokenIn, tokenOut, amountOut, false);
35+
36+
require(amountIn <= amountInMax, AmountInMoreThanMax());
37+
38+
swap(eulerSwap, tokenIn, tokenOut, amountIn, amountOut);
39+
}
2040

2141
/// @inheritdoc IEulerSwapPeriphery
2242
function quoteExactInput(address eulerSwap, address tokenIn, address tokenOut, uint256 amountIn)
@@ -36,15 +56,41 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
3656
return computeQuote(IEulerSwap(eulerSwap), tokenIn, tokenOut, amountOut, false);
3757
}
3858

39-
/// @dev High-level quoting function. It handles fees and performs
40-
/// state validation, for example that there is sufficient cash available.
59+
/// @dev Internal function to execute a token swap through EulerSwap
60+
/// @param eulerSwap The EulerSwap contract address to execute the swap through
61+
/// @param tokenIn The address of the input token being swapped
62+
/// @param tokenOut The address of the output token being received
63+
/// @param amountIn The amount of input tokens to swap
64+
/// @param amountOut The amount of output tokens to receive
65+
function swap(address eulerSwap, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut) internal {
66+
IERC20(tokenIn).safeTransferFrom(msg.sender, eulerSwap, amountIn);
67+
68+
bool isAsset0In = tokenIn < tokenOut;
69+
(isAsset0In)
70+
? IEulerSwap(eulerSwap).swap(0, amountOut, msg.sender, "")
71+
: IEulerSwap(eulerSwap).swap(amountOut, 0, msg.sender, "");
72+
}
73+
74+
/// @dev Computes the quote for a swap by applying fees and validating state conditions
75+
/// @param eulerSwap The EulerSwap contract to quote from
76+
/// @param tokenIn The input token address
77+
/// @param tokenOut The output token address
78+
/// @param amount The amount to quote (input amount if exactIn=true, output amount if exactIn=false)
79+
/// @param exactIn True if quoting for exact input amount, false if quoting for exact output amount
80+
/// @return The quoted amount (output amount if exactIn=true, input amount if exactIn=false)
81+
/// @dev Validates:
82+
/// - EulerSwap operator is installed
83+
/// - Token pair is supported
84+
/// - Sufficient reserves exist
85+
/// - Sufficient cash is available
4186
function computeQuote(IEulerSwap eulerSwap, address tokenIn, address tokenOut, uint256 amount, bool exactIn)
4287
internal
4388
view
4489
returns (uint256)
4590
{
4691
require(
47-
IEVC(evc).isAccountOperatorAuthorized(eulerSwap.myAccount(), address(eulerSwap)), OperatorNotInstalled()
92+
IEVC(eulerSwap.EVC()).isAccountOperatorAuthorized(eulerSwap.myAccount(), address(eulerSwap)),
93+
OperatorNotInstalled()
4894
);
4995

5096
uint256 feeMultiplier = eulerSwap.feeMultiplier();
@@ -83,9 +129,17 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
83129
return quote;
84130
}
85131

132+
/// @notice Binary searches for the output amount along a swap curve given input parameters
86133
/// @dev General-purpose routine for binary searching swapping curves.
87134
/// Although some curves may have more efficient closed-form solutions,
88135
/// this works with any monotonic curve.
136+
/// @param eulerSwap The EulerSwap contract to search the curve for
137+
/// @param reserve0 Current reserve of asset0 in the pool
138+
/// @param reserve1 Current reserve of asset1 in the pool
139+
/// @param amount The input or output amount depending on exactIn
140+
/// @param exactIn True if amount is input amount, false if amount is output amount
141+
/// @param asset0IsInput True if asset0 is being input, false if asset1 is being input
142+
/// @return output The calculated output amount from the binary search
89143
function binarySearch(
90144
IEulerSwap eulerSwap,
91145
uint112 reserve0,

src/interfaces/IEulerSwap.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ interface IEulerSwap {
3434
/// of the swapping curve).
3535
function verify(uint256 newReserve0, uint256 newReserve1) external view returns (bool);
3636

37+
/// @notice Returns the address of the Ethereum Vault Connector (EVC) used by this contract.
38+
/// @return The address of the EVC contract.
39+
function EVC() external view returns (address);
40+
3741
// EulerSwap Accessors
3842

3943
function curve() external view returns (bytes32);

src/interfaces/IEulerSwapPeriphery.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
pragma solidity >=0.8.0;
33

44
interface IEulerSwapPeriphery {
5+
/// @notice Swap `amountIn` of `tokenIn` for `tokenOut`, with at least `amountOutMin` received.
6+
function swapExactIn(address eulerSwap, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin)
7+
external;
8+
9+
/// @notice Swap `amountOut` of `tokenOut` for `tokenIn`, with at most `amountInMax` paid.
10+
function swapExactOut(address eulerSwap, address tokenIn, address tokenOut, uint256 amountOut, uint256 amountInMax)
11+
external;
12+
513
/// @notice How much `tokenOut` can I get for `amountIn` of `tokenIn`?
614
function quoteExactInput(address eulerSwap, address tokenIn, address tokenOut, uint256 amountIn)
715
external

test/EulerSwapPeriphery.t.sol

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity ^0.8.24;
3+
4+
import {EulerSwapTestBase, EulerSwap, EulerSwapPeriphery} from "./EulerSwapTestBase.t.sol";
5+
6+
contract EulerSwapPeripheryTest is EulerSwapTestBase {
7+
EulerSwap public eulerSwap;
8+
9+
function setUp() public virtual override {
10+
super.setUp();
11+
12+
eulerSwap = createEulerSwap(50e18, 50e18, 0, 1e18, 1e18, 0.4e18, 0.85e18);
13+
}
14+
15+
function test_SwapExactIn() public {
16+
uint256 amountIn = 1e18;
17+
uint256 amountOut =
18+
periphery.quoteExactInput(address(eulerSwap), address(assetTST), address(assetTST2), amountIn);
19+
20+
assetTST.mint(anyone, amountIn);
21+
22+
vm.startPrank(anyone);
23+
assetTST.approve(address(periphery), amountIn);
24+
periphery.swapExactIn(address(eulerSwap), address(assetTST), address(assetTST2), amountIn, amountOut);
25+
vm.stopPrank();
26+
27+
assertEq(assetTST2.balanceOf(anyone), amountOut);
28+
}
29+
30+
function test_SwapExactIn_AmountOutLessThanMin() public {
31+
uint256 amountIn = 1e18;
32+
uint256 amountOut =
33+
periphery.quoteExactInput(address(eulerSwap), address(assetTST), address(assetTST2), amountIn);
34+
35+
assetTST.mint(anyone, amountIn);
36+
37+
vm.startPrank(anyone);
38+
assetTST.approve(address(periphery), amountIn);
39+
vm.expectRevert(EulerSwapPeriphery.AmountOutLessThanMin.selector);
40+
periphery.swapExactIn(address(eulerSwap), address(assetTST), address(assetTST2), amountIn, amountOut + 1);
41+
vm.stopPrank();
42+
}
43+
44+
function test_SwapExactOut() public {
45+
uint256 amountOut = 1e18;
46+
uint256 amountIn =
47+
periphery.quoteExactOutput(address(eulerSwap), address(assetTST), address(assetTST2), amountOut);
48+
49+
assetTST.mint(anyone, amountIn);
50+
51+
vm.startPrank(anyone);
52+
assetTST.approve(address(periphery), amountIn);
53+
periphery.swapExactOut(address(eulerSwap), address(assetTST), address(assetTST2), amountOut, amountIn);
54+
vm.stopPrank();
55+
56+
assertEq(assetTST2.balanceOf(anyone), amountOut);
57+
}
58+
59+
function test_SwapExactOut_AmountInMoreThanMax() public {
60+
uint256 amountOut = 1e18;
61+
uint256 amountIn =
62+
periphery.quoteExactOutput(address(eulerSwap), address(assetTST), address(assetTST2), amountOut);
63+
64+
assetTST.mint(anyone, amountIn);
65+
66+
vm.startPrank(anyone);
67+
assetTST.approve(address(periphery), amountIn);
68+
vm.expectRevert(EulerSwapPeriphery.AmountInMoreThanMax.selector);
69+
periphery.swapExactOut(address(eulerSwap), address(assetTST), address(assetTST2), amountOut * 2, amountIn);
70+
vm.stopPrank();
71+
}
72+
}

test/EulerSwapTestBase.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ contract EulerSwapTestBase is EVaultTestBase {
2525
function setUp() public virtual override {
2626
super.setUp();
2727

28-
periphery = new EulerSwapPeriphery(address(evc));
28+
periphery = new EulerSwapPeriphery();
2929

3030
// Vault config
3131

0 commit comments

Comments
 (0)