Skip to content

Commit e3b8548

Browse files
authored
Merge pull request #66 from euler-xyz/uninstall-fix
Uninstall fix
2 parents 6e8a8c8 + c751556 commit e3b8548

File tree

3 files changed

+102
-42
lines changed

3 files changed

+102
-42
lines changed

src/EulerSwapFactory.sol

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.27;
33

4+
import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
5+
46
import {IEulerSwapFactory, IEulerSwap} from "./interfaces/IEulerSwapFactory.sol";
57
import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol";
68
import {GenericFactory} from "evk/GenericFactory/GenericFactory.sol";
@@ -13,15 +15,19 @@ import {MetaProxyDeployer} from "./utils/MetaProxyDeployer.sol";
1315
/// @custom:security-contact [email protected]
1416
/// @author Euler Labs (https://www.eulerlabs.com/)
1517
contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
16-
/// @dev An array to store all pools addresses.
17-
address[] private allPools;
18+
using EnumerableSet for EnumerableSet.AddressSet;
19+
1820
/// @dev Vaults must be deployed by this factory
1921
address public immutable evkFactory;
2022
/// @dev The EulerSwap code instance that will be proxied to
2123
address public immutable eulerSwapImpl;
22-
/// @dev Mapping between euler account and EulerAccountState
23-
mapping(address eulerAccount => EulerAccountState state) private eulerAccountState;
24-
mapping(address asset0 => mapping(address asset1 => address[])) private poolMap;
24+
25+
/// @dev Mapping from euler account to pool, if installed
26+
mapping(address eulerAccount => address) internal installedPools;
27+
/// @dev Set of all pool addresses
28+
EnumerableSet.AddressSet internal allPools;
29+
/// @dev Mapping from sorted pair of underlyings to set of pools
30+
mapping(address asset0 => mapping(address asset1 => EnumerableSet.AddressSet)) internal poolMap;
2531

2632
event PoolDeployed(address indexed asset0, address indexed asset1, address indexed eulerAccount, address pool);
2733
event PoolConfig(address indexed pool, IEulerSwap.Params params, IEulerSwap.InitialState initialState);
@@ -98,12 +104,12 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
98104

99105
/// @inheritdoc IEulerSwapFactory
100106
function poolByEulerAccount(address eulerAccount) external view returns (address) {
101-
return eulerAccountState[eulerAccount].pool;
107+
return installedPools[eulerAccount];
102108
}
103109

104110
/// @inheritdoc IEulerSwapFactory
105111
function poolsLength() external view returns (uint256) {
106-
return allPools.length;
112+
return allPools.length();
107113
}
108114

109115
/// @inheritdoc IEulerSwapFactory
@@ -113,12 +119,12 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
113119

114120
/// @inheritdoc IEulerSwapFactory
115121
function pools() external view returns (address[] memory) {
116-
return _getSlice(allPools, 0, type(uint256).max);
122+
return allPools.values();
117123
}
118124

119125
/// @inheritdoc IEulerSwapFactory
120126
function poolsByPairLength(address asset0, address asset1) external view returns (uint256) {
121-
return poolMap[asset0][asset1].length;
127+
return poolMap[asset0][asset1].length();
122128
}
123129

124130
/// @inheritdoc IEulerSwapFactory
@@ -132,7 +138,7 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
132138

133139
/// @inheritdoc IEulerSwapFactory
134140
function poolsByPair(address asset0, address asset1) external view returns (address[] memory) {
135-
return _getSlice(poolMap[asset0][asset1], 0, type(uint256).max);
141+
return poolMap[asset0][asset1].values();
136142
}
137143

138144
/// @notice Validates operator authorization for euler account and update the relevant EulerAccountState.
@@ -143,16 +149,10 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
143149

144150
(address asset0, address asset1) = _getAssets(newOperator);
145151

146-
address[] storage poolMapArray = poolMap[asset0][asset1];
152+
installedPools[eulerAccount] = newOperator;
147153

148-
eulerAccountState[eulerAccount] = EulerAccountState({
149-
pool: newOperator,
150-
allPoolsIndex: uint48(allPools.length),
151-
poolMapIndex: uint48(poolMapArray.length)
152-
});
153-
154-
allPools.push(newOperator);
155-
poolMapArray.push(newOperator);
154+
allPools.add(newOperator);
155+
poolMap[asset0][asset1].add(newOperator);
156156
}
157157

158158
/// @notice Uninstalls the pool associated with the given Euler account
@@ -161,32 +161,22 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
161161
/// @dev If no pool exists for the account, the function returns without any action
162162
/// @param eulerAccount The address of the Euler account whose pool should be uninstalled
163163
function uninstall(address eulerAccount) internal {
164-
address pool = eulerAccountState[eulerAccount].pool;
164+
address pool = installedPools[eulerAccount];
165165

166166
if (pool == address(0)) return;
167167

168168
require(!evc.isAccountOperatorAuthorized(eulerAccount, pool), OldOperatorStillInstalled());
169169

170170
(address asset0, address asset1) = _getAssets(pool);
171171

172-
address[] storage poolMapArr = poolMap[asset0][asset1];
173-
174-
swapAndPop(allPools, eulerAccountState[eulerAccount].allPoolsIndex);
175-
swapAndPop(poolMapArr, eulerAccountState[eulerAccount].poolMapIndex);
172+
allPools.remove(pool);
173+
poolMap[asset0][asset1].remove(pool);
176174

177-
delete eulerAccountState[eulerAccount];
175+
delete installedPools[eulerAccount];
178176

179177
emit PoolUninstalled(asset0, asset1, eulerAccount, pool);
180178
}
181179

