Skip to content

Commit 506d101

Browse files
authored
Merge pull request #51 from euler-xyz/v4-exploration-merge
V4 exploration merge
2 parents b2285c8 + 8e5915e commit 506d101

File tree

8 files changed

+69
-28
lines changed

8 files changed

+69
-28
lines changed

docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Virtual reserves control the maximum debt that the EulerSwap contract will attem
4343

4444
### Reserve synchronisation
4545

46-
The EulerSwap contract tracks what it believes the reserves to be by caching their values in storage. These reserves are updated on each swap. However, since the balance is not actually held by the EulerSwap contract (it is simply an operator), the actual underlying balances may get out of sync. This can happen gradually as interest is accrued, or suddenly if the holder moves funds or the position is liquidated. When this occurs, the `syncVirtualReserves()` should be invoked. This determines the actual balances (and debts) of the holder and adjusts them by the configured virtual reserve levels.
46+
The EulerSwap contract tracks what it believes the reserves to be by caching their values in storage. These reserves are updated on each swap. However, since the balance is not actually held by the EulerSwap contract (it is simply an operator), the actual underlying balances may get out of sync. This can happen gradually as interest is accrued, or suddenly if the holder moves funds or the position is liquidated. When this occurs, the EulerSwap operator should be uninstalled and a new, updated one installed instead.
4747

4848
## Components
4949

script/DeployProtocol.s.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ contract DeployProtocol is ScriptUtil {
1919

2020
address evc = vm.parseJsonAddress(json, ".evc");
2121
address poolManager = vm.parseJsonAddress(json, ".poolManager");
22+
address factory = vm.parseJsonAddress(json, ".factory");
2223

2324
vm.startBroadcast(deployerAddress);
2425

25-
new EulerSwapFactory(IPoolManager(poolManager), evc);
26+
new EulerSwapFactory(IPoolManager(poolManager), evc, factory);
2627
new EulerSwapPeriphery();
2728

2829
vm.stopBroadcast();

script/json/DeployProtocol_input.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"evc": "0x0C9a3dd6b8F28529d72d7f9cE918D493519EE383",
3-
"poolManager": "0x000000000004444c5dc75cB358380D2e3dE08A90"
4-
}
3+
"poolManager": "0x000000000004444c5dc75cB358380D2e3dE08A90",
4+
"factory": "0xF75548aF02f1928CbE9015985D4Fcbf96d728544"
5+
}

