Skip to content

Commit 0b598fe

Browse files
committed
feat: ERC4626 lib (SC-1233)
1 parent 1f93c32 commit 0b598fe

File tree

3 files changed

+143
-63
lines changed

3 files changed

+143
-63
lines changed

src/MainnetController.sol

Lines changed: 29 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ApproveLib } from "./libraries/ApproveLib.sol";
2222
import { AaveLib } from "./libraries/AaveLib.sol";
2323
import { CCTPLib } from "./libraries/CCTPLib.sol";
2424
import { CurveLib } from "./libraries/CurveLib.sol";
25+
import { ERC4626Lib } from "./libraries/ERC4626Lib.sol";
2526
import { IDaiUsdsLike, IPSMLike, PSMLib } from "./libraries/PSMLib.sol";
2627

2728
import { OptionsBuilder } from "layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
@@ -133,8 +134,6 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
133134
/*** State variables ***/
134135
/**********************************************************************************************/
135136

136-
uint256 public constant EXCHANGE_RATE_PRECISION = 1e36;
137-
138137
bytes32 public FREEZER = keccak256("FREEZER");
139138
bytes32 public RELAYER = keccak256("RELAYER");
140139

@@ -304,7 +303,7 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
304303

305304
emit MaxExchangeRateSet(
306305
token,
307-
maxExchangeRates[token] = _getExchangeRate(shares, maxExpectedAssets)
306+
maxExchangeRates[token] = ERC4626Lib.getExchangeRate(shares, maxExpectedAssets)
308307
);
309308
}
310309

@@ -457,67 +456,49 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
457456
external nonReentrant returns (uint256 shares)
458457
{
459458
_checkRole(RELAYER);
460-
_rateLimitedAddress(LIMIT_4626_DEPOSIT, token, amount);
461-
462-
// Approve asset to token from the proxy (assumes the proxy has enough of the asset).
463-
ApproveLib.approve(IERC4626(token).asset(), address(proxy), token, amount);
464-
465-
// Deposit asset into the token, proxy receives token shares, decode the resulting shares.
466-
shares = abi.decode(
467-
proxy.doCall(
468-
token,
469-
abi.encodeCall(IERC4626(token).deposit, (amount, address(proxy)))
470-
),
471-
(uint256)
472-
);
473459

474-
require(
475-
_getExchangeRate(shares, amount) <= maxExchangeRates[token],
476-
"MC/exchange-rate-too-high"
477-
);
460+
return ERC4626Lib.deposit({
461+
proxy : address(proxy),
462+
token : token,
463+
amount : amount,
464+
maxExchangeRate : maxExchangeRates[token],
465+
rateLimits : address(rateLimits),
466+
rateLimitId : LIMIT_4626_DEPOSIT
467+
});
478468
}
479469

480470
function withdrawERC4626(address token, uint256 amount)
481471
external nonReentrant returns (uint256 shares)
482472
{
483473
_checkRole(RELAYER);
484-
_rateLimitedAddress(LIMIT_4626_WITHDRAW, token, amount);
485474

486-
// Withdraw asset from a token, decode resulting shares.
487-
// Assumes proxy has adequate token shares.
488-
shares = abi.decode(
489-
proxy.doCall(
490-
token,
491-
abi.encodeCall(IERC4626(token).withdraw, (amount, address(proxy), address(proxy)))
492-
),
493-
(uint256)
494-
);
495-
496-
_cancelRateLimit(RateLimitHelpers.makeAddressKey(LIMIT_4626_DEPOSIT, token), amount);
475+
return ERC4626Lib.withdraw({
476+
proxy : address(proxy),
477+
token : token,
478+
amount : amount,
479+
rateLimits : address(rateLimits),
480+
withdrawRateLimitId : LIMIT_4626_WITHDRAW,
481+
depositRateLimitId : LIMIT_4626_DEPOSIT
482+
});
497483
}
498484

499-
// NOTE: !!! Rate limited at end of function !!!
500485
function redeemERC4626(address token, uint256 shares)
501486
external nonReentrant returns (uint256 assets)
502487
{
503488
_checkRole(RELAYER);
504489

505-
// Redeem shares for assets from the token, decode the resulting assets.
506-
// Assumes proxy has adequate token shares.
507-
assets = abi.decode(
508-
proxy.doCall(
509-
token,
510-
abi.encodeCall(IERC4626(token).redeem, (shares, address(proxy), address(proxy)))
511-
),
512-
(uint256)
513-
);
514-
515-
rateLimits.triggerRateLimitDecrease(
516-
RateLimitHelpers.makeAddressKey(LIMIT_4626_WITHDRAW, token),
517-
assets
518-
);
490+
return ERC4626Lib.redeem({
491+
proxy : address(proxy),
492+
token : token,
493+
shares : shares,
494+
rateLimits : address(rateLimits),
495+
withdrawRateLimitId : LIMIT_4626_WITHDRAW,
496+
depositRateLimitId : LIMIT_4626_DEPOSIT
497+
});
498+
}
519499

520-
_cancelRateLimit(RateLimitHelpers.makeAddressKey(LIMIT_4626_DEPOSIT, token), assets);
500+
function EXCHANGE_RATE_PRECISION() external pure returns (uint256) {
501+
return ERC4626Lib.EXCHANGE_RATE_PRECISION;
521502
}
522503

523504
/**********************************************************************************************/
@@ -1053,18 +1034,4 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
10531034
);
10541035
}
10551036