182-
/// @notice Swaps the element at the given index with the last element and removes the last element
183-
/// @param arr The storage array to modify
184-
/// @param index The index of the element to remove
185-
function swapAndPop(address[] storage arr, uint256 index) internal {
186-
arr[index] = arr[arr.length - 1];
187-
arr.pop();
188-
}
189-
190180
/// @notice Retrieves the asset addresses for a given pool
191181
/// @dev Calls the pool contract to get its asset0 and asset1 addresses
192182
/// @param pool The address of the pool to query
@@ -202,14 +192,18 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil, ProtocolFee {
202192
/// @param start The starting index of the slice (inclusive)
203193
/// @param end The ending index of the slice (exclusive)
204194
/// @return A new memory array containing the requested slice of addresses
205-
function _getSlice(address[] storage arr, uint256 start, uint256 end) internal view returns (address[] memory) {
206-
uint256 length = arr.length;
195+
function _getSlice(EnumerableSet.AddressSet storage arr, uint256 start, uint256 end)
196+
internal
197+
view
198+
returns (address[] memory)
199+
{
200+
uint256 length = arr.length();
207201
if (end == type(uint256).max) end = length;
208202
if (end < start || end > length) revert SliceOutOfBounds();
209203

210204
address[] memory slice = new address[](end - start);
211205
for (uint256 i; i < end - start; ++i) {
212-
slice[i] = arr[start + i];
206+
slice[i] = arr.at(start + i);
213207
}
214208

215209
return slice;

src/interfaces/IEulerSwapFactory.sol

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@ pragma solidity >=0.8.0;
44
import {IEulerSwap} from "./IEulerSwap.sol";
55

66
interface IEulerSwapFactory {
7-
struct EulerAccountState {
8-
address pool;
9-
uint48 allPoolsIndex;
10-
uint48 poolMapIndex;
11-
}
12-
137
/// @notice Deploy a new EulerSwap pool with the given parameters
148
/// @dev The pool address is deterministically generated using CREATE2 with a salt derived from
159
/// the euler account address and provided salt parameter. This allows the pool address to be

test/FactoryTest.t.sol

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,76 @@ contract FactoryTest is EulerSwapTestBase {
249249
assertEq(pools.length, 1);
250250
assertEq(pools[0], hookAddress);
251251
}
252+
253+
address alice = makeAddr("alice");
254+
address bob = makeAddr("bob");
255+
256+
function test_multipleUninstalls() public {
257+
(IEulerSwap.Params memory params, IEulerSwap.InitialState memory initialState) = getBasicParams();
258+
259+
// Deploy pool for Alice
260+
params.eulerAccount = holder = alice;
261+
(address alicePool, bytes32 aliceSalt) = mineSalt(params);
262+
263+
vm.startPrank(alice);
264+
evc.setAccountOperator(alice, alicePool, true);
265+
eulerSwapFactory.deployPool(params, initialState, aliceSalt);
266+
267+
// Deploy pool for Bob
268+
params.eulerAccount = holder = bob;
269+
(address bobPool, bytes32 bobSalt) = mineSalt(params);
270+
271+
vm.startPrank(bob);
272+
evc.setAccountOperator(bob, bobPool, true);
273+
eulerSwapFactory.deployPool(params, initialState, bobSalt);
274+
275+
{
276+
address[] memory ps = eulerSwapFactory.pools();
277+
assertEq(ps.length, 2);
278+
assertEq(ps[0], alicePool);
279+
assertEq(ps[1], bobPool);
280+
}
281+
282+
{
283+
(address asset0, address asset1) = EulerSwap(alicePool).getAssets();
284+
address[] memory ps = eulerSwapFactory.poolsByPair(asset0, asset1);
285+
assertEq(ps.length, 2);
286+
assertEq(ps[0], alicePool);
287+
assertEq(ps[1], bobPool);
288+
}
289+
290+
// Uninstall pool for Alice
291+
vm.startPrank(alice);
292+
evc.setAccountOperator(alice, alicePool, false);
293+
eulerSwapFactory.uninstallPool();
294+
295+
{
296+
address[] memory ps = eulerSwapFactory.pools();
297+
assertEq(ps.length, 1);
298+
assertEq(ps[0], bobPool);
299+
}
300+
301+
{
302+
(address asset0, address asset1) = EulerSwap(alicePool).getAssets();
303+
address[] memory ps = eulerSwapFactory.poolsByPair(asset0, asset1);
304+
assertEq(ps.length, 1);
305+
assertEq(ps[0], bobPool);
306+
}
307+
308+
// Uninstalling pool for Bob reverts due to an OOB access of the allPools array
309+
vm.startPrank(bob);
310+
evc.setAccountOperator(bob, bobPool, false);
311+
eulerSwapFactory.uninstallPool();
312+
313+
{
314+
address[] memory ps = eulerSwapFactory.pools();
315+
assertEq(ps.length, 0);
316+
}
317+
318+
{
319+
(address asset0, address asset1) = EulerSwap(alicePool).getAssets();
320+
address[] memory ps = eulerSwapFactory.poolsByPair(asset0, asset1);
321+
assertEq(ps.length, 0);
322+
}
323+
}
252324
}

0 commit comments

Comments
 (0)