Skip to content

Commit 8f5cd1f

Browse files
committed
Merge branch 'master' into v4-exploration
2 parents c7f93de + 4dc0b07 commit 8f5cd1f

File tree

9 files changed

+426
-74
lines changed

9 files changed

+426
-74
lines changed

script/DeployPool.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ contract DeployPool is ScriptUtil {
5757
evc.batch(items);
5858
vm.stopBroadcast();
5959

60-
address pool = factory.eulerAccountToPool(eulerAccount);
60+
address pool = factory.poolByEulerAccount(eulerAccount);
6161

6262
string memory outputScriptFileName = "DeployPool_output.json";
6363

src/EulerSwap.sol

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ contract EulerSwap is IEulerSwap, EVCUtil {
2323
address public immutable eulerAccount;
2424
uint112 public immutable equilibriumReserve0;
2525
uint112 public immutable equilibriumReserve1;
26-
uint256 public immutable feeMultiplier;
26+
uint256 public immutable fee;
27+
uint256 public immutable protocolFee;
28+
address public immutable protocolFeeRecipient;
2729

2830
uint256 public immutable priceX;
2931
uint256 public immutable priceY;
@@ -83,7 +85,9 @@ contract EulerSwap is IEulerSwap, EVCUtil {
8385
equilibriumReserve1 = params.equilibriumReserve1;
8486
reserve0 = params.currReserve0;
8587
reserve1 = params.currReserve1;
86-
feeMultiplier = 1e18 - params.fee;
88+
fee = params.fee;
89+
protocolFee = 0.1e18;
90+
protocolFeeRecipient = address(0);
8791

8892
// Curve params
8993

@@ -120,11 +124,8 @@ contract EulerSwap is IEulerSwap, EVCUtil {
120124

121125
// Deposit all available funds, adjust received amounts downward to collect fees
122126

123-
uint256 amount0In = IERC20(asset0).balanceOf(address(this));
124-
if (amount0In > 0) amount0In = depositAssets(vault0, amount0In) * feeMultiplier / 1e18;
125-
126-
uint256 amount1In = IERC20(asset1).balanceOf(address(this));
127-
if (amount1In > 0) amount1In = depositAssets(vault1, amount1In) * feeMultiplier / 1e18;
127+
uint256 amount0In = depositAssets(asset0, vault0);
128+
uint256 amount1In = depositAssets(asset1, vault1);
128129

129130
// Verify curve invariant is satisfied
130131

@@ -211,14 +212,29 @@ contract EulerSwap is IEulerSwap, EVCUtil {
211212
}
212213

213214
/// @notice Deposits assets into a vault and automatically repays any outstanding debt
215+
/// @param asset The address of the underlying asset
214216
/// @param vault The address of the vault to deposit into
215-
/// @param amount The amount of assets to deposit
216217
/// @return The amount of assets successfully deposited
217218
/// @dev This function attempts to deposit assets into the specified vault.
218219
/// @dev If the deposit fails with E_ZeroShares error, it safely returns 0 (this happens with very small amounts).
219220
/// @dev After successful deposit, if the user has any outstanding controller-enabled debt, it attempts to repay it.
220221
/// @dev If all debt is repaid, the controller is automatically disabled to reduce gas costs in future operations.
221-
function depositAssets(address vault, uint256 amount) internal returns (uint256) {
222+
function depositAssets(address asset, address vault) internal returns (uint256) {
223+
uint256 amount = IERC20(asset).balanceOf(address(this));
224+
if (amount == 0) return 0;
225+
226+
uint256 feeAmount = amount * fee / 1e18;
227+
228+
{
229+
uint256 protocolFeeAmount = feeAmount * protocolFee / 1e18;
230+
231+
if (protocolFeeAmount != 0) {
232+
IERC20(asset).transfer(protocolFeeRecipient, protocolFeeAmount);
233+
amount -= protocolFeeAmount;
234+
feeAmount -= protocolFeeAmount;
235+
}
236+
}
237+
222238
uint256 deposited;
223239

224240
if (IEVC(evc).isControllerEnabled(eulerAccount, vault)) {
@@ -238,13 +254,13 @@ contract EulerSwap is IEulerSwap, EVCUtil {
238254
try IEVault(vault).deposit(amount, eulerAccount) {}
239255
catch (bytes memory reason) {
240256
require(bytes4(reason) == EVKErrors.E_ZeroShares.selector, DepositFailure(reason));
241-
return deposited;
257+
amount = 0;
242258
}
243259

244260
deposited += amount;
245261
}
246262

247-
return deposited;
263+
return deposited > feeAmount ? deposited - feeAmount : 0;
248264
}
249265

250266
/// @notice Approves tokens for a given vault, supporting both standard approvals and permit2

src/EulerSwapFactory.sol

Lines changed: 122 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import {GenericFactory} from "evk/GenericFactory/GenericFactory.sol";
1212
/// @author Euler Labs (https://www.eulerlabs.com/)
1313
contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
1414
/// @dev An array to store all pools addresses.
15-
address[] public allPools;
16-
/// @dev Mapping between euler account and deployed pool that is currently set as operator
17-
mapping(address eulerAccount => address operator) public eulerAccountToPool;
15+
address[] private allPools;
1816
/// @dev Vaults must be deployed by this factory
1917
address public immutable evkFactory;
18+
/// @dev Mapping between euler account and EulerAccountState
19+
mapping(address eulerAccount => EulerAccountState state) private eulerAccountState;
20+
mapping(address asset0 => mapping(address asset1 => address[])) private poolMap;
2021

2122
IPoolManager immutable poolManager;
2223

@@ -25,7 +26,7 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
2526
address indexed asset1,
2627
address vault0,
2728
address vault1,
28-
uint256 indexed feeMultiplier,
29+
uint256 indexed fee,
2930
address eulerAccount,
3031
uint256 reserve0,
3132
uint256 reserve1,
@@ -35,12 +36,14 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
3536
uint256 concentrationY,
3637
address pool
3738
);
39+
event PoolUninstalled(address indexed asset0, address indexed asset1, address indexed eulerAccount, address pool);
3840

3941
error InvalidQuery();
4042
error Unauthorized();
4143
error OldOperatorStillInstalled();
4244
error OperatorNotInstalled();
4345
error InvalidVaultImplementation();
46+
error SliceOutOfBounds();
4447

4548
constructor(IPoolManager _manager, address evc, address evkFactory_) EVCUtil(evc) {
4649
poolManager = _manager;
@@ -58,12 +61,12 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
5861
InvalidVaultImplementation()
5962
);
6063

64+
uninstall(params.eulerAccount);
65+
6166
EulerSwapHook pool =
6267
new EulerSwapHook{salt: keccak256(abi.encode(params.eulerAccount, salt))}(poolManager, params, curveParams);
6368

64-
checkEulerAccountOperators(params.eulerAccount, address(pool));
65-
66-
allPools.push(address(pool));
69+
updateEulerAccountState(params.eulerAccount, address(pool));
6770

6871
pool.activate();
6972

@@ -72,7 +75,7 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
7275
pool.asset1(),
7376
params.vault0,
7477
params.vault1,
75-
pool.feeMultiplier(),
78+
pool.fee(),
7679
params.eulerAccount,
7780
params.currReserve0,
7881
params.currReserve1,
@@ -86,6 +89,11 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
8689
return address(pool);
8790
}
8891

92+
/// @inheritdoc IEulerSwapFactory
93+
function uninstallPool() external {
94+
uninstall(_msgSender());
95+
}
96+
8997
/// @inheritdoc IEulerSwapFactory
9098
function computePoolAddress(
9199
IEulerSwap.Params memory poolParams,
@@ -112,43 +120,127 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
112120
);
113121
}
114122

123+
/// @inheritdoc IEulerSwapFactory
115124
function EVC() external view override(EVCUtil, IEulerSwapFactory) returns (address) {
116125
return address(evc);
117126
}
118127

119128
/// @inheritdoc IEulerSwapFactory
120-
function allPoolsLength() external view returns (uint256) {
129+
function poolByEulerAccount(address eulerAccount) external view returns (address) {
130+
return eulerAccountState[eulerAccount].pool;
131+
}
132+
133+
/// @inheritdoc IEulerSwapFactory
134+
function poolsLength() external view returns (uint256) {
121135
return allPools.length;
122136
}
123137

124138
/// @inheritdoc IEulerSwapFactory
125-
function getAllPoolsListSlice(uint256 _start, uint256 _end) external view returns (address[] memory) {
126-
uint256 length = allPools.length;
127-
if (_end == type(uint256).max) _end = length;
128-
if (_end < _start || _end > length) revert InvalidQuery();
129-
130-
address[] memory allPoolsList = new address[](_end - _start);
131-
for (uint256 i; i < _end - _start; ++i) {
132-
allPoolsList[i] = allPools[_start + i];
133-
}
139+
function poolsSlice(uint256 start, uint256 end) external view returns (address[] memory) {
140+
return _getSlice(allPools, start, end);
141+
}
142+
143+
/// @inheritdoc IEulerSwapFactory
144+
function pools() external view returns (address[] memory) {
145+
return _getSlice(allPools, 0, type(uint256).max);
146+
}
147+
148+
/// @inheritdoc IEulerSwapFactory
149+
function poolsByPairLength(address asset0, address asset1) external view returns (uint256) {
150+
return poolMap[asset0][asset1].length;
151+
}
152+
153+
/// @inheritdoc IEulerSwapFactory
154+
function poolsByPairSlice(address asset0, address asset1, uint256 start, uint256 end)
155+
external
156+
view
157+
returns (address[] memory)
158+
{
159+
return _getSlice(poolMap[asset0][asset1], start, end);
160+
}
134161

135-
return allPoolsList;
162+
/// @inheritdoc IEulerSwapFactory
163+
function poolsByPair(address asset0, address asset1) external view returns (address[] memory) {
164+
return _getSlice(poolMap[asset0][asset1], 0, type(uint256).max);
136165
}
137166

138-
/// @notice Validates operator authorization for euler account. First checks if the account has an existing operator
139-
/// and ensures it is deauthorized. Then verifies the new pool is authorized as an operator. Finally, updates the
140-
/// mapping to track the new pool as the account's operator.
167+
/// @notice Validates operator authorization for euler account and update the relevant EulerAccountState.
141168
/// @param eulerAccount The address of the euler account.
142-
/// @param newPool The address of the new pool.
143-
function checkEulerAccountOperators(address eulerAccount, address newPool) internal {
144-
address operator = eulerAccountToPool[eulerAccount];
169+
/// @param newOperator The address of the new pool.
170+
function updateEulerAccountState(address eulerAccount, address newOperator) internal {
171+
require(evc.isAccountOperatorAuthorized(eulerAccount, newOperator), OperatorNotInstalled());
145172

146-
if (operator != address(0)) {
147-
require(!evc.isAccountOperatorAuthorized(eulerAccount, operator), OldOperatorStillInstalled());
148-
}
173+
(address asset0, address asset1) = _getAssets(newOperator);
174+
175+
address[] storage poolMapArray = poolMap[asset0][asset1];
176+
177+
eulerAccountState[eulerAccount] = EulerAccountState({
178+
pool: newOperator,
179+
allPoolsIndex: uint48(allPools.length),
180+
poolMapIndex: uint48(poolMapArray.length)
181+
});
182+
183+
allPools.push(newOperator);
184+
poolMapArray.push(newOperator);
185+
}
186+
187+
/// @notice Uninstalls the pool associated with the given Euler account
188+
/// @dev This function removes the pool from the factory's tracking and emits a PoolUninstalled event
189+
/// @dev The function checks if the operator is still installed and reverts if it is
190+
/// @dev If no pool exists for the account, the function returns without any action
191+
/// @param eulerAccount The address of the Euler account whose pool should be uninstalled
192+
function uninstall(address eulerAccount) internal {
193+
address pool = eulerAccountState[eulerAccount].pool;
194+
195+
if (pool == address(0)) return;
196+
197+
require(!evc.isAccountOperatorAuthorized(eulerAccount, pool), OldOperatorStillInstalled());
198+
199+
(address asset0, address asset1) = _getAssets(pool);
149200

150-
require(evc.isAccountOperatorAuthorized(eulerAccount, newPool), OperatorNotInstalled());
201+
address[] storage poolMapArr = poolMap[asset0][asset1];
202+
203+
swapAndPop(allPools, eulerAccountState[eulerAccount].allPoolsIndex);
204+
swapAndPop(poolMapArr, eulerAccountState[eulerAccount].poolMapIndex);
205+
206+
delete eulerAccountState[eulerAccount];
207+
208+
emit PoolUninstalled(asset0, asset1, eulerAccount, pool);
209+
}
210+
211+
/// @notice Swaps the element at the given index with the last element and removes the last element
212+
/// @param arr The storage array to modify
213+
/// @param index The index of the element to remove
214+
function swapAndPop(address[] storage arr, uint256 index) internal {
215+
arr[index] = arr[arr.length - 1];
216+
arr.pop();
217+
}
218+
219+
/// @notice Retrieves the asset addresses for a given pool
220+
/// @dev Calls the pool contract to get its asset0 and asset1 addresses
221+
/// @param pool The address of the pool to query
222+
/// @return The addresses of asset0 and asset1 in the pool
223+
function _getAssets(address pool) internal view returns (address, address) {
224+
return (EulerSwap(pool).asset0(), EulerSwap(pool).asset1());
225+
}
226+
227+
/// @notice Returns a slice of an array of addresses
228+
/// @dev Creates a new memory array containing elements from start to end index
229+
/// If end is type(uint256).max, it will return all elements from start to the end of the array
230+
/// @param arr The storage array to slice
231+
/// @param start The starting index of the slice (inclusive)
232+
/// @param end The ending index of the slice (exclusive)
233+
/// @return A new memory array containing the requested slice of addresses
234+
function _getSlice(address[] storage arr, uint256 start, uint256 end) internal view returns (address[] memory) {
235+
uint256 length = arr.length;
236+
if (end == type(uint256).max) end = length;
237+
if (end < start || end > length) revert SliceOutOfBounds();
238+
239+
address[] memory slice = new address[](end - start);
240+
for (uint256 i; i < end - start; ++i) {
241+
slice[i] = arr[start + i];
242+
}
151243

152-
eulerAccountToPool[eulerAccount] = newPool;
244+
return slice;
153245
}
154246
}

src/EulerSwapPeriphery.sol

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,11 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
105105
);
106106
require(amount <= type(uint112).max, SwapLimitExceeded());
107107

108-
uint256 feeMultiplier = eulerSwap.feeMultiplier();
108+
uint256 fee = eulerSwap.fee();
109109
(uint112 reserve0, uint112 reserve1,) = eulerSwap.getReserves();
110110

111-
// exactIn: decrease received amountIn, rounding down
112-
if (exactIn) amount = amount * feeMultiplier / 1e18;
111+
// exactIn: decrease effective amountIn
112+
if (exactIn) amount = amount - (amount * fee / 1e18);
113113

114114
bool asset0IsInput = checkTokens(eulerSwap, tokenIn, tokenOut);
115115
(uint256 inLimit, uint256 outLimit) = calcLimits(eulerSwap, asset0IsInput);
@@ -124,8 +124,8 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
124124
require(amount <= outLimit && quote <= inLimit, SwapLimitExceeded());
125125
}
126126

127-
// exactOut: increase required quote(amountIn), rounding up
128-
if (!exactIn) quote = (quote * 1e18 + (feeMultiplier - 1)) / feeMultiplier;
127+
// exactOut: inflate required amountIn
128+
if (!exactIn) quote = (quote * 1e18) / (1e18 - fee);
129129

130130
return quote;
131131
}

src/interfaces/IEulerSwap.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ interface IEulerSwap {
5050
function eulerAccount() external view returns (address);
5151
function equilibriumReserve0() external view returns (uint112);
5252
function equilibriumReserve1() external view returns (uint112);
53-
function feeMultiplier() external view returns (uint256);
53+
function fee() external view returns (uint256);
54+
function protocolFee() external view returns (uint256);
55+
function protocolFeeRecipient() external view returns (address);
5456
/// @notice Returns the current reserves of the pool
5557
/// @return reserve0 The amount of asset0 in the pool
5658
/// @return reserve1 The amount of asset1 in the pool

0 commit comments

Comments
 (0)