Skip to content

Commit e8315ca

Browse files
supercontractsdeluca-mikelucas-manuel
authored
feat: Add minOutShares to weETH (DEV-1121) (#223)
* add: minOutShares to weETH * file name change 1 * file name change * fix: review * fix: old import naming --------- Co-authored-by: Michael De Luca <michael.deluca@circle-free.com> Co-authored-by: Lucas Manuel <lucasmanuel.tech@gmail.com>
1 parent 8883ef8 commit e8315ca

File tree

6 files changed

+83
-34
lines changed

6 files changed

+83
-34
lines changed

src/MainnetController.sol

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { ERC4626Lib } from "./libraries/ERC4626Lib.sol";
2424
import { LayerZeroLib } from "./libraries/LayerZeroLib.sol";
2525
import { IDaiUsdsLike, IPSMLike, PSMLib } from "./libraries/PSMLib.sol";
2626
import { UniswapV4Lib } from "./libraries/UniswapV4Lib.sol";
27-
import { WeETHLib } from "./libraries/WeETHLib.sol";
27+
import { WEETHLib } from "./libraries/WEETHLib.sol";
2828

2929
import { RateLimitHelpers } from "./RateLimitHelpers.sol";
3030

@@ -165,9 +165,9 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
165165
bytes32 public LIMIT_USDE_MINT = keccak256("LIMIT_USDE_MINT");
166166
bytes32 public LIMIT_USDS_MINT = keccak256("LIMIT_USDS_MINT");
167167
bytes32 public LIMIT_USDS_TO_USDC = keccak256("LIMIT_USDS_TO_USDC");
168-
bytes32 public LIMIT_WEETH_CLAIM_WITHDRAW = WeETHLib.LIMIT_WEETH_CLAIM_WITHDRAW;
169-
bytes32 public LIMIT_WEETH_DEPOSIT = WeETHLib.LIMIT_WEETH_DEPOSIT;
170-
bytes32 public LIMIT_WEETH_REQUEST_WITHDRAW = WeETHLib.LIMIT_WEETH_REQUEST_WITHDRAW;
168+
bytes32 public LIMIT_WEETH_CLAIM_WITHDRAW = WEETHLib.LIMIT_WEETH_CLAIM_WITHDRAW;
169+
bytes32 public LIMIT_WEETH_DEPOSIT = WEETHLib.LIMIT_WEETH_DEPOSIT;
170+
bytes32 public LIMIT_WEETH_REQUEST_WITHDRAW = WEETHLib.LIMIT_WEETH_REQUEST_WITHDRAW;
171171
bytes32 public LIMIT_WSTETH_DEPOSIT = keccak256("LIMIT_WSTETH_DEPOSIT");
172172
bytes32 public LIMIT_WSTETH_REQUEST_WITHDRAW = keccak256("LIMIT_WSTETH_REQUEST_WITHDRAW");
173173

@@ -281,7 +281,7 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
281281

282282
// Prevent rotating buffer while a swap is pending and not ready
283283
require(otc.sentTimestamp == 0 || isOtcSwapReady(exchange), "MC/swap-in-progress");
284-
284+
285285
emit OTCBufferSet(exchange, otc.buffer, otcBuffer);
286286
otc.buffer = otcBuffer;
287287
}
@@ -493,13 +493,19 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
493493
/*** weETH Integration ***/
494494
/**********************************************************************************************/
495495