1056-
/**********************************************************************************************/
1057-
/*** Exchange rate helper functions ***/
1058-
/**********************************************************************************************/
1059-
1060-
function _getExchangeRate(uint256 shares, uint256 assets) internal pure returns (uint256) {
1061-
// Return 0 for zero assets first, to handle the valid case of 0 shares and 0 assets.
1062-
if (assets == 0) return 0;
1063-
1064-
// Zero shares with non-zero assets is invalid (infinite exchange rate).
1065-
if (shares == 0) revert("MC/zero-shares");
1066-
1067-
return (EXCHANGE_RATE_PRECISION * assets) / shares;
1068-
}
1069-
10701037
}

src/libraries/AaveLib.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { IPool } from "aave-v3-origin/src/core/contracts/interfaces/IPool.sol"
66

77
import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
88

9-
import { IALMProxy } from "../interfaces/IALMProxy.sol";
9+
import { IALMProxy } from "../interfaces/IALMProxy.sol";
1010
import { IRateLimits } from "../interfaces/IRateLimits.sol";
1111

1212
import { ApproveLib } from "./ApproveLib.sol";

src/libraries/ERC4626Lib.sol

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
pragma solidity ^0.8.21;
3+
4+
import { IERC4626 } from "../../lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
5+
6+
import { IALMProxy } from "../interfaces/IALMProxy.sol";
7+
import { IRateLimits } from "../interfaces/IRateLimits.sol";
8+
9+
import { ApproveLib } from "./ApproveLib.sol";
10+
11+
import { RateLimitHelpers } from "../RateLimitHelpers.sol";
12+
13+
library ERC4626Lib {
14+
15+
uint256 internal constant EXCHANGE_RATE_PRECISION = 1e36;
16+
17+
function deposit(
18+
address proxy,
19+
address token,
20+
uint256 amount,
21+
uint256 maxExchangeRate,
22+
address rateLimits,
23+
bytes32 rateLimitId
24+
) external returns (uint256 shares) {
25+
IRateLimits(rateLimits).triggerRateLimitDecrease(
26+
RateLimitHelpers.makeAddressKey(rateLimitId, token),
27+
amount
28+
);
29+
30+
// Approve asset to token from the proxy (assumes the proxy has enough of the asset).
31+
ApproveLib.approve(IERC4626(token).asset(), proxy, token, amount);
32+
33+
// Deposit asset into the token, proxy receives token shares, decode the resulting shares.
34+
shares = abi.decode(
35+
IALMProxy(proxy).doCall(
36+
token,
37+
abi.encodeCall(IERC4626(token).deposit, (amount, proxy))
38+
),
39+
(uint256)
40+
);
41+
42+
require(getExchangeRate(shares, amount) <= maxExchangeRate, "MC/exchange-rate-too-high");
43+
}
44+
45+
function withdraw(
46+
address proxy,
47+
address token,
48+
uint256 amount,
49+
address rateLimits,
50+
bytes32 withdrawRateLimitId,
51+
bytes32 depositRateLimitId
52+
) external returns (uint256 shares) {
53+
IRateLimits(rateLimits).triggerRateLimitDecrease(
54+
RateLimitHelpers.makeAddressKey(withdrawRateLimitId, token),
55+
amount
56+
);
57+
58+
// Withdraw asset from a token, decode resulting shares.
59+
// Assumes proxy has adequate token shares.
60+
shares = abi.decode(
61+
IALMProxy(proxy).doCall(
62+
token,
63+
abi.encodeCall(IERC4626(token).withdraw, (amount, proxy, proxy))
64+
),
65+
(uint256)
66+
);
67+
68+
IRateLimits(rateLimits).triggerRateLimitIncrease(
69+
RateLimitHelpers.makeAddressKey(depositRateLimitId, token),
70+
amount
71+
);
72+
}
73+
74+
function redeem(
75+
address proxy,
76+
address token,
77+
uint256 shares,
78+
address rateLimits,
79+
bytes32 withdrawRateLimitId,
80+
bytes32 depositRateLimitId
81+
) external returns (uint256 assets) {
82+
// Redeem shares for assets from the token, decode the resulting assets.
83+
// Assumes proxy has adequate token shares.
84+
assets = abi.decode(
85+
IALMProxy(proxy).doCall(
86+
token,
87+
abi.encodeCall(IERC4626(token).redeem, (shares, proxy, proxy))
88+
),
89+
(uint256)
90+
);
91+
92+
IRateLimits(rateLimits).triggerRateLimitDecrease(
93+
RateLimitHelpers.makeAddressKey(withdrawRateLimitId, token),
94+
assets
95+
);
96+
97+
IRateLimits(rateLimits).triggerRateLimitIncrease(
98+
RateLimitHelpers.makeAddressKey(depositRateLimitId, token),
99+
assets
100+
);
101+
}
102+
103+
function getExchangeRate(uint256 shares, uint256 assets) public pure returns (uint256) {
104+
// Return 0 for zero assets first, to handle the valid case of 0 shares and 0 assets.
105+
if (assets == 0) return 0;
106+
107+
// Zero shares with non-zero assets is invalid (infinite exchange rate).
108+
if (shares == 0) revert("MC/zero-shares");
109+
110+
return (EXCHANGE_RATE_PRECISION * assets) / shares;
111+
}
112+
113+
}

0 commit comments

Comments
 (0)