diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36353e3..38341f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: - name: Run Forge build run: | - forge build --sizes + forge build --sizes --skip script id: build - name: Run Forge tests diff --git a/.gitignore b/.gitignore index 3eea1cd..9d01b69 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ docs/ # Dotenv file .env + +/dev-ctx/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 031cf40..b97dc5a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,18 @@ [submodule "libflat/ethereum-vault-connector"] path = libflat/ethereum-vault-connector url = https://github.com/euler-xyz/ethereum-vault-connector +[submodule "libflat/permit2"] + path = libflat/permit2 + url = https://github.com/Uniswap/permit2 +[submodule "libflat/openzeppelin-contracts"] + path = libflat/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "libflat/v4-core"] + path = libflat/v4-core + url = https://github.com/uniswap/v4-core +[submodule "libflat/v4-periphery"] + path = libflat/v4-periphery + url = https://github.com/uniswap/v4-periphery +[submodule "libflat/solmate"] + path = libflat/solmate + url = https://github.com/rari-capital/solmate diff --git a/chains.js b/chains.js new file mode 100644 index 0000000..95c4199 --- /dev/null +++ b/chains.js @@ -0,0 +1,30 @@ +let chains = [ + //// PRODUCTION + + { + chainId: 31337, + name: 'dev', + safeBaseUrl: 'https://app.safe.global', + safeAddressPrefix: 'dev', + status: 'beta', + }, + ]; + + + + const fs = require("node:fs"); + + for (let c of chains) { + let addrsDir = `./dev-ctx/addresses/${c.chainId}/`; + + c.addresses = {}; + + for (const file of fs.readdirSync(addrsDir)) { + if (!file.endsWith('Addresses.json')) continue; + let section = file.replace(/Addresses[.]json$/, 'Addrs'); + section = (section[0] + '').toLowerCase() + section.substr(1); + c.addresses[section] = JSON.parse(fs.readFileSync(`${addrsDir}/${file}`).toString()); + } + } + + fs.writeFileSync('./dev-ctx/EulerChains.json', JSON.stringify(chains)); \ No newline at end of file diff --git a/deploy-scenario.sh b/deploy-scenario.sh new file mode 100644 index 0000000..ba3ba83 --- /dev/null +++ b/deploy-scenario.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +SCENARIO=$1 + +rm -rf dev-ctx/ +mkdir -p dev-ctx/{addresses,labels,priceapi}/31337 + +forge script --rpc-url ${RPC_URL:-http://127.0.0.1:8545} "script/scenarios/$SCENARIO.s.sol" --broadcast --code-size-limit 100000 -vv +cast rpc evm_increaseTime 86400 || true +cast rpc evm_mine || true + +node chains.js diff --git a/devland.sh b/devland.sh new file mode 100755 index 0000000..956d599 --- /dev/null +++ b/devland.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e + +SCENARIO=${1:-EulerSwapBasic} + + +function cleanup { + pkill -P $$ +} + +trap cleanup EXIT + + +PORT=${PORT:-8545} +anvil --port $PORT --code-size-limit 100000 & +sleep 1 + + +RPC_URL=http://127.0.0.1:$PORT bash ./deploy-scenario.sh "$SCENARIO" + + +echo ------------------------------- +echo DEVLAND READY +echo SCENARIO = $SCENARIO +echo ------------------------------- + +wait diff --git a/foundry.toml b/foundry.toml index b812041..ec90863 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,4 +5,5 @@ libs = ["lib"] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options -auto_detect_remappings = false \ No newline at end of file +auto_detect_remappings = false +fs_permissions = [{ access = "read-write", path = "./dev-ctx/"}] \ No newline at end of file diff --git a/libflat/openzeppelin-contracts b/libflat/openzeppelin-contracts new file mode 160000 index 0000000..0aaa23e --- /dev/null +++ b/libflat/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 0aaa23e57df7eb0949a1d9ae90742c85f771a7cf diff --git a/libflat/permit2 b/libflat/permit2 new file mode 160000 index 0000000..cc56ad0 --- /dev/null +++ b/libflat/permit2 @@ -0,0 +1 @@ +Subproject commit cc56ad0f3439c502c246fc5cfcc3db92bb8b7219 diff --git a/libflat/solmate b/libflat/solmate new file mode 160000 index 0000000..c93f771 --- /dev/null +++ b/libflat/solmate @@ -0,0 +1 @@ +Subproject commit c93f7716c9909175d45f6ef80a34a650e2d24e56 diff --git a/libflat/v4-core b/libflat/v4-core new file mode 160000 index 0000000..b619b67 --- /dev/null +++ b/libflat/v4-core @@ -0,0 +1 @@ +Subproject commit b619b6718e31aa5b4fa0286520c455ceb950276d diff --git a/libflat/v4-periphery b/libflat/v4-periphery new file mode 160000 index 0000000..9628c36 --- /dev/null +++ b/libflat/v4-periphery @@ -0,0 +1 @@ +Subproject commit 9628c36b4f5083d19606e63224e4041fe748edae diff --git a/remappings.txt b/remappings.txt index b6b41c2..78d88e2 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,7 +5,7 @@ openzeppelin-contracts/=libflat/openzeppelin-contracts/contracts/ @uniswap/v4-periphery/=libflat/v4-periphery/ v4-periphery/=libflat/v4-periphery/ v4-core/=libflat/v4-core/src/ -solmate/=libflat/solmate/ +solmate/=libflat/solmate/src/ ethereum-vault-connector/=libflat/ethereum-vault-connector/src/ evc/=libflat/ethereum-vault-connector/src/ diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index 529964d..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {JITpilot} from "../src/JITpilot.sol"; - -contract JITpilotScript is Script { - // JITpilot public JITpilot; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - // JITpilot = new JITpilot(); - - vm.stopBroadcast(); - } -} diff --git a/script/DeployScenario.s.sol b/script/DeployScenario.s.sol new file mode 100644 index 0000000..92319fe --- /dev/null +++ b/script/DeployScenario.s.sol @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +// System + +import {Script, console} from "forge-std/Script.sol"; + +// Deploy base + +import {GenericFactory} from "evk/GenericFactory/GenericFactory.sol"; + +import {EVault} from "evk/EVault/EVault.sol"; +import {ProtocolConfig} from "evk/ProtocolConfig/ProtocolConfig.sol"; + +import {Dispatch} from "evk/EVault/Dispatch.sol"; + +import {Initialize} from "evk/EVault/modules/Initialize.sol"; +import {Token} from "evk/EVault/modules/Token.sol"; +import {Vault} from "evk/EVault/modules/Vault.sol"; +import {Borrowing} from "evk/EVault/modules/Borrowing.sol"; +import {Liquidation} from "evk/EVault/modules/Liquidation.sol"; +import {BalanceForwarder} from "evk/EVault/modules/BalanceForwarder.sol"; +import {Governance} from "evk/EVault/modules/Governance.sol"; +import {RiskManager} from "evk/EVault/modules/RiskManager.sol"; + +import {IEVault, IERC20} from "evk/EVault/IEVault.sol"; +import {TypesLib} from "evk/EVault/shared/types/Types.sol"; +import {Base} from "evk/EVault/shared/Base.sol"; + +import {EthereumVaultConnector} from "ethereum-vault-connector/EthereumVaultConnector.sol"; + +import {TestERC20} from "evk-test/mocks/TestERC20.sol"; +import {MockBalanceTracker} from "evk-test/mocks/MockBalanceTracker.sol"; +import {MockPriceOracle} from "evk-test/mocks/MockPriceOracle.sol"; +import {IRMTestDefault} from "evk-test/mocks/IRMTestDefault.sol"; +import {IHookTarget} from "evk/interfaces/IHookTarget.sol"; +import {SequenceRegistry} from "evk/SequenceRegistry/SequenceRegistry.sol"; + +// Euler swap + +import {TestERC20} from "evk-test/unit/evault/EVaultTestBase.t.sol"; +import {IEVault} from "evk/EVault/IEVault.sol"; +import {IEulerSwap, IEVC, EulerSwap} from "euler-swap/EulerSwap.sol"; +import {EulerSwapFactory} from "euler-swap/EulerSwapFactory.sol"; +import {EulerSwapPeriphery} from "euler-swap/EulerSwapPeriphery.sol"; +import {PoolManagerDeployer} from "euler-swap/../test/utils/PoolManagerDeployer.sol"; + +// Maglev stuff + +import {MaglevLens} from "src/MaglevLens.sol"; + +struct Asset { + string symbol; + address asset; + address vault; + string price; + uint256 priceNum; +} + +contract DeployScenario is Script { + //////// Users + + uint256 user0PK = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + uint256 user1PK = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + uint256 user2PK = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a; + uint256 user3PK = 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6; + + address user0 = vm.addr(user0PK); + address user1 = vm.addr(user1PK); + address user2 = vm.addr(user2PK); + address user3 = vm.addr(user3PK); + + //////// Main system + + EthereumVaultConnector public evc; + address admin; + address feeReceiver; + address protocolFeeReceiver; + ProtocolConfig protocolConfig; + address balanceTracker; + MockPriceOracle oracle; + address unitOfAccount; + address permit2; + address sequenceRegistry; + GenericFactory public factory; + + Base.Integrations integrations; + Dispatch.DeployedModules modules; + + address initializeModule; + address tokenModule; + address vaultModule; + address borrowingModule; + address liquidationModule; + address riskManagerModule; + address balanceForwarderModule; + address governanceModule; + + //////// Tokens + + Asset[] assets; + + TestERC20 assetWETH; + IEVault eWETH; + + TestERC20 assetwstETH; + IEVault ewstETH; + + TestERC20 assetUSDC; + IEVault eUSDC; + + TestERC20 assetUSDT; + IEVault eUSDT; + + TestERC20 assetDAI; + IEVault eDAI; + + TestERC20 assetUSDZ; + IEVault eUSDZ; + + //////// EulerSwap + + address poolManager; + address eulerSwapImpl; + EulerSwapFactory eulerSwapFactory; + EulerSwapPeriphery eulerSwapPeriphery; + + //////// Maglev + + MaglevLens maglevLens; + + function run() public { + vm.startBroadcast(user3PK); + + deployEulerSystem(); + deployAssets(); + deployEulerSwap(); + deployMaglevLens(); + + vm.stopBroadcast(); + + addLiquidity(); + + setup(); + } + + function deployEulerSystem() internal { + admin = makeAddr("admin"); + feeReceiver = makeAddr("feeReceiver"); + protocolFeeReceiver = makeAddr("protocolFeeReceiver"); + factory = new GenericFactory(user3); + + evc = new EthereumVaultConnector(); + protocolConfig = new ProtocolConfig(admin, protocolFeeReceiver); + balanceTracker = address(new MockBalanceTracker()); + oracle = new MockPriceOracle(); + unitOfAccount = address(1); + permit2 = address(0); + sequenceRegistry = address(new SequenceRegistry()); + integrations = + Base.Integrations(address(evc), address(protocolConfig), sequenceRegistry, balanceTracker, permit2); + + initializeModule = address(new Initialize(integrations)); + tokenModule = address(new Token(integrations)); + vaultModule = address(new Vault(integrations)); + borrowingModule = address(new Borrowing(integrations)); + liquidationModule = address(new Liquidation(integrations)); + riskManagerModule = address(new RiskManager(integrations)); + balanceForwarderModule = address(new BalanceForwarder(integrations)); + governanceModule = address(new Governance(integrations)); + + modules = Dispatch.DeployedModules({ + initialize: initializeModule, + token: tokenModule, + vault: vaultModule, + borrowing: borrowingModule, + liquidation: liquidationModule, + riskManager: riskManagerModule, + balanceForwarder: balanceForwarderModule, + governance: governanceModule + }); + + address evaultImpl = address(new EVault(integrations, modules)); + + factory.setImplementation(evaultImpl); + + string memory result = vm.serializeAddress("coreAddresses", "evc", address(evc)); + result = vm.serializeAddress("coreAddresses", "eVaultFactory", address(factory)); + vm.writeJson(result, "./dev-ctx/addresses/31337/CoreAddresses.json"); + } + + function genAsset(string memory symbol, uint8 decimals, string memory price, uint256 priceNum) + internal + returns (TestERC20, IEVault) + { + Asset memory a; + + a.symbol = symbol; + a.asset = address(new TestERC20(string(abi.encodePacked(symbol, " Token")), symbol, decimals, false)); + a.vault = factory.createProxy(address(0), true, abi.encodePacked(a.asset, address(oracle), unitOfAccount)); + a.price = price; + a.priceNum = priceNum; + + IEVault(a.vault).setHookConfig(address(0), 0); + IEVault(a.vault).setInterestRateModel(address(new IRMTestDefault())); + IEVault(a.vault).setMaxLiquidationDiscount(0.2e4); + IEVault(a.vault).setFeeReceiver(feeReceiver); + + assets.push(a); + return (TestERC20(a.asset), IEVault(a.vault)); + } + + function deployAssets() internal virtual { + (assetWETH, eWETH) = genAsset("WETH", 18, "2865", 2865e18); + (assetwstETH, ewstETH) = genAsset("wstETH", 18, "3055", 3055e18); + (assetUSDC, eUSDC) = genAsset("USDC", 6, "1.000142", 1e18 * 1e12); + (assetUSDT, eUSDT) = genAsset("USDT", 6, "0.999218", 1e18 * 1e12); + (assetDAI, eDAI) = genAsset("DAI", 18, "1.00123", 1e18); + (assetUSDZ, eUSDZ) = genAsset("USDZ", 6, "1.00081", 1e18 * 1e12); + + for (uint256 i; i < assets.length; ++i) { + oracle.setPrice(assets[i].vault, unitOfAccount, assets[i].priceNum); + + for (uint256 j; j < assets.length; ++j) { + if (i == j) continue; + IEVault(assets[i].vault).setLTV(assets[j].vault, 0.92e4, 0.94e4, 0); + } + } + + eWETH.setLTV(address(ewstETH), 0.5e4, 0.52e4, 0); // lower wstETH/WETH LTV for testing + ewstETH.setLTV(address(eWETH), 0.91e4, 0.93e4, 0); // change WETH/wstETH LTV for testing + ewstETH.setLTV(address(eUSDC), 0.8e4, 0.82e4, 0); // change USDC/wstETH LTV for testing + + eWETH.setLTV(address(eUSDC), 0.65e4, 0.67e4, 0); // change USDC/WETH LTV for testing + eWETH.setLTV(address(eUSDT), 0.85e4, 0.87e4, 0); // change USDT/WETH LTV for testing + + address[] memory vaults = new address[](assets.length); + for (uint256 i; i < assets.length; ++i) { + vaults[i] = assets[i].vault; + } + + { + string memory result = vm.serializeAddress("products", "vaults", vaults); + string memory obj = vm.serializeString("products2", "testing-product", result); + vm.writeJson(obj, "./dev-ctx/labels/31337/products.json"); + } + + { + string memory pricesFile = "./dev-ctx/priceapi/31337/prices.json"; + vm.writeLine(pricesFile, "{"); + + for (uint256 i; i < assets.length; ++i) { + string memory line = string( + abi.encodePacked( + "\"", + vm.toString(assets[i].asset), + "\": {\"price\":", + assets[i].price, + "}", + (i == assets.length - 1 ? "" : ",") + ) + ); + vm.writeLine(pricesFile, line); + } + + vm.writeLine(pricesFile, "}"); + } + } + + function deployEulerSwap() internal { + poolManager = address(PoolManagerDeployer.deploy(address(0))); + eulerSwapImpl = address(new EulerSwap(address(evc), poolManager)); + eulerSwapFactory = new EulerSwapFactory(address(evc), address(factory), eulerSwapImpl, address(0), address(0)); + eulerSwapPeriphery = new EulerSwapPeriphery(); + + string memory result = vm.serializeAddress("eulerSwap", "eulerSwapFactory", address(eulerSwapFactory)); + result = vm.serializeAddress("eulerSwap", "eulerSwapPeriphery", address(eulerSwapPeriphery)); + vm.writeJson(result, "./dev-ctx/addresses/31337/EulerSwapAddresses.json"); + } + + function deployMaglevLens() internal { + maglevLens = new MaglevLens(); + + string memory result = vm.serializeAddress("maglev", "maglevLens", address(maglevLens)); + vm.writeJson(result, "./dev-ctx/addresses/31337/MaglevAddresses.json"); + } + + function getSubaccount(address user, uint8 account) internal pure returns (address) { + return address(uint160(user) ^ account); + } + + function addLiquidity() internal virtual { + // user2 is passive depositor + vm.startBroadcast(user2PK); + + assetUSDC.mint(user2, 1000000e6); + assetUSDT.mint(user2, 1000000e6); + assetWETH.mint(user2, 100000e18); + assetwstETH.mint(user2, 100000e18); + assetDAI.mint(user2, 1000000e18); + assetUSDZ.mint(user2, 1000000e6); + + assetUSDC.approve(address(eUSDC), type(uint256).max); + assetUSDT.approve(address(eUSDT), type(uint256).max); + assetWETH.approve(address(eWETH), type(uint256).max); + assetwstETH.approve(address(ewstETH), type(uint256).max); + assetDAI.approve(address(eDAI), type(uint256).max); + assetUSDZ.approve(address(eUSDZ), type(uint256).max); + + eUSDC.deposit(1000000e6, user2); + eUSDT.deposit(1000000e6, user2); + eWETH.deposit(100000e18, user2); + ewstETH.deposit(100000e18, user2); + eDAI.deposit(1000000e18, user2); + eUSDZ.deposit(1000000e6, user2); + + vm.stopBroadcast(); + } + + function giveLotsOfCash(address user) internal virtual { + assetUSDC.mint(user, 1000000e6); + assetUSDT.mint(user, 1000000e6); + assetWETH.mint(user, 1000e18); + assetwstETH.mint(user, 1000e18); + assetDAI.mint(user, 1000000e18); + assetUSDZ.mint(user, 1000000e18); + } + + function setup() internal virtual {} +} diff --git a/script/scenarios/EulerSwapDebtScenario.s.sol b/script/scenarios/EulerSwapDebtScenario.s.sol new file mode 100644 index 0000000..1070249 --- /dev/null +++ b/script/scenarios/EulerSwapDebtScenario.s.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {DeployScenario} from "../DeployScenario.s.sol"; +import {JITpilot} from "../../src/JITpilot.sol"; +import {IEulerSwap} from "euler-swap/interfaces/IEulerSwap.sol"; +import {HookMiner} from "../../libflat/euler-swap/test/utils/HookMiner.sol"; +import {Hooks} from "v4-core/libraries/Hooks.sol"; +import {MetaProxyDeployer} from "euler-swap/utils/MetaProxyDeployer.sol"; +import {console2 as console} from "forge-std/console2.sol"; +import {TestERC20} from "evk-test/unit/evault/EVaultTestBase.t.sol"; +import {IEVault} from "evk/EVault/IEVault.sol"; + +contract EulerSwapDebtScenario is DeployScenario { + address eulerSwap; + JITpilot jitPilot; + + function setup() internal virtual override { + vm.startBroadcast(user3PK); + eUSDC.setLTV(address(eWETH), 0.65e4, 0.67e4, 0); + eUSDC.setLTV(address(eUSDT), 0.85e4, 0.87e4, 0); + deployJITPilot(); + vm.stopBroadcast(); + + createEulerSwap(); // user 2 + + // create borrow positions for other users + giveTonsOfCash(user0); + giveTonsOfCash(user1); + // giveTonsOfCash(user3); + + depositCollateralIntoVault(user0, user0PK, address(eUSDC), 3_000_000_000e6); + depositCollateralIntoVault(user1, user1PK, address(eWETH), 1_000_000e18); + + borrowFromVault(user0, user0PK, address(eWETH), 500_000e18); + borrowFromVault(user1, user1PK, address(eUSDC), 1_600_000_000e6); + + jitPilot.getData(user2); + console.log("user2", user2); + + // buy USDC so that user2's EulerSwap position has to borrow + uint256 amountOut = 286_500_000e6; + _swapExactOut(address(assetWETH), address(assetUSDC), amountOut, user0, user0PK); + } + + function giveTonsOfCash(address user) internal virtual { + assetUSDC.mint(user, 100_000_000_000e6); + assetUSDT.mint(user, 100_000_000_000e6); + assetWETH.mint(user, 10_000_000e18); + assetwstETH.mint(user, 10_000_000e18); + assetDAI.mint(user, 1_000_000_000e18); + assetUSDZ.mint(user, 1_000_000_000e18); + } + + function deployJITPilot() internal { + jitPilot = new JITpilot(); + jitPilot.setEVC(address(evc)); + // jitPilot.setEVK(address(factory)); + jitPilot.setMaglevLens(address(maglevLens)); + jitPilot.setEulerSwapFactory(address(eulerSwapFactory)); + + string memory result = vm.serializeAddress("jitpilot", "jitPilot", address(jitPilot)); + vm.writeJson(result, "./dev-ctx/addresses/31337/JITpilotAddresses.json"); + } + + function createEulerSwap() internal { + vm.startBroadcast(user2PK); + + // Create pool parameters + IEulerSwap.Params memory poolParams = IEulerSwap.Params({ + vault0: address(eUSDC), + vault1: address(eWETH), + eulerAccount: user2, + equilibriumReserve0: 800_000_000e6, + equilibriumReserve1: 280_000e18, + priceX: 1e18, + priceY: 2865e6, + concentrationX: 0.9e18, + concentrationY: 0.9e18, + fee: 0, + protocolFee: 0, + protocolFeeRecipient: address(0) + }); + + // Define required hook flags + uint160 flags = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG + | Hooks.BEFORE_DONATE_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG + ); + + // Mine salt + bytes memory creationCode = MetaProxyDeployer.creationCodeMetaProxy(eulerSwapImpl, abi.encode(poolParams)); + (address hookAddress, bytes32 salt) = HookMiner.find(address(eulerSwapFactory), flags, creationCode); + + eulerSwap = hookAddress; + evc.setAccountOperator(user2, eulerSwap, true); + + eulerSwapFactory.deployPool( + poolParams, IEulerSwap.InitialState({currReserve0: 800_000_000e6, currReserve1: 280_000e18}), salt + ); + + string memory result = vm.serializeAddress("eulerSwap", "eulerSwap", address(eulerSwap)); + vm.writeJson(result, "./dev-ctx/addresses/31337/EulerSwapAddresses.json"); + vm.stopBroadcast(); + } + + function depositCollateralIntoVault(address user, uint256 userPK, address vaultAddress, uint256 amount) internal { + vm.startBroadcast(userPK); + TestERC20(IEVault(vaultAddress).asset()).mint(user, amount); + TestERC20(IEVault(vaultAddress).asset()).approve(vaultAddress, type(uint256).max); + IEVault(vaultAddress).deposit(amount, user); + + if (!evc.isCollateralEnabled(user, vaultAddress)) { + evc.enableCollateral(user, vaultAddress); + } + + vm.stopBroadcast(); + } + + function borrowFromVault(address user, uint256 userPK, address vaultAddress, uint256 amount) internal { + vm.startBroadcast(userPK); + + if (evc.getControllers(user).length > 0) { + require(evc.isControllerEnabled(user, vaultAddress), "Controller not enabled"); + } else { + evc.enableController(user, vaultAddress); + } + + IEVault(vaultAddress).borrow(amount, user); + + vm.stopBroadcast(); + } + + function _swapExactOut(address tokenIn, address tokenOut, uint256 amountOut, address receiver, uint256 userPK) + internal + { + vm.startBroadcast(userPK); + uint256 amountIn = eulerSwapPeriphery.quoteExactOutput(eulerSwap, tokenIn, tokenOut, amountOut); + console.log("amountIn", amountIn); + + TestERC20(tokenIn).approve(address(eulerSwapPeriphery), type(uint256).max); + console.log("user1's WETH balance (tokenIn)", TestERC20(tokenIn).balanceOf(user1)); + console.log("WETH address (tokenIn)", tokenIn); + console.log("user1's USDC balance (tokenOut)", TestERC20(tokenOut).balanceOf(user1)); + console.log("USDC address (tokenOut)", tokenOut); + console.log("user1's address: ", user1); + console.log("eulerSwap address: ", eulerSwap); + eulerSwapPeriphery.swapExactOut( + eulerSwap, + tokenIn, + tokenOut, + amountOut, + receiver, + amountIn * 101 / 100, // allow up to 1% slippage + 0 + ); + vm.stopBroadcast(); + } +} diff --git a/src/JITpilot.sol b/src/JITpilot.sol index 7755df8..b86a4ce 100644 --- a/src/JITpilot.sol +++ b/src/JITpilot.sol @@ -39,7 +39,7 @@ contract JITpilot { uint256 currentLTV; uint256 swapFees; int256 netInterest; - uint256 depositValue; + int256 depositValue; } // LP data structure @@ -156,7 +156,7 @@ contract JITpilot { // Calculate current Yield uint256 currentYield = 0; - uint256 swapApy = currentData.swapFees / currentData.depositValue; + uint256 swapApy = uint256(int256(currentData.swapFees) * 1e18 / currentData.depositValue); if (currentData.depositValue > 0) { if (currentData.netInterest >= 0) { currentYield = (uint256(currentData.netInterest) * PRECISION) + swapApy; @@ -287,37 +287,13 @@ contract JITpilot { BlockData memory blockData; - blockData.allowedLTV = 0; - blockData.currentLTV = 0; - blockData.swapFees = 0; - blockData.netInterest = 0; - blockData.depositValue = 0; - // uint256 swapFees = 0; uint256 supplyApyTotal = 0; uint256 borrowApyTotal = 0; - // We only consider the EulerSwap vaults as collateral for this account, even if - // they have other enabled collaterals. This may differ from Euler's own UI, but it's - // more accurate for our purposes. - // uint256 collateralValue0 = getCollateralValue(lp, vault0); - // uint256 collateralValue1 = getCollateralValue(lp, vault1); - uint256 collateralValueTotal = - getCollateralValue(lp, eulerSwapData.params.vault0) + getCollateralValue(lp, eulerSwapData.params.vault1); - - // get supply APY data using the MaglevLens contract - IMaglevLens maglevLens = IMaglevLens(MaglevLens); - address[] memory collateralVaults = new address[](2); - collateralVaults[0] = eulerSwapData.params.vault0; - collateralVaults[1] = eulerSwapData.params.vault1; - IMaglevLens.VaultGlobal[] memory collateralVaultsGlobal = maglevLens.vaultsGlobal(collateralVaults); - // packed2: shares (160), supply APY (48), borrow APY (48) - for (uint256 i; i < collateralVaultsGlobal.length; ++i) { - uint256 supplyApy = uint256((collateralVaultsGlobal[i].packed2 << (256 - 96)) >> (256 - 48)); - supplyApyTotal += supplyApy * getCollateralValue(lp, collateralVaults[i]) / collateralValueTotal; - console.log("Supply APY for vault", collateralVaults[i], "is", supplyApy); - console.log("Supply APY total is", supplyApyTotal); - } + (uint256 collateralValueTotal, uint256 debtValue) = getDepositValue(lp); + blockData.depositValue = int256(collateralValueTotal) - int256(debtValue); + supplyApyTotal = getSupplyApy(lp); // get the currently enabled controller vault (i.e. the debt vault) address controllerVault = getCurrentControllerVault(lp); @@ -327,8 +303,7 @@ contract JITpilot { console.log("doesn't have controller vault: ", controllerVault); blockData.allowedLTV = 0; blockData.currentLTV = 0; - // If there is no debt, there is no looping or leverage - blockData.depositValue = collateralValueTotal; + // If there is no debt, there is no looping or leverage, so interest is just supplyAPY blockData.netInterest = int256(supplyApyTotal); } else { console.log("has controller vault: ", controllerVault); @@ -336,7 +311,6 @@ contract JITpilot { address collateralVault = (controllerVault == eulerSwapData.params.vault0) ? eulerSwapData.params.vault1 : eulerSwapData.params.vault0; - uint256 debtValue = getDebtValue(lp, controllerVault); console.log("debtValue: ", debtValue); blockData.allowedLTV = uint256(IEVault(controllerVault).LTVLiquidation(collateralVault)) * 1e18 / 1e4; blockData.currentLTV = debtValue * 1e18 / collateralValueTotal; @@ -344,24 +318,27 @@ contract JITpilot { address[] memory controllerVaultArray = new address[](1); controllerVaultArray[0] = controllerVault; - IMaglevLens.VaultGlobal[] memory controllerVaultsGlobal = maglevLens.vaultsGlobal(controllerVaultArray); + IMaglevLens.VaultGlobal[] memory controllerVaultsGlobal = + IMaglevLens(MaglevLens).vaultsGlobal(controllerVaultArray); // borrow APY is on the last 48 bits. Shift left then right to extract it. uint256 borrowApy = uint256((controllerVaultsGlobal[0].packed2 << (256 - 48)) >> (256 - 48)); borrowApyTotal = borrowApy; - - // The EVC already ensures that liabilityValue is less than collateralValueTotal, so this is safe - blockData.depositValue = collateralValueTotal - debtValue; if (supplyApyTotal * collateralValueTotal < borrowApyTotal * debtValue) { // avoid overflow - blockData.netInterest = -int256((borrowApyTotal * debtValue - supplyApyTotal * collateralValueTotal) / blockData.depositValue) - * 1e9; + blockData.netInterest = -int256( + (borrowApyTotal * debtValue - supplyApyTotal * collateralValueTotal) + / uint256(blockData.depositValue) + ) * 1e9; } else { blockData.netInterest = int256( - (supplyApyTotal * collateralValueTotal - borrowApyTotal * debtValue) / blockData.depositValue + (supplyApyTotal * collateralValueTotal - borrowApyTotal * debtValue) + / uint256(blockData.depositValue) ) * 1e9; } } + blockData.depositValue = int256(collateralValueTotal) - int256(debtValue); + // get LP's liquidity status in the controller vault, with regards to liquidation // ( // address[] memory collateralVaults, @@ -416,6 +393,8 @@ contract JITpilot { uint256 outLimit01; uint256 inLimit10; uint256 outLimit10; + uint16 borrowLTV01; + uint16 borrowLTV10; } function getEulerSwapData(address poolAddr) internal view returns (EulerSwapData memory output) { @@ -432,6 +411,9 @@ contract JITpilot { output.asset1 = asset1; (output.inLimit01, output.outLimit01) = pool.getLimits(asset0, asset1); (output.inLimit10, output.outLimit10) = pool.getLimits(asset1, asset0); + // fetch borrow LTVs. These will be used to calculate reserves for rebalancing + output.borrowLTV01 = IEVault(output.params.vault0).LTVBorrow(output.params.vault1); + output.borrowLTV10 = IEVault(output.params.vault1).LTVBorrow(output.params.vault0); } /** * @dev Rebalance LP position (placeholder - to be implemented later) @@ -578,4 +560,57 @@ contract JITpilot { function getData(address lp) external view returns (BlockData memory) { return fetchData(lp); } + + /** + * @dev Get the deposit value of an LP + * @param lp LP address + * @return collateralValueTotal Collateral value of the LP + * @return debtValue Debt value of the LP + */ + function getDepositValue(address lp) + internal + view + virtual + returns (uint256 collateralValueTotal, uint256 debtValue) + { + address poolAddr = IEulerSwapFactory(EulerSwapFactory).poolByEulerAccount(lp); + IEulerSwap.Params memory eulerSwapParams = IEulerSwap(poolAddr).getParams(); + + collateralValueTotal = + getCollateralValue(lp, eulerSwapParams.vault0) + getCollateralValue(lp, eulerSwapParams.vault1); + debtValue = getDebtValue(lp, eulerSwapParams.vault0) + getDebtValue(lp, eulerSwapParams.vault1); + + return (collateralValueTotal, debtValue); + } + + /** + * @dev Get the supply APY of an LP + * @param lp LP address + * @return supplyApyTotal Supply APY of the LP, weighted by collateral value + */ + function getSupplyApy(address lp) internal view virtual returns (uint256 supplyApyTotal) { + address poolAddr = IEulerSwapFactory(EulerSwapFactory).poolByEulerAccount(lp); + IEulerSwap.Params memory eulerSwapParams = IEulerSwap(poolAddr).getParams(); + + // get supply APY data using the MaglevLens contract + IMaglevLens maglevLens = IMaglevLens(MaglevLens); + address[] memory collateralVaults = new address[](2); + collateralVaults[0] = eulerSwapParams.vault0; + collateralVaults[1] = eulerSwapParams.vault1; + IMaglevLens.VaultGlobal[] memory collateralVaultsGlobal = maglevLens.vaultsGlobal(collateralVaults); + // packed2: shares (160), supply APY (48), borrow APY (48) + uint256 collateralValueTotal; + for (uint256 i; i < collateralVaultsGlobal.length; ++i) { + uint256 supplyApy = uint256((collateralVaultsGlobal[i].packed2 << (256 - 96)) >> (256 - 48)); + uint256 collateralValue = getCollateralValue(lp, collateralVaults[i]); + supplyApyTotal += supplyApy * collateralValue; + collateralValueTotal += collateralValue; + console.log("Supply APY for vault", collateralVaults[i], "is", supplyApy); + console.log("Supply APY total is", supplyApyTotal); + } + + supplyApyTotal = supplyApyTotal / collateralValueTotal; + + return supplyApyTotal; + } }