Skip to content

Commit 7fe863c

Browse files
committed
ip
1 parent 91dd4ca commit 7fe863c

File tree

2 files changed

+172
-30
lines changed

2 files changed

+172
-30
lines changed

src/LayerCredit.sol

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ contract LayerCredit is EVCUtil {
1616
IEulerRouterFactory immutable private routerFactory;
1717
StubOracle immutable private stubOracle;
1818

19+
uint256 private constant MAX_COLLATERALS = 3;
20+
1921
address public settingAdmin;
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 settingMaxTermDuration = 90 days; // Special value of 0 means system sunset (no new bond creation allowed)
2223
uint40 public settingReserveMultiplier = 20e4; // 1e4 scale
2324
uint80 public settingSettlementInterestRate = 21964959992727444861; // 100% APY
2425

@@ -56,37 +57,38 @@ contract LayerCredit is EVCUtil {
5657
}
5758

5859

59-
struct BondStorage {
60-
address vault;
61-
uint40 bondId;
62-
uint16 state; // 0 = active, 1 = soft settlement, 2 = hard settlement, 3 = dead
60+
struct BondState {
61+
uint8 state; // 0 = none, 1 = active, 2 = soft settlement, 3 = hard settlement, 4 = inactive
6362
uint40 termEnd;
6463
uint40 termStart;
6564
address restrictedLender;
6665
address restrictedBorrower;
66+
uint64 earlyRepayPenalty;
6767
uint40 reserveMultiplier; // 1e4 scale
6868
uint80 settlementInterestRate;
6969
}
7070

71-
mapping(address vault => BondStorage) private bondsByVault;
72-
mapping(uint256 bondId => address vault) private bondsById;
73-
uint256 private nextBondId = 1;
71+
mapping(address vault => BondState) private bondsByVault;
7472
EnumerableSet.AddressSet private activeBonds;
73+
EnumerableSet.AddressSet private settlingBonds;
74+
address[] private inactiveBonds;
7575

7676
mapping(address vault => mapping(address who => uint256 shares)) reservedShares;
7777

7878

7979
error SystemSunset();
8080
error InvalidTermDuration();
8181
error InvalidNumberOfCollaterals();
82-
error InvalidLTVIndex();
82+
error InvalidAsset();
83+
error InvalidLTV();
8384
error VaultNotEVCCompatible();
85+
error InvalidEarlyRepayPenalty();
8486

8587
function deployBond(DeployBondParams memory p) external returns (address) {
86-
require(settingMaxCollaterals != 0, SystemSunset());
88+
require(settingMaxTermDuration != 0, SystemSunset());
8789
require(p.termDuration <= settingMaxTermDuration, InvalidTermDuration());
88-
89-
require(p.collaterals.length >= 1 && p.collaterals.length <= settingMaxCollaterals, InvalidNumberOfCollaterals());
90+
require(p.collaterals.length >= 1 && p.collaterals.length <= MAX_COLLATERALS, InvalidNumberOfCollaterals());
91+
require(p.earlyRepayPenalty <= 1e18, InvalidEarlyRepayPenalty());
9092

9193
IEulerRouter router = IEulerRouter(IEulerRouterFactory(routerFactory).deploy(address(this)));
9294

@@ -100,6 +102,8 @@ contract LayerCredit is EVCUtil {
100102
vault.setLiquidationCoolOffTime(1);
101103

102104
for (uint256 i = 0; i < p.collaterals.length; i++) {
105+
require(p.collaterals[i].asset != address(0), InvalidAsset());
106+
103107
IEVault collateralVault;
104108

105109
if (p.collaterals[i].isExternalVault) {
@@ -111,31 +115,30 @@ contract LayerCredit is EVCUtil {
111115

112116
router.govSetResolvedVault(address(vault), true);
113117

118+
uint16 liqLTV = p.collaterals[i].liquidationLTV;
119+
require(liqLTV > 0.1e4, InvalidLTV());
120+
114121
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);
122+
vault.setLTV(address(collateralVault), uint16(liqLTV * 0.98e18 / 1e18), liqLTV, 0);
116123
router.govSetConfig(collateralVault.asset(), p.unitOfAccount, p.collaterals[i].oracle);
117124
}
118125

119126
router.transferGovernance(address(0));
120127
vault.setGovernorAdmin(address(0));
121128

122-
bondsByVault[address(vault)] = BondStorage({
123-
vault: address(vault),
124-
bondId: uint40(nextBondId),
129+
bondsByVault[address(vault)] = BondState({
130+
state: 1,
125131
termEnd: uint40(block.timestamp + p.termDuration),
126132
termStart: uint40(block.timestamp),
127133
restrictedLender: p.restrictedLender,
128134
restrictedBorrower: p.restrictedBorrower,
135+
earlyRepayPenalty: p.earlyRepayPenalty,
129136
reserveMultiplier: settingReserveMultiplier,
130137
settlementInterestRate: settingSettlementInterestRate
131138
});
132139

133-
bondsById[nextBondId] = address(vault);
134-
135140
activeBonds.add(address(vault));
136141

137-
nextBondId++;
138-
139142
return address(vault);
140143
}
141144

@@ -149,4 +152,28 @@ contract LayerCredit is EVCUtil {
149152

150153
return newEscrow;
151154
}
155+
156+
function getBond(address bond) external view returns (BondState memory) {
157+
return bondsByVault[bond];
158+
}
159+
160+
function getActiveBonds(uint256 start, uint256 end) external view returns (address[] memory) {
161+
return getSlice(activeBonds, start, end);
162+
}
163+
164+
165+
error SliceOutOfBounds();
166+
167+
function getSlice(EnumerableSet.AddressSet storage arr, uint256 start, uint256 end) internal view returns (address[] memory) {
168+
uint256 length = arr.length();
169+
if (end == type(uint256).max) end = length;
170+
if (end < start || end > length) revert SliceOutOfBounds();
171+
172+
address[] memory slice = new address[](end - start);
173+
for (uint256 i; i < end - start; ++i) {
174+
slice[i] = arr.at(start + i);
175+
}
176+
177+
return slice;
178+
}
152179
}

src/LayerCreditLens.sol

Lines changed: 124 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,132 @@ import {RPow} from "evk/EVault/shared/lib/RPow.sol";
77
import {LayerCredit} from "./LayerCredit.sol";
88

99
contract LayerCreditLens {
10-
// vault, bondId, state, termEnd, termStart, flags(restrictedLender, restrictedBorrower), supplyAPY, borrowAPY, supplyCap, collaterals
11-
// 20 + 5 + 1 + 5 + 5 + 1 + 6 + 6 + 2 + N
12-
function activeBonds(address layerCreditAddr) external view returns (bytes[] memory output) {
13-
/*
10+
// word0: 20 + 1 + 1 + 5 + 5
11+
// vault, state, flags(restrictedLender, restrictedBorrower), termEnd, termStart
12+
// word1: 1 + 2 + 2 + 2 + 2 + 2 + 2
13+
// assetDecimals, cash, borrows, supplyCap, supplyAPY, borrowAPY, earlyRepayPenalty
14+
// word2: 1 + 20 + 8
15+
// flags(col0External, col1External, col2External), col0, col1[0:8]
16+
// word3: 12 + 20
17+
// col1[8:20], col2
18+
function genCompressedBond(address layerCredit, address bond) internal view returns (uint256 w0, uint256 w1, uint256 w2, uint256 w3) {
1419
unchecked {
15-
output = new bytes[](vaults.length);
16-
for (uint256 i; i < vaults.length; ++i) {
17-
IEVault v = IEVault(vaults[i]);
18-
output[i] = abi.encodePacked(v.asset(), v.decimals(), v.symbol());
20+
LayerCredit.BondState memory b = LayerCredit(layerCredit).getBond(bond);
21+
22+
{
23+
uint8 bondFlags;
24+
if (b.restrictedLender != address(0)) bondFlags |= 2;
25+
if (b.restrictedBorrower != address(0)) bondFlags |= 1;
26+
27+
w0 = uint160(bond);
28+
w0 = (w0 << 8) | b.state;
29+
w0 = (w0 << 8) | bondFlags;
30+
w0 = (w0 << 40) | b.termEnd;
31+
w0 = (w0 << 40) | b.termStart;
32+
}
33+
34+
if (b.state == 0) return (w0, w1, w2, w3);
35+
36+
IEVault v = IEVault(bond);
37+
38+
{
39+
uint256 cash = v.cash();
40+
uint256 borrows = v.totalBorrows();
41+
42+
(uint256 borrowAPY, uint256 supplyAPY) = _computeAPYs(v.interestRate(), cash, borrows, v.interestFee());
43+
(uint16 supplyCap,) = v.caps();
44+
45+
w1 = v.decimals();
46+
w1 = (w1 << 16) | dfloat16(cash);
47+
w1 = (w1 << 16) | dfloat16(borrows);
48+
w1 = (w1 << 16) | supplyCap;
49+
w1 = (w1 << 16) | dfloat16(supplyAPY);
50+
w1 = (w1 << 16) | dfloat16(borrowAPY);
51+
w1 = (w1 << 16) | dfloat16(b.earlyRepayPenalty);
52+
}
53+
54+
{
55+
address[] memory ltvs = v.LTVList();
56+
57+
uint8 collateralFlags;
58+
59+
if (ltvs.length >= 1) {
60+
if (isEscrow(layerCredit, ltvs[0])) collateralFlags |= 4;
61+
w2 |= uint160(ltvs[0]) << (8*8);
62+
}
63+
64+
if (ltvs.length >= 2) {
65+
if (isEscrow(layerCredit, ltvs[1])) collateralFlags |= 2;
66+
w2 |= uint160(ltvs[1]) >> (12*8);
67+
w3 |= uint160(ltvs[1]) << (20*8);
68+
}
69+
70+
if (ltvs.length >= 2) {
71+
if (isEscrow(layerCredit, ltvs[2])) collateralFlags |= 1;
72+
w3 |= uint160(ltvs[2]);
73+
}
74+
75+
w2 |= collateralFlags << (28*8);
1976
}
2077
}
21-
*/
78+
}
79+
80+
function getAllActiveBonds(address layerCredit) external view returns (uint256[] memory output) {
81+
address[] memory bonds = LayerCredit(layerCredit).getActiveBonds(0, type(uint256).max);
82+
output = new uint256[](bonds.length * 4);
83+
84+
uint256 offset;
85+
for (uint256 i; i < bonds.length; ++i) {
86+
(output[offset], output[offset+1], output[offset+2], output[offset+3]) = genCompressedBond(layerCredit, bonds[i]);
87+
offset += 4;
88+
}
89+
}
90+
91+
92+
93+
function isEscrow(address layerCredit, address v) internal view returns (bool) {
94+
return LayerCredit(layerCredit).escrowVaults(IEVault(v).asset()) == v;
95+
}
96+
97+
98+
uint256 internal constant SECONDS_PER_YEAR = 365.2425 * 86400;
99+
100+
function _computeAPYs(uint256 borrowSPY, uint256 cash, uint256 borrows, uint256 interestFee)
101+
internal
102+
pure
103+
returns (uint256 borrowAPY, uint256 supplyAPY)
104+
{
105+
unchecked {
106+
uint256 totalAssets = cash + borrows;
107+
bool overflow;
108+
109+
(borrowAPY, overflow) = RPow.rpow(borrowSPY + 1e27, SECONDS_PER_YEAR, 1e27);
110+
111+
if (overflow) return (0, 0);
112+
113+
borrowAPY -= 1e27;
114+
supplyAPY = totalAssets == 0 ? 0 : borrowAPY * borrows * (1e4 - interestFee) / totalAssets / 1e4;
115+
116+
borrowAPY /= 1e18;
117+
supplyAPY /= 1e18;
118+
}
119+
}
120+
121+
122+
error DFloat16Overflow();
123+
124+
function dfloat16(uint256 n) internal pure returns (uint16) {
125+
unchecked {
126+
uint256 exponent = 2;
127+
128+
while (n > 999) {
129+
exponent++;
130+
n /= 10;
131+
}
132+
133+
require(exponent < 64, DFloat16Overflow());
134+
135+
return uint16((n << 6) | exponent);
136+
}
22137
}
23138
}

0 commit comments

Comments
 (0)