forked from Rari-Capital/vaults
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathVaultRouterModule.sol
304 lines (253 loc) · 14 KB
/
VaultRouterModule.sol
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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.10;
import {WETH} from "solmate/tokens/WETH.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {AllowedPermit} from "../interfaces/AllowedPermit.sol";
import {Vault} from "../Vault.sol";
/// @title Rari Vault Router Module
/// @author Transmissions11 and JetJadeja
/// @notice Module that enables depositing ETH into WETH compatible Vaults
/// and approval-free deposits into Vaults with permit compatible underlying.
contract VaultRouterModule {
using SafeTransferLib for ERC20;
using SafeTransferLib for address;
using FixedPointMathLib for uint256;
/*///////////////////////////////////////////////////////////////
DEPOSIT LOGIC
//////////////////////////////////////////////////////////////*/
/// @notice Deposit ETH into a WETH compatible Vault.
/// @param vault The WETH compatible Vault to deposit into.
function depositETHIntoVault(Vault vault) external payable {
// Ensure the Vault's underlying is stored as WETH compatible.
require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH");
// Get the Vault's underlying as WETH.
WETH weth = WETH(payable(address(vault.UNDERLYING())));
// Wrap the ETH into WETH.
weth.deposit{value: msg.value}();
// Deposit and transfer the minted rvTokens back to the caller.
depositIntoVaultForCaller(vault, weth, msg.value);
}
/// @notice Deposits into a Vault, transferring in its underlying token from the caller via permit.
/// @param vault The Vault to deposit into.
/// @param underlyingAmount The amount of underlying tokens to deposit into the Vault.
/// @param deadline A timestamp, the block's timestamp must be less than or equal to this timestamp.
/// @param v Must produce valid secp256k1 signature from the caller along with r and s.
/// @param r Must produce valid secp256k1 signature from the caller along with v and s.
/// @param s Must produce valid secp256k1 signature from the caller along with r and v.
/// @dev Use depositIntoVaultWithAllowedPermit for tokens using DAI's non-standard permit interface.
function depositIntoVaultWithPermit(
Vault vault,
uint256 underlyingAmount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// Get the Vault's underlying token.
ERC20 underlying = vault.UNDERLYING();
// Transfer in the provided amount of underlying tokens from the caller via permit.
permitAndTransferFromCaller(underlying, underlyingAmount, deadline, v, r, s);
// Deposit and transfer the minted rvTokens back to the caller.
depositIntoVaultForCaller(vault, underlying, underlyingAmount);
}
/// @notice Deposits into a Vault, transferring in its underlying token from the caller via allowed permit.
/// @param vault The Vault to deposit into.
/// @param underlyingAmount The amount of underlying tokens to deposit into the Vault.
/// @param nonce The callers's nonce, increases at each call to permit.
/// @param expiry The timestamp at which the permit is no longer valid.
/// @param v Must produce valid secp256k1 signature from the caller along with r and s.
/// @param r Must produce valid secp256k1 signature from the caller along with v and s.
/// @param s Must produce valid secp256k1 signature from the caller along with r and v.
/// @dev Alternative to depositIntoVaultWithPermit for tokens using DAI's non-standard permit interface.
function depositIntoVaultWithAllowedPermit(
Vault vault,
uint256 underlyingAmount,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) external {
// Get the Vault's underlying token.
ERC20 underlying = vault.UNDERLYING();
// Transfer in the provided amount of underlying tokens from the caller via allowed permit.
allowedPermitAndTransferFromCaller(underlying, underlyingAmount, nonce, expiry, v, r, s);
// Deposit and transfer the minted rvTokens back to the caller.
depositIntoVaultForCaller(vault, underlying, underlyingAmount);
}
/*///////////////////////////////////////////////////////////////
WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
/// @notice Withdraw ETH from a WETH compatible Vault.
/// @param vault The WETH compatible Vault to withdraw from.
/// @param underlyingAmount The amount of ETH to withdraw from the Vault.
function withdrawETHFromVault(Vault vault, uint256 underlyingAmount) external {
// Ensure the Vault's underlying is stored as WETH compatible.
require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH");
// Compute the amount of rvTokens equivalent to the underlying amount.
// We know the Vault's base unit is 1e18 as it's required for underlyingIsWETH to be true.
uint256 rvTokenAmount = underlyingAmount.divWadDown(vault.convertToAssets(10**vault.decimals()));
// Transfer in the equivalent amount of rvTokens from the caller.
ERC20(vault).safeTransferFrom(msg.sender, address(this), rvTokenAmount);
// Withdraw from the Vault.
vault.withdraw(underlyingAmount, address(this), address(this));
// Unwrap the withdrawn amount of WETH and transfer it to the caller.
unwrapAndTransfer(WETH(payable(address(vault.UNDERLYING()))), underlyingAmount);
}
/// @notice Withdraw ETH from a WETH compatible Vault.
/// @param vault The WETH compatible Vault to withdraw from.
/// @param underlyingAmount The amount of ETH to withdraw from the Vault.
/// @param deadline A timestamp, the block's timestamp must be less than or equal to this timestamp.
/// @param v Must produce valid secp256k1 signature from the caller along with r and s.
/// @param r Must produce valid secp256k1 signature from the caller along with v and s.
/// @param s Must produce valid secp256k1 signature from the caller along with r and v.
function withdrawETHFromVaultWithPermit(
Vault vault,
uint256 underlyingAmount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// Ensure the Vault's underlying is stored as WETH compatible.
require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH");
// Compute the amount of rvTokens equivalent to the underlying amount.
// We know the Vault's base unit is 1e18 as it's required for underlyingIsWETH to be true.
uint256 rvTokenAmount = underlyingAmount.divWadDown(vault.convertToAssets(10**vault.decimals()));
// Transfer in the equivalent amount of rvTokens from the caller via permit.
permitAndTransferFromCaller(vault, rvTokenAmount, deadline, v, r, s);
// Withdraw from the Vault.
vault.withdraw(underlyingAmount, address(this), address(this));
// Unwrap the withdrawn amount of WETH and transfer it to the caller.
unwrapAndTransfer(WETH(payable(address(vault.UNDERLYING()))), underlyingAmount);
}
/*///////////////////////////////////////////////////////////////
REDEEM LOGIC
//////////////////////////////////////////////////////////////*/
/// @notice Redeem ETH from a WETH compatible Vault.
/// @param vault The WETH compatible Vault to redeem from.
/// @param rvTokenAmount The amount of rvTokens to withdraw from the Vault.
function redeemETHFromVault(Vault vault, uint256 rvTokenAmount) external {
// Ensure the Vault's underlying is stored as WETH compatible.
require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH");
// Transfer in the provided amount of rvTokens from the caller.
ERC20(vault).safeTransferFrom(msg.sender, address(this), rvTokenAmount);
// Redeem the rvTokens.
vault.redeem(rvTokenAmount, address(this), address(this));
// Get the Vault's underlying as WETH.
WETH weth = WETH(payable(address(vault.UNDERLYING())));
// Unwrap all our WETH and transfer it to the caller.
unwrapAndTransfer(weth, weth.balanceOf(address(this)));
}
/// @notice Redeem ETH from a WETH compatible Vault.
/// @param vault The WETH compatible Vault to redeem from.
/// @param rvTokenAmount The amount of rvTokens to withdraw from the Vault.
/// @param deadline A timestamp, the block's timestamp must be less than or equal to this timestamp.
/// @param v Must produce valid secp256k1 signature from the caller along with r and s.
/// @param r Must produce valid secp256k1 signature from the caller along with v and s.
/// @param s Must produce valid secp256k1 signature from the caller along with r and v.
function redeemETHFromVaultWithPermit(
Vault vault,
uint256 rvTokenAmount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// Ensure the Vault's underlying is stored as WETH compatible.
require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH");
// Transfer in the provided amount of rvTokens from the caller via permit.
permitAndTransferFromCaller(vault, rvTokenAmount, deadline, v, r, s);
// Redeem the rvTokens.
vault.redeem(rvTokenAmount, address(this), address(this));
// Get the Vault's underlying as WETH.
WETH weth = WETH(payable(address(vault.UNDERLYING())));
// Unwrap all our WETH and transfer it to the caller.
unwrapAndTransfer(weth, weth.balanceOf(address(this)));
}
/*///////////////////////////////////////////////////////////////
WETH UNWRAPPING LOGIC
//////////////////////////////////////////////////////////////*/
/// @dev Unwraps the provided amount of WETH and transfers it to the caller.
/// @param weth The WETH contract to withdraw the amount from.
/// @param amount The amount of WETH to unwrap into ETH and transfer.
function unwrapAndTransfer(WETH weth, uint256 amount) internal {
// Convert the WETH into ETH.
weth.withdraw(amount);
// Transfer the unwrapped ETH to the caller.
msg.sender.safeTransferETH(amount);
}
/*///////////////////////////////////////////////////////////////
VAULT DEPOSIT LOGIC
//////////////////////////////////////////////////////////////*/
/// @dev Approves tokens, deposits them into a Vault
/// and transfers the minted rvTokens back to the caller.
/// @param vault The Vault to deposit into.
/// @param underlying The underlying token the Vault accepts.
/// @param amount The minimum amount that must be approved.
function depositIntoVaultForCaller(
Vault vault,
ERC20 underlying,
uint256 amount
) internal {
// Approve the underlying tokens to the Vault.
underlying.safeApprove(address(vault), amount);
// Deposit the underlying tokens into the Vault.
vault.deposit(amount, address(this));
// Transfer the newly minted rvTokens back to the caller.
ERC20(vault).safeTransfer(msg.sender, vault.balanceOf(address(this)));
}
/*///////////////////////////////////////////////////////////////
PERMIT LOGIC
//////////////////////////////////////////////////////////////*/
/// @dev Permits tokens from the caller and transfers them into the module.
/// @param token The token to permit and transfer in.
/// @param amount The amount of tokens to permit and transfer in.
/// @param deadline A timestamp, the block's timestamp must be less than or equal to this timestamp.
/// @param v Must produce valid secp256k1 signature from the caller along with r and s.
/// @param r Must produce valid secp256k1 signature from the caller along with v and s.
/// @param s Must produce valid secp256k1 signature from the caller along with r and v.
function permitAndTransferFromCaller(
ERC20 token,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
// Approve the tokens from the caller to the module via permit.
token.permit(msg.sender, address(this), amount, deadline, v, r, s);
// Transfer the tokens from the caller to the module.
token.safeTransferFrom(msg.sender, address(this), amount);
}
/// @dev Max permits tokens from the caller and transfers them into the module.
/// @param token The token to permit and transfer in.
/// @param amount The amount of tokens to permit and transfer in.
/// @param nonce The callers's nonce, increases at each call to permit.
/// @param expiry The timestamp at which the permit is no longer valid.
/// @param v Must produce valid secp256k1 signature from the caller along with r and s.
/// @param r Must produce valid secp256k1 signature from the caller along with v and s.
/// @param s Must produce valid secp256k1 signature from the caller along with r and v.
/// @dev Alternative to permitAndTransferFromCaller for tokens using DAI's non-standard permit interface.
function allowedPermitAndTransferFromCaller(
ERC20 token,
uint256 amount,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) internal {
// Approve the tokens from the caller to the module via DAI's non-standard permit.
AllowedPermit(address(token)).permit(msg.sender, address(this), nonce, expiry, true, v, r, s);
// Transfer the tokens from the caller to the module.
token.safeTransferFrom(msg.sender, address(this), amount);
}
/*///////////////////////////////////////////////////////////////
RECIEVE ETHER LOGIC
//////////////////////////////////////////////////////////////*/
/// @dev Required for the module to receive unwrapped ETH.
receive() external payable {}
}