| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262 |
129×
1×
1×
2×
15×
15×
15×
15×
15×
15×
15×
8×
15×
15×
16×
16×
16×
16×
15×
10×
10×
10×
10×
15×
8×
26×
26×
26×
26×
26×
25×
25×
25×
25×
25×
25×
25×
25×
25×
25×
25×
25×
25×
25×
25×
25×
18×
18×
18×
18×
18×
28×
28×
28×
28×
1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
1×
2×
52×
52×
52×
| // SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;
import "../Euler.sol";
import "../Storage.sol";
import "../modules/EToken.sol";
import "../modules/Markets.sol";
import "../BaseIRMLinearKink.sol";
import "../vendor/RPow.sol";
interface IExec {
function getPriceFull(address underlying) external view returns (uint twap, uint twapPeriod, uint currPrice);
function getPrice(address underlying) external view returns (uint twap, uint twapPeriod);
function detailedLiquidity(address account) external view returns (IRiskManager.AssetLiquidity[] memory assets);
function liquidity(address account) external view returns (IRiskManager.LiquidityStatus memory status);
}
contract EulerGeneralView is Constants {
bytes32 immutable public moduleGitCommit;
constructor(bytes32 moduleGitCommit_) {
moduleGitCommit = moduleGitCommit_;
}
// Query
struct Query {
address eulerContract;
address account;
address[] markets;
}
// Response
struct ResponseMarket {
// Universal
address underlying;
string name;
string symbol;
uint8 decimals;
address eTokenAddr;
address dTokenAddr;
address pTokenAddr;
Storage.AssetConfig config;
uint poolSize;
uint totalBalances;
uint totalBorrows;
uint reserveBalance;
uint32 reserveFee;
uint borrowAPY;
uint supplyAPY;
// Pricing
uint twap;
uint twapPeriod;
uint currPrice;
uint16 pricingType;
uint32 pricingParameters;
address pricingForwarded;
// Account specific
uint underlyingBalance;
uint eulerAllowance;
uint eTokenBalance;
uint eTokenBalanceUnderlying;
uint dTokenBalance;
IRiskManager.LiquidityStatus liquidityStatus;
}
struct Response {
uint timestamp;
uint blockNumber;
ResponseMarket[] markets;
address[] enteredMarkets;
}
// Implementation
function doQueryBatch(Query[] memory qs) external view returns (Response[] memory r) {
r = new Response[](qs.length);
for (uint i = 0; i < qs.length; ++i) {
r[i] = doQuery(qs[i]);
}
}
function doQuery(Query memory q) public view returns (Response memory r) {
r.timestamp = block.timestamp;
r.blockNumber = block.number;
Euler eulerProxy = Euler(q.eulerContract);
Markets marketsProxy = Markets(eulerProxy.moduleIdToProxy(MODULEID__MARKETS));
IExec execProxy = IExec(eulerProxy.moduleIdToProxy(MODULEID__EXEC));
IRiskManager.AssetLiquidity[] memory liqs;
if (q.account != address(0)) {
liqs = execProxy.detailedLiquidity(q.account);
}
r.markets = new ResponseMarket[](liqs.length + q.markets.length);
for (uint i = 0; i < liqs.length; ++i) {
ResponseMarket memory m = r.markets[i];
m.underlying = liqs[i].underlying;
m.liquidityStatus = liqs[i].status;
populateResponseMarket(q, m, marketsProxy, execProxy);
}
for (uint j = liqs.length; j < liqs.length + q.markets.length; ++j) {
uint i = j - liqs.length;
ResponseMarket memory m = r.markets[j];
m.underlying = q.markets[i];
populateResponseMarket(q, m, marketsProxy, execProxy);
}
if (q.account != address(0)) {
r.enteredMarkets = marketsProxy.getEnteredMarkets(q.account);
}
}
function populateResponseMarket(Query memory q, ResponseMarket memory m, Markets marketsProxy, IExec execProxy) private view {
m.name = getStringOrBytes32(m.underlying, IERC20.name.selector);
m.symbol = getStringOrBytes32(m.underlying, IERC20.symbol.selector);
m.decimals = IERC20(m.underlying).decimals();
m.eTokenAddr = marketsProxy.underlyingToEToken(m.underlying);
if (m.eTokenAddr == address(0)) return; // not activated
m.dTokenAddr = marketsProxy.eTokenToDToken(m.eTokenAddr);
m.pTokenAddr = marketsProxy.underlyingToPToken(m.underlying);
{
Storage.AssetConfig memory c = marketsProxy.underlyingToAssetConfig(m.underlying);
m.config = c;
}
m.poolSize = IERC20(m.underlying).balanceOf(q.eulerContract);
m.totalBalances = EToken(m.eTokenAddr).totalSupplyUnderlying();
m.totalBorrows = IERC20(m.dTokenAddr).totalSupply();
m.reserveBalance = EToken(m.eTokenAddr).reserveBalanceUnderlying();
m.reserveFee = marketsProxy.reserveFee(m.underlying);
{
uint borrowSPY = uint(int(marketsProxy.interestRate(m.underlying)));
(m.borrowAPY, m.supplyAPY) = computeAPYs(borrowSPY, m.totalBorrows, m.totalBalances, m.reserveFee);
}
(m.twap, m.twapPeriod, m.currPrice) = execProxy.getPriceFull(m.underlying);
(m.pricingType, m.pricingParameters, m.pricingForwarded) = marketsProxy.getPricingConfig(m.underlying);
if (q.account == address(0)) return;
m.underlyingBalance = IERC20(m.underlying).balanceOf(q.account);
m.eTokenBalance = IERC20(m.eTokenAddr).balanceOf(q.account);
m.eTokenBalanceUnderlying = EToken(m.eTokenAddr).balanceOfUnderlying(q.account);
m.dTokenBalance = IERC20(m.dTokenAddr).balanceOf(q.account);
m.eulerAllowance = IERC20(m.underlying).allowance(q.account, q.eulerContract);
}
function computeAPYs(uint borrowSPY, uint totalBorrows, uint totalBalancesUnderlying, uint32 reserveFee) public pure returns (uint borrowAPY, uint supplyAPY) {
borrowAPY = RPow.rpow(borrowSPY + 1e27, SECONDS_PER_YEAR, 10**27) - 1e27;
uint supplySPY = totalBalancesUnderlying == 0 ? 0 : borrowSPY * totalBorrows / totalBalancesUnderlying;
supplySPY = supplySPY * (RESERVE_FEE_SCALE - reserveFee) / RESERVE_FEE_SCALE;
supplyAPY = RPow.rpow(supplySPY + 1e27, SECONDS_PER_YEAR, 10**27) - 1e27;
}
// Interest rate model queries
struct QueryIRM {
address eulerContract;
address underlying;
}
struct ResponseIRM {
uint kink;
uint baseAPY;
uint kinkAPY;
uint maxAPY;
uint baseSupplyAPY;
uint kinkSupplyAPY;
uint maxSupplyAPY;
}
function doQueryIRM(QueryIRM memory q) external view returns (ResponseIRM memory r) {
Euler eulerProxy = Euler(q.eulerContract);
Markets marketsProxy = Markets(eulerProxy.moduleIdToProxy(MODULEID__MARKETS));
uint moduleId = marketsProxy.interestRateModel(q.underlying);
address moduleImpl = eulerProxy.moduleIdToImplementation(moduleId);
BaseIRMLinearKink irm = BaseIRMLinearKink(moduleImpl);
uint kink = r.kink = irm.kink();
uint32 reserveFee = marketsProxy.reserveFee(q.underlying);
uint baseSPY = irm.baseRate();
uint kinkSPY = baseSPY + (kink * irm.slope1());
uint maxSPY = kinkSPY + ((type(uint32).max - kink) * irm.slope2());
(r.baseAPY, r.baseSupplyAPY) = computeAPYs(baseSPY, 0, type(uint32).max, reserveFee);
(r.kinkAPY, r.kinkSupplyAPY) = computeAPYs(kinkSPY, kink, type(uint32).max, reserveFee);
(r.maxAPY, r.maxSupplyAPY) = computeAPYs(maxSPY, type(uint32).max, type(uint32).max, reserveFee);
}
// AccountLiquidity queries
struct ResponseAccountLiquidity {
IRiskManager.AssetLiquidity[] markets;
}
function doQueryAccountLiquidity(address eulerContract, address[] memory addrs) external view returns (ResponseAccountLiquidity[] memory r) {
Euler eulerProxy = Euler(eulerContract);
IExec execProxy = IExec(eulerProxy.moduleIdToProxy(MODULEID__EXEC));
r = new ResponseAccountLiquidity[](addrs.length);
for (uint i = 0; i < addrs.length; ++i) {
r[i].markets = execProxy.detailedLiquidity(addrs[i]);
}
}
// For tokens like MKR which return bytes32 on name() or symbol()
function getStringOrBytes32(address contractAddress, bytes4 selector) private view returns (string memory) {
(bool success, bytes memory result) = contractAddress.staticcall(abi.encodeWithSelector(selector));
Iif (!success) return "";
return result.length == 32 ? string(abi.encodePacked(result)) : abi.decode(result, (string));
}
}
|