Skip to content

Commit 7a87195

Browse files
committed
feat: ERC4626 lib (SC-1233)
1 parent 943acae commit 7a87195

File tree

3 files changed

+143
-71
lines changed

3 files changed

+143
-71
lines changed

src/MainnetController.sol

Lines changed: 29 additions & 70 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";
@@ -82,14 +83,6 @@ interface IWstETHLike {
8283
function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256);
8384
}
8485

85-
struct OTC {
86-
address buffer;
87-
uint256 rechargeRate18;
88-
uint256 sent18;
89-
uint256 sentTimestamp;
90-
uint256 claimed18;
91-
}
92-
9386
contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
9487

9588
using OptionsBuilder for bytes;
@@ -141,8 +134,6 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
141134
/*** State variables ***/
142135
/**********************************************************************************************/
143136

144-
uint256 public constant EXCHANGE_RATE_PRECISION = 1e36;
145-
146137
bytes32 public FREEZER = keccak256("FREEZER");
147138
bytes32 public RELAYER = keccak256("RELAYER");
148139

@@ -312,7 +303,7 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
312303

313304
emit MaxExchangeRateSet(
314305
token,
315-
maxExchangeRates[token] = _getExchangeRate(shares, maxExpectedAssets)
306+
maxExchangeRates[token] = ERC4626Lib.getExchangeRate(shares, maxExpectedAssets)
316307
);
317308
}
318309

@@ -465,67 +456,49 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
465456
external nonReentrant returns (uint256 shares)
466457
{
467458
_checkRole(RELAYER);
468-
_rateLimitedAddress(LIMIT_4626_DEPOSIT, token, amount);
469-
470-
// Approve asset to token from the proxy (assumes the proxy has enough of the asset).
471-
ApproveLib.approve(IERC4626(token).asset(), address(proxy), token, amount);
472-
473-
// Deposit asset into the token, proxy receives token shares, decode the resulting shares.
474-
shares = abi.decode(
475-
proxy.doCall(
476-
token,
477-
abi.encodeCall(IERC4626(token).deposit, (amount, address(proxy)))
478-
),
479-
(uint256)
480-
);
481459

482-
require(
483-
_getExchangeRate(shares, amount) <= maxExchangeRates[token],
484-
"MC/exchange-rate-too-high"
485-
);
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+
});
486468
}
487469

488470
function withdrawERC4626(address token, uint256 amount)
489471
external nonReentrant returns (uint256 shares)
490472
{
491473
_checkRole(RELAYER);
492-
_rateLimitedAddress(LIMIT_4626_WITHDRAW, token, amount);
493-
494-
// Withdraw asset from a token, decode resulting shares.
495-
// Assumes proxy has adequate token shares.
496-
shares = abi.decode(
497-
proxy.doCall(
498-
token,
499-
abi.encodeCall(IERC4626(token).withdraw, (amount, address(proxy), address(proxy)))
500-
),
501-
(uint256)
502-
);
503474

504-
_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+
});
505483
}
506484

507-
// NOTE: !!! Rate limited at end of function !!!
508485
function redeemERC4626(address token, uint256 shares)
509486
external nonReentrant returns (uint256 assets)
510487
{
511488
_checkRole(RELAYER);
512489

513-
// Redeem shares for assets from the token, decode the resulting assets.
514-
// Assumes proxy has adequate token shares.
515-
assets = abi.decode(
516-
proxy.doCall(
517-
token,
518-
abi.encodeCall(IERC4626(token).redeem, (shares, address(proxy), address(proxy)))
519-
),
520-
(uint256)
521-
);
522-
523-
rateLimits.triggerRateLimitDecrease(
524-
RateLimitHelpers.makeAddressKey(LIMIT_4626_WITHDRAW, token),
525-
assets
526-
);
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+
}
527499

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

531504
/**********************************************************************************************/
@@ -1061,18 +1034,4 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
10611034
);
10621035
}
10631036

1064-
/**********************************************************************************************/
1065-
/*** Exchange rate helper functions ***/
1066-
/**********************************************************************************************/
1067-
1068-
function _getExchangeRate(uint256 shares, uint256 assets) internal pure returns (uint256) {
1069-
// Return 0 for zero assets first, to handle the valid case of 0 shares and 0 assets.
1070-
if (assets == 0) return 0;
1071-
1072-
// Zero shares with non-zero assets is invalid (infinite exchange rate).
1073-
if (shares == 0) revert("MC/zero-shares");
1074-
1075-
return (EXCHANGE_RATE_PRECISION * assets) / shares;
1076-
}
1077-
10781037
}

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)