src/EulerSwap.sol

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ contract EulerSwap is IEulerSwap, EVCUtil {
5050
error Overflow();
5151
error BadParam();
5252
error AmountTooBig();
53-
error DifferentEVC();
5453
error AssetsOutOfOrderOrEqual();
5554
error CurveViolation();
5655
error DepositFailure(bytes reason);
@@ -70,7 +69,6 @@ contract EulerSwap is IEulerSwap, EVCUtil {
7069
require(curveParams.priceX > 0 && curveParams.priceY > 0, BadParam());
7170
require(curveParams.priceX <= 1e36 && curveParams.priceY <= 1e36, BadParam());
7271
require(curveParams.concentrationX <= 1e18 && curveParams.concentrationY <= 1e18, BadParam());
73-
require(IEVault(params.vault0).EVC() == IEVault(params.vault1).EVC(), DifferentEVC());
7472

7573
address asset0Addr = IEVault(params.vault0).asset();
7674
address asset1Addr = IEVault(params.vault1).asset();
@@ -96,9 +94,9 @@ contract EulerSwap is IEulerSwap, EVCUtil {
9694

9795
// Validate reserves
9896

99-
require(verify(equilibriumReserve0, equilibriumReserve1), CurveViolation());
10097
require(verify(reserve0, reserve1), CurveViolation());
101-
require(!verify(reserve0 > 0 ? reserve0 - 1 : 0, reserve1 > 0 ? reserve1 - 1 : 0), CurveViolation());
98+
require(!verify(reserve0 > 0 ? reserve0 - 1 : 0, reserve1), CurveViolation());
99+
require(!verify(reserve0, reserve1 > 0 ? reserve1 - 1 : 0), CurveViolation());
102100

103101
emit EulerSwapCreated(asset0Addr, asset1Addr);
104102
}
@@ -128,7 +126,7 @@ contract EulerSwap is IEulerSwap, EVCUtil {
128126
uint256 amount1In = IERC20(asset1).balanceOf(address(this));
129127
if (amount1In > 0) amount1In = depositAssets(vault1, amount1In) * feeMultiplier / 1e18;
130128

131-
// Verify curve invariant is satisified
129+
// Verify curve invariant is satisfied
132130

133131
{
134132
uint256 newReserve0 = reserve0 + amount0In - amount0Out;
@@ -154,6 +152,7 @@ contract EulerSwap is IEulerSwap, EVCUtil {
154152

155153
/// @inheritdoc IEulerSwap
156154
function getReserves() external view returns (uint112, uint112, uint32) {
155+
require(status != 2, Locked());
157156
return (reserve0, reserve1, status);
158157
}
159158

@@ -220,23 +219,32 @@ contract EulerSwap is IEulerSwap, EVCUtil {
220219
/// @dev After successful deposit, if the user has any outstanding controller-enabled debt, it attempts to repay it.
221220
/// @dev If all debt is repaid, the controller is automatically disabled to reduce gas costs in future operations.
222221
function depositAssets(address vault, uint256 amount) internal returns (uint256) {
223-
try IEVault(vault).deposit(amount, eulerAccount) {}
224-
catch (bytes memory reason) {
225-
require(bytes4(reason) == EVKErrors.E_ZeroShares.selector, DepositFailure(reason));
226-
return 0;
227-
}
222+
uint256 deposited;
228223

229224
if (IEVC(evc).isControllerEnabled(eulerAccount, vault)) {
230-
IEVC(evc).call(
231-
vault, eulerAccount, 0, abi.encodeCall(IBorrowing.repayWithShares, (type(uint256).max, eulerAccount))
232-
);
225+
uint256 debt = myDebt(vault);
226+
uint256 repaid = IEVault(vault).repay(amount > debt ? debt : amount, eulerAccount);
233227

234-
if (myDebt(vault) == 0) {
228+
amount -= repaid;
229+
debt -= repaid;
230+
deposited += repaid;
231+
232+
if (debt == 0) {
235233
IEVC(evc).call(vault, eulerAccount, 0, abi.encodeCall(IRiskManager.disableController, ()));
236234
}
237235
}
238236

239-
return amount;
237+
if (amount > 0) {
238+
try IEVault(vault).deposit(amount, eulerAccount) {}
239+
catch (bytes memory reason) {
240+
require(bytes4(reason) == EVKErrors.E_ZeroShares.selector, DepositFailure(reason));
241+
return deposited;
242+
}
243+
244+
deposited += amount;
245+
}
246+
247+
return deposited;
240248
}
241249

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

src/EulerSwapFactory.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
55
import {IEulerSwapFactory, IEulerSwap} from "./interfaces/IEulerSwapFactory.sol";
66
import {EulerSwapHook} from "./EulerSwapHook.sol";
77
import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol";
8+
import {GenericFactory} from "evk/GenericFactory/GenericFactory.sol";
89

910
/// @title EulerSwapFactory contract
1011
/// @custom:security-contact [email protected]
@@ -14,6 +15,8 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
1415
address[] public allPools;
1516
/// @dev Mapping between euler account and deployed pool that is currently set as operator
1617
mapping(address eulerAccount => address operator) public eulerAccountToPool;
18+
/// @dev Vaults must be deployed by this factory
19+
address public immutable evkFactory;
1720

1821
IPoolManager immutable poolManager;
1922

@@ -37,9 +40,11 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
3740
error Unauthorized();
3841
error OldOperatorStillInstalled();
3942
error OperatorNotInstalled();
43+
error InvalidVaultImplementation();
4044

41-
constructor(IPoolManager _manager, address evc) EVCUtil(evc) {
45+
constructor(IPoolManager _manager, address evc, address evkFactory_) EVCUtil(evc) {
4246
poolManager = _manager;
47+
evkFactory = evkFactory_;
4348
}
4449

4550
/// @inheritdoc IEulerSwapFactory
@@ -48,6 +53,10 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
4853
returns (address)
4954
{
5055
require(_msgSender() == params.eulerAccount, Unauthorized());
56+
require(
57+
GenericFactory(evkFactory).isProxy(params.vault0) && GenericFactory(evkFactory).isProxy(params.vault1),
58+
InvalidVaultImplementation()
59+
);
5160

5261
EulerSwapHook pool =
5362
new EulerSwapHook{salt: keccak256(abi.encode(params.eulerAccount, salt))}(poolManager, params, curveParams);

src/EulerSwapPeriphery.sol

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
2424

2525
require(amountOut >= amountOutMin, AmountOutLessThanMin());
2626

27-
swap(eulerSwap, tokenIn, tokenOut, amountIn, amountOut);
27+
swap(IEulerSwap(eulerSwap), tokenIn, tokenOut, amountIn, amountOut);
2828
}
2929

3030
/// @inheritdoc IEulerSwapPeriphery
@@ -35,7 +35,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
3535

3636
require(amountIn <= amountInMax, AmountInMoreThanMax());
3737

38-
swap(eulerSwap, tokenIn, tokenOut, amountIn, amountOut);
38+
swap(IEulerSwap(eulerSwap), tokenIn, tokenOut, amountIn, amountOut);
3939
}
4040

4141
/// @inheritdoc IEulerSwapPeriphery
@@ -73,13 +73,13 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
7373
/// @param tokenOut The address of the output token being received
7474
/// @param amountIn The amount of input tokens to swap
7575
/// @param amountOut The amount of output tokens to receive
76-
function swap(address eulerSwap, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut) internal {
77-
IERC20(tokenIn).safeTransferFrom(msg.sender, eulerSwap, amountIn);
76+
function swap(IEulerSwap eulerSwap, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut)
77+
internal
78+
{
79+
IERC20(tokenIn).safeTransferFrom(msg.sender, address(eulerSwap), amountIn);
7880

7981
bool isAsset0In = tokenIn < tokenOut;
80-
(isAsset0In)
81-
? IEulerSwap(eulerSwap).swap(0, amountOut, msg.sender, "")
82-
: IEulerSwap(eulerSwap).swap(amountOut, 0, msg.sender, "");
82+
(isAsset0In) ? eulerSwap.swap(0, amountOut, msg.sender, "") : eulerSwap.swap(amountOut, 0, msg.sender, "");
8383
}
8484

8585
/// @dev Computes the quote for a swap by applying fees and validating state conditions

test/DepositFailures.t.sol

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,26 @@ contract DepositFailuresTest is EulerSwapTestBase {
8585

8686
assertEq(assetTST2.balanceOf(address(eulerSwap)), 1); // griefing transfer was untouched
8787
}
88+
89+
function test_manualEnableController() public monotonicHolderNAV {
90+
vm.prank(holder);
91+
evc.enableController(holder, address(eTST));
92+
93+
uint256 amountIn = 50e18;
94+
uint256 amountOut =
95+
periphery.quoteExactInput(address(eulerSwap), address(assetTST), address(assetTST2), amountIn);
96+
97+
assetTST.mint(address(this), amountIn);
98+
assetTST.transfer(address(eulerSwap), amountIn);
99+
eulerSwap.swap(0, amountOut, address(this), "");
100+
101+
// Swap the other way to measure gas impact
102+
103+
amountIn = 100e18;
104+
amountOut = periphery.quoteExactInput(address(eulerSwap), address(assetTST2), address(assetTST), amountIn);
105+
106+
assetTST2.mint(address(this), amountIn);
107+
assetTST2.transfer(address(eulerSwap), amountIn);
108+
eulerSwap.swap(amountOut, 0, address(this), "");
109+
}
88110
}

test/EulerSwapFactoryTest.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ contract EulerSwapFactoryTest is EulerSwapTestBase {
2020

2121
vm.startPrank(creator);
2222
poolManager = PoolManagerDeployer.deploy(creator);
23-
eulerSwapFactory = new EulerSwapFactory(poolManager, address(evc));
23+
eulerSwapFactory = new EulerSwapFactory(poolManager, address(evc), address(factory));
2424
vm.stopPrank();
2525
}
2626

0 commit comments

Comments
 (0)