|
1 | 1 | // SPDX-License-Identifier: UNLICENSED |
2 | 2 | pragma solidity ^0.8.27; |
3 | 3 |
|
4 | | -import {IEVC} from "evc/interfaces/IEthereumVaultConnector.sol"; |
| 4 | +import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol"; |
| 5 | +import {EVCUtil} from "evc/utils/EVCUtil.sol"; |
5 | 6 | import {IEVault, IERC20} from "evk/EVault/IEVault.sol"; |
6 | | -import {IEulerRouterFactory, IEulerRouter} from "./interfaces/Misc.sol"; |
7 | 7 | import "evk/EVault/shared/Constants.sol"; |
8 | 8 | import {GenericFactory} from "evk/GenericFactory/GenericFactory.sol"; |
| 9 | +import {IEulerRouterFactory, IEulerRouter} from "./interfaces/Misc.sol"; |
9 | 10 | import {StubOracle} from "./StubOracle.sol"; |
10 | 11 |
|
11 | | -contract LayerCredit { |
| 12 | +contract LayerCredit is EVCUtil { |
| 13 | + using EnumerableSet for EnumerableSet.AddressSet; |
| 14 | + |
12 | 15 | GenericFactory immutable private eVaultFactory; |
13 | 16 | IEulerRouterFactory immutable private routerFactory; |
14 | 17 | StubOracle immutable private stubOracle; |
15 | 18 |
|
16 | 19 | address public settingAdmin; |
17 | | - uint16 public settingNumVaultLimit; // Special value of 0 means system sunset (no new bond creation allowed) |
18 | | - uint40 public settingMaxTermDuration; |
19 | | - uint40 public settingReserveMultiplier; // 1e4 scale |
20 | | - uint256 public settlementInterestRate; |
| 20 | + uint16 public settingMaxCollaterals = 3; // Special value of 0 means system sunset (no new bond creation allowed) |
| 21 | + uint40 public settingMaxTermDuration = 90 days; |
| 22 | + uint40 public settingReserveMultiplier = 20e4; // 1e4 scale |
| 23 | + uint80 public settingSettlementInterestRate = 21964959992727444861; // 100% APY |
21 | 24 |
|
22 | | - constructor(address eVaultFactory_, address routerFactory_, address settingAdmin_) { |
| 25 | + mapping(address asset => address escrowVault) public escrowVaults; |
| 26 | + |
| 27 | + constructor(address evc, address eVaultFactory_, address routerFactory_, address settingAdmin_) EVCUtil(evc) { |
23 | 28 | eVaultFactory = GenericFactory(eVaultFactory_); |
24 | 29 | routerFactory = IEulerRouterFactory(routerFactory_); |
25 | 30 | stubOracle = new StubOracle(); |
26 | 31 |
|
27 | 32 | settingAdmin = settingAdmin_; |
28 | | - settingNumVaultLimit = 4; |
29 | | - settingMaxTermDuration = 90 days; |
30 | | - settingReserveMultiplier = 20e4; |
31 | | - settlementInterestRate = 21964959992727444861; // 100% APY |
32 | 33 | } |
33 | 34 |
|
34 | | - struct DeployBondVault { |
| 35 | + struct DeployBondCollateral { |
35 | 36 | address asset; |
36 | | - address oracle; |
37 | | - uint256 interestRate; |
38 | | - address restrictedLender; |
39 | | - address restrictedBorrower; |
40 | | - bool noEarlyRepay; |
41 | | - } |
42 | | - |
43 | | - struct DeployBondLTV { |
44 | | - uint256 collateralIndex; |
45 | | - uint256 liabilityIndex; |
| 37 | + bool isExternalVault; |
46 | 38 | uint16 liquidationLTV; |
| 39 | + address oracle; |
47 | 40 | } |
48 | 41 |
|
49 | 42 | struct DeployBondParams { |
| 43 | + address asset; |
50 | 44 | address unitOfAccount; |
51 | 45 | uint256 termDuration; |
52 | | - uint256 interestFee; |
53 | | - address interestFeeRecipient; |
54 | | - DeployBondVault[] vaults; |
55 | | - DeployBondLTV[] ltvs; |
| 46 | + |
| 47 | + uint80 interestRate; |
| 48 | + uint16 interestFee; |
| 49 | + address interestFeeReceiver; |
| 50 | + |
| 51 | + address restrictedLender; |
| 52 | + address restrictedBorrower; |
| 53 | + uint64 earlyRepayPenalty; |
| 54 | + |
| 55 | + DeployBondCollateral[] collaterals; |
| 56 | + } |
| 57 | + |
| 58 | + |
| 59 | + struct BondStorage { |
| 60 | + address vault; |
| 61 | + uint40 bondId; |
| 62 | + uint16 state; // 0 = active, 1 = soft settlement, 2 = hard settlement, 3 = dead |
| 63 | + uint40 termEnd; |
| 64 | + uint40 termStart; |
| 65 | + address restrictedLender; |
| 66 | + address restrictedBorrower; |
| 67 | + uint40 reserveMultiplier; // 1e4 scale |
| 68 | + uint80 settlementInterestRate; |
56 | 69 | } |
57 | 70 |
|
| 71 | + mapping(address vault => BondStorage) private bondsByVault; |
| 72 | + mapping(uint256 bondId => address vault) private bondsById; |
| 73 | + uint256 private nextBondId = 1; |
| 74 | + EnumerableSet.AddressSet private activeBonds; |
| 75 | + |
| 76 | + mapping(address vault => mapping(address who => uint256 shares)) reservedShares; |
| 77 | + |
| 78 | + |
58 | 79 | error SystemSunset(); |
59 | 80 | error InvalidTermDuration(); |
60 | | - error InvalidNumberOfVaults(); |
| 81 | + error InvalidNumberOfCollaterals(); |
61 | 82 | error InvalidLTVIndex(); |
| 83 | + error VaultNotEVCCompatible(); |
62 | 84 |
|
63 | | - function deployBond(DeployBondParams memory p) external { |
64 | | - require(settingNumVaultLimit != 0, SystemSunset()); |
| 85 | + function deployBond(DeployBondParams memory p) external returns (address) { |
| 86 | + require(settingMaxCollaterals != 0, SystemSunset()); |
65 | 87 | require(p.termDuration <= settingMaxTermDuration, InvalidTermDuration()); |
66 | 88 |
|
67 | | - require(p.vaults.length >= 2 && p.vaults.length <= settingNumVaultLimit, InvalidNumberOfVaults()); |
| 89 | + require(p.collaterals.length >= 1 && p.collaterals.length <= settingMaxCollaterals, InvalidNumberOfCollaterals()); |
68 | 90 |
|
69 | 91 | IEulerRouter router = IEulerRouter(IEulerRouterFactory(routerFactory).deploy(address(this))); |
70 | 92 |
|
71 | | - IEVault[] memory vaults = new IEVault[](p.vaults.length); |
| 93 | + IEVault vault = IEVault(GenericFactory(eVaultFactory).createProxy(address(0), true, abi.encodePacked(p.asset, address(router), p.unitOfAccount))); |
| 94 | + |
| 95 | + vault.setInterestRateModel(address(this)); |
| 96 | + vault.setInterestFee(p.interestFee); |
| 97 | + vault.setFeeReceiver(p.interestFeeReceiver); |
| 98 | + vault.setHookConfig(address(this), OP_CONVERT_FEES | OP_BORROW | OP_REPAY | OP_REPAY_WITH_SHARES | OP_DEPOSIT | OP_MINT | OP_SKIM | OP_WITHDRAW | OP_REDEEM); |
| 99 | + vault.setMaxLiquidationDiscount(0.15e4); |
| 100 | + vault.setLiquidationCoolOffTime(1); |
72 | 101 |
|
73 | | - for (uint256 i = 0; i < p.vaults.length; i++) { |
74 | | - IEVault vault = vaults[i] = IEVault(GenericFactory(eVaultFactory).createProxy(address(0), true, abi.encodePacked(p.vaults[i].asset, address(router), p.unitOfAccount))); |
| 102 | + for (uint256 i = 0; i < p.collaterals.length; i++) { |
| 103 | + IEVault collateralVault; |
75 | 104 |
|
76 | | - vault.setInterestRateModel(address(this)); |
77 | | - vault.setHookConfig(address(this), OP_CONVERT_FEES | OP_BORROW | OP_REPAY | OP_REPAY_WITH_SHARES | OP_DEPOSIT | OP_MINT | OP_SKIM | OP_VAULT_STATUS_CHECK); |
78 | | - vault.setMaxLiquidationDiscount(0.15e4); |
79 | | - vault.setLiquidationCoolOffTime(1); |
| 105 | + if (p.collaterals[i].isExternalVault) { |
| 106 | + collateralVault = IEVault(p.collaterals[i].asset); |
| 107 | + require(collateralVault.EVC() == address(evc), VaultNotEVCCompatible()); |
| 108 | + } else { |
| 109 | + collateralVault = getEscrowVault(p.collaterals[i].asset); |
| 110 | + } |
80 | 111 |
|
81 | 112 | router.govSetResolvedVault(address(vault), true); |
82 | | - router.govSetConfig(p.vaults[i].asset, p.unitOfAccount, address(stubOracle)); |
| 113 | + |
| 114 | + router.govSetConfig(collateralVault.asset(), p.unitOfAccount, address(stubOracle)); |
| 115 | + vault.setLTV(address(collateralVault), uint16(p.collaterals[i].liquidationLTV * 0.98e18 / 1e18), p.collaterals[i].liquidationLTV, 0); |
| 116 | + router.govSetConfig(collateralVault.asset(), p.unitOfAccount, p.collaterals[i].oracle); |
83 | 117 | } |
84 | 118 |
|
85 | | - for (uint256 i = 0; i < p.ltvs.length; i++) { |
86 | | - require(p.ltvs[i].collateralIndex < p.vaults.length, InvalidLTVIndex()); |
87 | | - require(p.ltvs[i].liabilityIndex < p.vaults.length, InvalidLTVIndex()); |
| 119 | + router.transferGovernance(address(0)); |
| 120 | + vault.setGovernorAdmin(address(0)); |
| 121 | + |
| 122 | + bondsByVault[address(vault)] = BondStorage({ |
| 123 | + vault: address(vault), |
| 124 | + bondId: uint40(nextBondId), |
| 125 | + termEnd: uint40(block.timestamp + p.termDuration), |
| 126 | + termStart: uint40(block.timestamp), |
| 127 | + restrictedLender: p.restrictedLender, |
| 128 | + restrictedBorrower: p.restrictedBorrower, |
| 129 | + reserveMultiplier: settingReserveMultiplier, |
| 130 | + settlementInterestRate: settingSettlementInterestRate |
| 131 | + }); |
88 | 132 |
|
89 | | - vaults[p.ltvs[i].liabilityIndex].setLTV(address(vaults[p.ltvs[i].collateralIndex]), uint16(p.ltvs[i].liquidationLTV * 0.98e18 / 1e18), p.ltvs[i].liquidationLTV, 0); |
90 | | - } |
| 133 | + bondsById[nextBondId] = address(vault); |
91 | 134 |
|
92 | | - // Install final oracles |
| 135 | + activeBonds.add(address(vault)); |
93 | 136 |
|
94 | | - for (uint256 i = 0; i < p.vaults.length; i++) { |
95 | | - router.govSetConfig(p.vaults[i].asset, p.unitOfAccount, p.vaults[i].oracle); |
96 | | - } |
| 137 | + nextBondId++; |
97 | 138 |
|
98 | | - router.transferGovernance(address(0)); |
| 139 | + return address(vault); |
| 140 | + } |
| 141 | + |
| 142 | + function getEscrowVault(address asset) internal returns (IEVault) { |
| 143 | + if (escrowVaults[asset] != address(0)) return IEVault(escrowVaults[asset]); |
| 144 | + |
| 145 | + IEVault newEscrow = IEVault(GenericFactory(eVaultFactory).createProxy(address(0), true, abi.encodePacked(asset, address(0), address(0)))); |
| 146 | + escrowVaults[asset] = address(newEscrow); |
| 147 | + |
| 148 | + newEscrow.setGovernorAdmin(address(0)); |
| 149 | + |
| 150 | + return newEscrow; |
99 | 151 | } |
100 | 152 | } |
0 commit comments