Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Virtual reserves control the maximum debt that the EulerSwap contract will attem

### Reserve synchronisation

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.
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.

## Components

Expand Down
3 changes: 2 additions & 1 deletion script/DeployProtocol.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ contract DeployProtocol is ScriptUtil {

address evc = vm.parseJsonAddress(json, ".evc");
address poolManager = vm.parseJsonAddress(json, ".poolManager");
address factory = vm.parseJsonAddress(json, ".factory");

vm.startBroadcast(deployerAddress);

new EulerSwapFactory(IPoolManager(poolManager), evc);
new EulerSwapFactory(IPoolManager(poolManager), evc, factory);
new EulerSwapPeriphery();

vm.stopBroadcast();
Expand Down
5 changes: 3 additions & 2 deletions script/json/DeployProtocol_input.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"evc": "0x0C9a3dd6b8F28529d72d7f9cE918D493519EE383",
"poolManager": "0x000000000004444c5dc75cB358380D2e3dE08A90"
}
"poolManager": "0x000000000004444c5dc75cB358380D2e3dE08A90",
"factory": "0xF75548aF02f1928CbE9015985D4Fcbf96d728544"
}
38 changes: 23 additions & 15 deletions src/EulerSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ contract EulerSwap is IEulerSwap, EVCUtil {
error Overflow();
error BadParam();
error AmountTooBig();
error DifferentEVC();
error AssetsOutOfOrderOrEqual();
error CurveViolation();
error DepositFailure(bytes reason);
Expand All @@ -70,7 +69,6 @@ contract EulerSwap is IEulerSwap, EVCUtil {
require(curveParams.priceX > 0 && curveParams.priceY > 0, BadParam());
require(curveParams.priceX <= 1e36 && curveParams.priceY <= 1e36, BadParam());
require(curveParams.concentrationX <= 1e18 && curveParams.concentrationY <= 1e18, BadParam());
require(IEVault(params.vault0).EVC() == IEVault(params.vault1).EVC(), DifferentEVC());

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

// Validate reserves

require(verify(equilibriumReserve0, equilibriumReserve1), CurveViolation());
require(verify(reserve0, reserve1), CurveViolation());
require(!verify(reserve0 > 0 ? reserve0 - 1 : 0, reserve1 > 0 ? reserve1 - 1 : 0), CurveViolation());
require(!verify(reserve0 > 0 ? reserve0 - 1 : 0, reserve1), CurveViolation());
require(!verify(reserve0, reserve1 > 0 ? reserve1 - 1 : 0), CurveViolation());

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

// Verify curve invariant is satisified
// Verify curve invariant is satisfied

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

/// @inheritdoc IEulerSwap
function getReserves() external view returns (uint112, uint112, uint32) {
require(status != 2, Locked());
return (reserve0, reserve1, status);
}

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

if (IEVC(evc).isControllerEnabled(eulerAccount, vault)) {
IEVC(evc).call(
vault, eulerAccount, 0, abi.encodeCall(IBorrowing.repayWithShares, (type(uint256).max, eulerAccount))
);
uint256 debt = myDebt(vault);
uint256 repaid = IEVault(vault).repay(amount > debt ? debt : amount, eulerAccount);

if (myDebt(vault) == 0) {
amount -= repaid;
debt -= repaid;
deposited += repaid;

if (debt == 0) {
IEVC(evc).call(vault, eulerAccount, 0, abi.encodeCall(IRiskManager.disableController, ()));
}
}

return amount;
if (amount > 0) {
try IEVault(vault).deposit(amount, eulerAccount) {}
catch (bytes memory reason) {
require(bytes4(reason) == EVKErrors.E_ZeroShares.selector, DepositFailure(reason));
return deposited;
}

deposited += amount;
}

return deposited;
}

/// @notice Approves tokens for a given vault, supporting both standard approvals and permit2
Expand Down
11 changes: 10 additions & 1 deletion src/EulerSwapFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IEulerSwapFactory, IEulerSwap} from "./interfaces/IEulerSwapFactory.sol";
import {EulerSwapHook} from "./EulerSwapHook.sol";
import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol";
import {GenericFactory} from "evk/GenericFactory/GenericFactory.sol";

/// @title EulerSwapFactory contract
/// @custom:security-contact security@euler.xyz
Expand All @@ -14,6 +15,8 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
address[] public allPools;
/// @dev Mapping between euler account and deployed pool that is currently set as operator
mapping(address eulerAccount => address operator) public eulerAccountToPool;
/// @dev Vaults must be deployed by this factory
address public immutable evkFactory;

IPoolManager immutable poolManager;

Expand All @@ -37,9 +40,11 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
error Unauthorized();
error OldOperatorStillInstalled();
error OperatorNotInstalled();
error InvalidVaultImplementation();

constructor(IPoolManager _manager, address evc) EVCUtil(evc) {
constructor(IPoolManager _manager, address evc, address evkFactory_) EVCUtil(evc) {
poolManager = _manager;
evkFactory = evkFactory_;
}

/// @inheritdoc IEulerSwapFactory
Expand All @@ -48,6 +53,10 @@ contract EulerSwapFactory is IEulerSwapFactory, EVCUtil {
returns (address)
{
require(_msgSender() == params.eulerAccount, Unauthorized());
require(
GenericFactory(evkFactory).isProxy(params.vault0) && GenericFactory(evkFactory).isProxy(params.vault1),
InvalidVaultImplementation()
);

EulerSwapHook pool =
new EulerSwapHook{salt: keccak256(abi.encode(params.eulerAccount, salt))}(poolManager, params, curveParams);
Expand Down
14 changes: 7 additions & 7 deletions src/EulerSwapPeriphery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {

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

swap(eulerSwap, tokenIn, tokenOut, amountIn, amountOut);
swap(IEulerSwap(eulerSwap), tokenIn, tokenOut, amountIn, amountOut);
}

/// @inheritdoc IEulerSwapPeriphery
Expand All @@ -35,7 +35,7 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {

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

swap(eulerSwap, tokenIn, tokenOut, amountIn, amountOut);
swap(IEulerSwap(eulerSwap), tokenIn, tokenOut, amountIn, amountOut);
}

/// @inheritdoc IEulerSwapPeriphery
Expand Down Expand Up @@ -73,13 +73,13 @@ contract EulerSwapPeriphery is IEulerSwapPeriphery {
/// @param tokenOut The address of the output token being received
/// @param amountIn The amount of input tokens to swap
/// @param amountOut The amount of output tokens to receive
function swap(address eulerSwap, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut) internal {
IERC20(tokenIn).safeTransferFrom(msg.sender, eulerSwap, amountIn);
function swap(IEulerSwap eulerSwap, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut)
internal
{
IERC20(tokenIn).safeTransferFrom(msg.sender, address(eulerSwap), amountIn);

bool isAsset0In = tokenIn < tokenOut;
(isAsset0In)
? IEulerSwap(eulerSwap).swap(0, amountOut, msg.sender, "")
: IEulerSwap(eulerSwap).swap(amountOut, 0, msg.sender, "");
(isAsset0In) ? eulerSwap.swap(0, amountOut, msg.sender, "") : eulerSwap.swap(amountOut, 0, msg.sender, "");
}

/// @dev Computes the quote for a swap by applying fees and validating state conditions
Expand Down
22 changes: 22 additions & 0 deletions test/DepositFailures.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,26 @@ contract DepositFailuresTest is EulerSwapTestBase {

assertEq(assetTST2.balanceOf(address(eulerSwap)), 1); // griefing transfer was untouched
}

function test_manualEnableController() public monotonicHolderNAV {
vm.prank(holder);
evc.enableController(holder, address(eTST));

uint256 amountIn = 50e18;
uint256 amountOut =
periphery.quoteExactInput(address(eulerSwap), address(assetTST), address(assetTST2), amountIn);

assetTST.mint(address(this), amountIn);
assetTST.transfer(address(eulerSwap), amountIn);
eulerSwap.swap(0, amountOut, address(this), "");

// Swap the other way to measure gas impact

amountIn = 100e18;
amountOut = periphery.quoteExactInput(address(eulerSwap), address(assetTST2), address(assetTST), amountIn);

assetTST2.mint(address(this), amountIn);
assetTST2.transfer(address(eulerSwap), amountIn);
eulerSwap.swap(amountOut, 0, address(this), "");
}
}
2 changes: 1 addition & 1 deletion test/EulerSwapFactoryTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ contract EulerSwapFactoryTest is EulerSwapTestBase {

vm.startPrank(creator);
poolManager = PoolManagerDeployer.deploy(creator);
eulerSwapFactory = new EulerSwapFactory(poolManager, address(evc));
eulerSwapFactory = new EulerSwapFactory(poolManager, address(evc), address(factory));
vm.stopPrank();
}

Expand Down
Loading