Skip to content
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.27"
optimizer = true
optimizer_runs = 10000
gas_reports = ["*"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
16 changes: 0 additions & 16 deletions src/EulerSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,6 @@ import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"
import {EVCUtil} from "evc/utils/EVCUtil.sol";

contract EulerSwap is IEulerSwap, EVCUtil {
struct Params {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved them to interface file

address vault0;
address vault1;
address myAccount;
uint112 debtLimit0;
uint112 debtLimit1;
uint256 fee;
}

struct CurveParams {
uint256 priceX;
uint256 priceY;
uint256 concentrationX;
uint256 concentrationY;
}

bytes32 public constant curve = keccak256("EulerSwap v1");

address public immutable vault0;
Expand Down
109 changes: 109 additions & 0 deletions src/EulerSwapFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import {IEulerSwapFactory} from "./interfaces/IEulerSwapFactory.sol";
import {IEulerSwap, EulerSwap} from "./EulerSwap.sol";
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";

/// @title EulerSwapFactory contract
/// @custom:security-contact [email protected]
/// @author Euler Labs (https://www.eulerlabs.com/)
contract EulerSwapFactory is IEulerSwapFactory, Ownable {
/// @dev An array to store all pools addresses.
address[] public allPools;
/// @dev Mapping to store pool addresses
mapping(bytes32 poolKey => address pool) public getPool;

event PoolDeployed(
address indexed asset0,
address indexed asset1,
uint256 indexed feeMultiplier,
address swapAccount,
uint256 priceX,
uint256 priceY,
uint256 concentrationX,
uint256 concentrationY,
address pool
);

error InvalidQuery();

constructor() Ownable(msg.sender) {}

/// @notice Deploy EulerSwap pool.
function deployPool(DeployParams memory params) external returns (address) {
EulerSwap pool = new EulerSwap(
IEulerSwap.Params({
vault0: params.vault0,
vault1: params.vault1,
myAccount: params.holder,
debtLimit0: params.debtLimit0,
debtLimit1: params.debtLimit1,
fee: params.fee
}),
IEulerSwap.CurveParams({
priceX: params.priceX,
priceY: params.priceY,
concentrationX: params.concentrationX,
concentrationY: params.concentrationY
})
);

address poolAsset0 = pool.asset0();
address poolAsset1 = pool.asset1();
uint256 feeMultiplier = pool.feeMultiplier();

bytes32 poolKey = keccak256(
abi.encode(
poolAsset0,
poolAsset1,
feeMultiplier,
params.holder,
params.priceX,
params.priceY,
params.concentrationX,
params.concentrationY
)
);

getPool[poolKey] = address(pool);
allPools.push(address(pool));

emit PoolDeployed(
poolAsset0,
poolAsset1,
feeMultiplier,
params.holder,
params.priceX,
params.priceY,
params.concentrationX,
params.concentrationY,
address(pool)
);

return address(pool);
}

/// @notice Get the length of `allPools` array.
/// @return `allPools` length.
function allPoolsLength() external view returns (uint256) {
return allPools.length;
}

/// @notice Get a slice of the deployed pools array.
/// @param _start Start index of the slice.
/// @param _end End index of the slice.
/// @return An array containing the slice of the deployed pools.
function getAllPoolsListSlice(uint256 _start, uint256 _end) external view returns (address[] memory) {
uint256 length = allPools.length;
if (_end == type(uint256).max) _end = length;
if (_end < _start || _end > length) revert InvalidQuery();

address[] memory allPoolsList = new address[](_end - _start);
for (uint256 i; i < _end - _start; ++i) {
allPoolsList[i] = allPools[_start + i];
}

return allPoolsList;
}
}
16 changes: 16 additions & 0 deletions src/interfaces/IEulerSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
pragma solidity >=0.8.0;

interface IEulerSwap {
struct Params {
address vault0;
address vault1;
address myAccount;
uint112 debtLimit0;
uint112 debtLimit1;
uint256 fee;
}

struct CurveParams {
uint256 priceX;
uint256 priceY;
uint256 concentrationX;
uint256 concentrationY;
}

/// @notice Optimistically sends the requested amounts of tokens to the `to`
/// address, invokes `uniswapV2Call` callback on `to` (if `data` was provided),
/// and then verifies that a sufficient amount of tokens were transferred to
Expand Down
24 changes: 24 additions & 0 deletions src/interfaces/IEulerSwapFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;

interface IEulerSwapFactory {
struct DeployParams {
address vault0;
address vault1;
address holder;
uint256 fee;
uint256 priceX;
uint256 priceY;
uint256 concentrationX;
uint256 concentrationY;
uint112 debtLimit0;
uint112 debtLimit1;
}

function deployPool(DeployParams memory params) external returns (address);

function allPools(uint256 index) external view returns (address);
function getPool(bytes32 poolKey) external view returns (address);
function allPoolsLength() external view returns (uint256);
function getAllPoolsListSlice(uint256 start, uint256 end) external view returns (address[] memory);
}
79 changes: 79 additions & 0 deletions test/EulerSwapFactoryTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.24;

import {EulerSwapTestBase, EulerSwap} from "./EulerSwapTestBase.t.sol";
import {EulerSwapFactory, IEulerSwapFactory} from "../src/EulerSwapFactory.sol";

contract EulerSwapFactoryTest is EulerSwapTestBase {
EulerSwapFactory public eulerSwapFactory;

uint256 minFee = 0.0000000000001e18;

function setUp() public virtual override {
super.setUp();

vm.prank(creator);
eulerSwapFactory = new EulerSwapFactory();
}

function testDeployPool() public {
uint256 allPoolsLengthBefore = eulerSwapFactory.allPoolsLength();

vm.prank(creator);
EulerSwap eulerSwap = EulerSwap(
eulerSwapFactory.deployPool(
IEulerSwapFactory.DeployParams(
address(eTST), address(eTST2), holder, 0, 1e18, 1e18, 0.4e18, 0.85e18, 50e18, 50e18
)
)
);

uint256 allPoolsLengthAfter = eulerSwapFactory.allPoolsLength();
bytes32 poolKey = keccak256(
abi.encode(
eulerSwap.asset0(),
eulerSwap.asset1(),
eulerSwap.feeMultiplier(),
eulerSwap.myAccount(),
eulerSwap.priceX(),
eulerSwap.priceY(),
eulerSwap.concentrationX(),
eulerSwap.concentrationY()
)
);

assertEq(allPoolsLengthAfter - allPoolsLengthBefore, 1);
assertEq(eulerSwapFactory.getPool(poolKey), address(eulerSwap));
assertEq(eulerSwapFactory.getPool(poolKey), address(eulerSwap));

address[] memory poolsList = eulerSwapFactory.getAllPoolsListSlice(0, type(uint256).max);
assertEq(poolsList.length, 1);
assertEq(poolsList[0], address(eulerSwap));
assertEq(eulerSwapFactory.allPools(0), address(eulerSwap));
}

function testInvalidGetAllPoolsListSliceQuery() public {
vm.expectRevert(EulerSwapFactory.InvalidQuery.selector);
eulerSwapFactory.getAllPoolsListSlice(1, 0);
}

function testDeployWithAssetsOutOfOrderOrEqual() public {
vm.prank(creator);
vm.expectRevert(EulerSwap.AssetsOutOfOrderOrEqual.selector);
eulerSwapFactory.deployPool(
IEulerSwapFactory.DeployParams(
address(eTST), address(eTST), holder, 0, 1e18, 1e18, 0.4e18, 0.85e18, 50e18, 50e18
)
);
}

function testDeployWithBadFee() public {
vm.prank(creator);
vm.expectRevert(EulerSwap.BadFee.selector);
eulerSwapFactory.deployPool(
IEulerSwapFactory.DeployParams(
address(eTST), address(eTST2), holder, 1e18, 1e18, 1e18, 0.4e18, 0.85e18, 50e18, 50e18
)
);
}
}
2 changes: 1 addition & 1 deletion test/EulerSwapTest.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.24;

import {IEVault, EulerSwapTestBase, EulerSwap, TestERC20} from "./EulerSwapTestBase.t.sol";
import {IEVault, IEulerSwap, EulerSwapTestBase, EulerSwap, TestERC20} from "./EulerSwapTestBase.t.sol";

contract EulerSwapTest is EulerSwapTestBase {
EulerSwap public eulerSwap;
Expand Down
6 changes: 3 additions & 3 deletions test/EulerSwapTestBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.24;
import {Test, console} from "forge-std/Test.sol";
import {EVaultTestBase, TestERC20} from "evk-test/unit/evault/EVaultTestBase.t.sol";
import {IEVault} from "evk/EVault/IEVault.sol";
import {EulerSwap} from "../src/EulerSwap.sol";
import {IEulerSwap, EulerSwap} from "../src/EulerSwap.sol";
import {EulerSwapPeriphery} from "../src/EulerSwapPeriphery.sol";

contract EulerSwapTestBase is EVaultTestBase {
Expand Down Expand Up @@ -87,7 +87,7 @@ contract EulerSwapTestBase is EVaultTestBase {
vm.prank(creator);
EulerSwap eulerSwap = new EulerSwap(
getEulerSwapParams(debtLimitA, debtLimitB, fee),
EulerSwap.CurveParams({priceX: px, priceY: py, concentrationX: cx, concentrationY: cy})
IEulerSwap.CurveParams({priceX: px, priceY: py, concentrationX: cx, concentrationY: cy})
);

vm.prank(holder);
Expand Down Expand Up @@ -146,7 +146,7 @@ contract EulerSwapTestBase is EVaultTestBase {
view
returns (EulerSwap.Params memory)
{
return EulerSwap.Params({
return IEulerSwap.Params({
vault0: address(eTST),
vault1: address(eTST2),
myAccount: holder,
Expand Down
Loading