496-
function depositToWeETH(uint256 amount) external nonReentrant returns (uint256 shares) {
496+
function depositToWeETH(
497+
uint256 amount,
498+
uint256 minSharesOut
499+
)
500+
external nonReentrant returns (uint256 shares)
501+
{
497502
_checkRole(RELAYER);
498503

499-
shares = WeETHLib.deposit({
500-
proxy : proxy,
501-
rateLimits : rateLimits,
502-
amount : amount
504+
shares = WEETHLib.deposit({
505+
proxy : proxy,
506+
rateLimits : rateLimits,
507+
amount : amount,
508+
minSharesOut : minSharesOut
503509
});
504510
}
505511

@@ -511,7 +517,7 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
511517
{
512518
_checkRole(RELAYER);
513519

514-
requestId = WeETHLib.requestWithdraw({
520+
requestId = WEETHLib.requestWithdraw({
515521
proxy : proxy,
516522
rateLimits : rateLimits,
517523
weETHShares : weETHShares,
@@ -527,7 +533,7 @@ contract MainnetController is ReentrancyGuard, AccessControlEnumerable {
527533
{
528534
_checkRole(RELAYER);
529535

530-
ethReceived = WeETHLib.claimWithdrawal({
536+
ethReceived = WEETHLib.claimWithdrawal({
531537
proxy : proxy,
532538
rateLimits : rateLimits,
533539
requestId : requestId,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import { IERC20Metadata as IERC20 } from "openzeppelin-contracts/contracts/token
66

77
import { Ethereum } from "spark-address-registry/Ethereum.sol";
88

9-
import { IEETHLike, ILiquidityPoolLike, IWETHLike, IWEETHLike } from "./libraries/WeETHLib.sol";
9+
import { IEETHLike, ILiquidityPoolLike, IWETHLike, IWEETHLike } from "./libraries/WEETHLib.sol";
1010

1111
interface IWithdrawRequestNFTLike {
1212
function claimWithdraw(uint256 requestId) external;
1313
function isFinalized(uint256 requestId) external view returns (bool);
1414
function isValid(uint256 requestId) external view returns (bool);
1515
}
1616

17-
contract WeEthModule is AccessControlEnumerable {
17+
contract WEETHModule is AccessControlEnumerable {
1818

1919
address public immutable almProxy;
2020

src/libraries/LayerZeroLib.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { OptionsBuilder } from "layerzerolabs/oapp-evm/contracts/oapp/libs/Optio
1212
library LayerZeroLib {
1313

1414
using OptionsBuilder for bytes;
15-
15+
1616
bytes32 public constant LIMIT_LAYERZERO_TRANSFER = keccak256("LIMIT_LAYERZERO_TRANSFER");
1717

1818
/**********************************************************************************************/

src/libraries/PSMLib.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ library PSMLib {
126126
/*** Helper functions ***/
127127
/**********************************************************************************************/
128128

129-
// NOTE: As swaps are only done between USDC and USDS and vice versa, using `_forceApprove`
129+
// NOTE: As swaps are only done between USDC and USDS and vice versa, using `_forceApprove`
130130
// is unnecessary.
131131
function _approve(
132132
IALMProxy proxy,
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ interface IWETHLike {
4040
function withdraw(uint256 amount) external;
4141
}
4242

43-
library WeETHLib {
43+
library WEETHLib {
4444

4545
bytes32 public constant LIMIT_WEETH_CLAIM_WITHDRAW = keccak256("LIMIT_WEETH_CLAIM_WITHDRAW");
4646
bytes32 public constant LIMIT_WEETH_DEPOSIT = keccak256("LIMIT_WEETH_DEPOSIT");
@@ -53,7 +53,8 @@ library WeETHLib {
5353
function deposit(
5454
IALMProxy proxy,
5555
IRateLimits rateLimits,
56-
uint256 amount
56+
uint256 amount,
57+
uint256 minSharesOut
5758
) external returns (uint256 shares) {
5859
_rateLimited(rateLimits, LIMIT_WEETH_DEPOSIT, amount);
5960

@@ -88,6 +89,8 @@ library WeETHLib {
8889
),
8990
(uint256)
9091
);
92+
93+
require(shares >= minSharesOut, "MC/slippage-too-high");
9194
}
9295

9396
function requestWithdraw(

test/mainnet-fork/weETH.t.sol

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ pragma solidity >=0.8.0;
33

44
import { ReentrancyGuard } from "../../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
55

6-
import { IWEETHLike, ILiquidityPoolLike, IEETHLike } from "../../src/libraries/WeETHLib.sol";
6+
import { IWEETHLike, IEETHLike } from "../../src/libraries/WEETHLib.sol";
77

8-
import { WeEthModule } from "../../src/WeEthModule.sol";
8+
import { WEETHModule } from "../../src/WEETHModule.sol";
99

1010
import "./ForkTestBase.t.sol";
1111

12+
interface ILiquidityPoolLike {
13+
function amountForShare(uint256 shareAmount) external view returns (uint256);
14+
function sharesForAmount(uint256 amount) external view returns (uint256);
15+
function withdrawRequestNFT() external view returns (address);
16+
}
17+
1218
interface IWithdrawRequestNFTLike {
1319
function finalizeRequests(uint256 requestId) external;
1420
function getClaimableAmount(uint256 requestId) external view returns (uint256);
@@ -39,21 +45,25 @@ contract MainnetControllerWeETHTestBase is ForkTestBase {
3945
eETH = IEETHLike(address(IWEETHLike(Ethereum.WEETH).eETH()));
4046
liquidityPool = ILiquidityPoolLike(IEETHLike(eETH).liquidityPool());
4147

42-
weETHModule = address(new WeEthModule(Ethereum.SPARK_PROXY, address(almProxy)));
48+
weETHModule = address(new WEETHModule(Ethereum.SPARK_PROXY, address(almProxy)));
4349
}
4450

4551
function _getBlock() internal override pure returns (uint256) {
4652
return 23469772; // September 29, 2025
4753
}
4854

55+
function _getMinSharesOut(uint256 amount) internal view returns (uint256) {
56+
return liquidityPool.sharesForAmount(amount) - 1;
57+
}
58+
4959
}
5060

5161
contract MainnetControllerDepositToWeETHFailureTests is MainnetControllerWeETHTestBase {
5262

5363
function test_depositToWeETH_reentrancy() external {
5464
_setControllerEntered();
5565
vm.expectRevert(ReentrancyGuard.ReentrancyGuardReentrantCall.selector);
56-
mainnetController.depositToWeETH(1e18);
66+
mainnetController.depositToWeETH(1e18, 0);
5767
}
5868

5969
function test_depositToWeETH_notRelayer() external {
@@ -62,13 +72,13 @@ contract MainnetControllerDepositToWeETHFailureTests is MainnetControllerWeETHTe
6272
address(this),
6373
RELAYER
6474
));
65-
mainnetController.depositToWeETH(1e18);
75+
mainnetController.depositToWeETH(1e18, 0);
6676
}
6777

6878
function test_depositToWeETH_zeroMaxAmount() external {
6979
vm.prank(relayer);
7080
vm.expectRevert("RateLimits/zero-maxAmount");
71-
mainnetController.depositToWeETH(1e18);
81+
mainnetController.depositToWeETH(1e18, 0);
7282
}
7383

7484
function test_depositToWeETH_rateLimitsBoundary() external {
@@ -81,10 +91,28 @@ contract MainnetControllerDepositToWeETHFailureTests is MainnetControllerWeETHTe
8191

8292
vm.prank(relayer);
8393
vm.expectRevert("RateLimits/rate-limit-exceeded");
84-
mainnetController.depositToWeETH(1_000e18 + 1);
94+
mainnetController.depositToWeETH(1_000e18 + 1, 0);
8595

8696
vm.prank(relayer);
87-
mainnetController.depositToWeETH(1_000e18);
97+
mainnetController.depositToWeETH(1_000e18, 0);
98+
}
99+
100+
function test_depositToWeETH_slippageTooHighBoundary() external {
101+
bytes32 key = mainnetController.LIMIT_WEETH_DEPOSIT();
102+
103+
vm.prank(Ethereum.SPARK_PROXY);
104+
rateLimits.setRateLimitData(key, 1_000e18, uint256(1_000e18) / 1 days);
105+
106+
deal(Ethereum.WETH, address(almProxy), 1_000e18);
107+
108+
uint256 minSharesOut = _getMinSharesOut(1_000e18);
109+
110+
vm.prank(relayer);
111+
vm.expectRevert("MC/slippage-too-high");
112+
mainnetController.depositToWeETH(1_000e18, minSharesOut + 1);
113+
114+
vm.prank(relayer);
115+
mainnetController.depositToWeETH(1_000e18, minSharesOut);
88116
}
89117

90118
}
@@ -109,10 +137,12 @@ contract MainnetControllerDepositToWeETHTests is MainnetControllerWeETHTestBase
109137
assertEq(weETH.balanceOf(address(almProxy)), 0);
110138
assertEq(address(liquidityPool).balance, initialLiquidityPoolBalance);
111139

140+
uint256 minSharesOut = _getMinSharesOut(1_000e18);
141+
112142
vm.record();
113143

114144
vm.prank(relayer);
115-
uint256 shares = mainnetController.depositToWeETH(1_000e18);
145+
uint256 shares = mainnetController.depositToWeETH(1_000e18, minSharesOut);
116146

117147
_assertReentrancyGuardWrittenToTwice();
118148

@@ -197,8 +227,10 @@ contract MainnetControllerRequestWithdrawFromWeETHTests is MainnetControllerWeET
197227

198228
deal(Ethereum.WETH, address(almProxy), 1_000e18);
199229

230+
uint256 minSharesOut = _getMinSharesOut(1_000e18);
231+
200232
vm.prank(relayer);
201-
mainnetController.depositToWeETH(1_000e18);
233+
mainnetController.depositToWeETH(1_000e18, minSharesOut);
202234

203235
uint256 initialWeETHBalance = weETH.balanceOf(address(almProxy));
204236

@@ -229,7 +261,7 @@ contract MainnetControllerRequestWithdrawFromWeETHTests is MainnetControllerWeET
229261

230262
vm.prank(WITHDRAW_REQUEST_NFT_ADMIN);
231263
IWithdrawRequestNFTLike(withdrawRequestNFT).finalizeRequests(requestId);
232-
264+
233265
assertEq(withdrawRequestNFT.isFinalized(requestId), true);
234266
assertEq(withdrawRequestNFT.getClaimableAmount(requestId), expectedEEthBalance - 1); // Rounding error
235267

@@ -274,8 +306,10 @@ contract MainnetControllerClaimWithdrawalFromWeETHFailureTests is MainnetControl
274306

275307
deal(Ethereum.WETH, address(almProxy), 1_000e18);
276308

309+
uint256 minSharesOut = _getMinSharesOut(1_000e18);
310+
277311
vm.prank(relayer);
278-
mainnetController.depositToWeETH(1_000e18);
312+
mainnetController.depositToWeETH(1_000e18, minSharesOut);
279313

280314
vm.record();
281315

@@ -321,8 +355,10 @@ contract MainnetControllerClaimWithdrawalFromWeETHFailureTests is MainnetControl
321355

322356
deal(Ethereum.WETH, address(almProxy), 1_000e18);
323357

358+
uint256 minSharesOut = _getMinSharesOut(1_000e18);
359+
324360
vm.prank(relayer);
325-
mainnetController.depositToWeETH(1_000e18);
361+
mainnetController.depositToWeETH(1_000e18, minSharesOut);
326362

327363
vm.record();
328364

@@ -335,7 +371,7 @@ contract MainnetControllerClaimWithdrawalFromWeETHFailureTests is MainnetControl
335371

336372
vm.prank(WITHDRAW_REQUEST_NFT_ADMIN);
337373
IWithdrawRequestNFTLike(withdrawRequestNFT).invalidateRequest(requestId);
338-
374+
339375
vm.prank(relayer);
340376
vm.expectRevert("WeEthModule/invalid-request-id");
341377
mainnetController.claimWithdrawalFromWeETH(weETHModule, requestId);
@@ -362,8 +398,10 @@ contract MainnetControllerClaimWithdrawalFromWeETHFailureTests is MainnetControl
362398

363399
deal(Ethereum.WETH, address(almProxy), 1_000e18);
364400

401+
uint256 minSharesOut = _getMinSharesOut(1_000e18);
402+
365403
vm.prank(relayer);
366-
mainnetController.depositToWeETH(1_000e18);
404+
mainnetController.depositToWeETH(1_000e18, minSharesOut);
367405

368406
vm.record();
369407

@@ -405,8 +443,10 @@ contract MainnetControllerClaimWithdrawalFromWeETHTests is MainnetControllerWeET
405443

406444
deal(Ethereum.WETH, address(almProxy), 1_000e18);
407445

446+
uint256 minSharesOut = _getMinSharesOut(1_000e18);
447+
408448
vm.prank(relayer);
409-
mainnetController.depositToWeETH(1_000e18);
449+
mainnetController.depositToWeETH(1_000e18, minSharesOut);
410450

411451
vm.record();
412452

0 commit comments

Comments
 (0)