@@ -29,6 +29,8 @@ contract ERC4626DonationAttackTestBase is ForkTestBase {
2929 address allocator = makeAddr ("allocator " );
3030 address skim_recipient = makeAddr ("skim_recipient " );
3131
32+ address attacker = makeAddr ("attacker " );
33+
3234 function setUp () override public {
3335 super .setUp ();
3436
@@ -65,12 +67,12 @@ contract ERC4626DonationAttackTestBase is ForkTestBase {
6567 assertEq (morphoVault.curator (), curator);
6668 assertEq (morphoVault.guardian (), guardian);
6769 assertEq (morphoVault.feeRecipient (), fee_recipient);
70+
6871 assertTrue (morphoVault.isAllocator (allocator));
6972
7073 vm.startPrank (Ethereum.SPARK_PROXY);
7174 rateLimits.setRateLimitData (depositKey, 5_000_000e18 , uint256 (1_000_000e18 ) / 4 hours);
7275 rateLimits.setRateLimitData (withdrawKey, 5_000_000e18 , uint256 (1_000_000e18 ) / 4 hours);
73- mainnetController.setMaxSlippage (address (morphoVault), 1e18 - 1e4 ); // Rounding slippage
7476 vm.stopPrank ();
7577 }
7678
@@ -82,18 +84,51 @@ contract ERC4626DonationAttackTestBase is ForkTestBase {
8284
8385contract ERC4626DonationAttack is ERC4626DonationAttackTestBase {
8486
85- function test_donationAttackERC4626_usds () external {
86- address mallory = makeAddr ("mallory " );
87+ function test_depositERC4626_donationAttackFailure () external {
88+ vm.prank (Ethereum.SPARK_PROXY);
89+ mainnetController.setMaxSlippage (address (morphoVault), 1e18 - 1e4 ); // Rounding slippage
90+
91+ _doAttack ();
92+ vm.prank (relayer);
93+ vm.expectRevert ("MainnetController/slippage-too-high " );
94+ mainnetController.depositERC4626 (address (morphoVault), 2_000_000e18 );
95+ }
96+
97+ function test_depositERC4626_donationAttackSuccess () external {
98+ // Set max slippage to (close to) 100%
99+ vm.prank (Ethereum.SPARK_PROXY);
100+ mainnetController.setMaxSlippage (address (morphoVault), 1 );
101+
102+ _doAttack ();
103+ vm.prank (relayer);
104+ uint256 shares = mainnetController.depositERC4626 (address (morphoVault), 2_000_000e18 );
105+
106+ // shares == assets * (totalSupply + 1) / (totalAssets + 1)
107+ // == 2_000_000e18 * (1 + 1) / (1_000_000e18 + 1 + 1)
108+ // == 3.9..
109+ // Rounding down, the proxy receives 3 shares.
110+ assertEq (shares, 3 );
111+ assertEq (morphoVault.totalAssets (), 3_000_000e18 + 1 );
112+ assertEq (morphoVault.totalSupply (), 4 );
113+
114+ uint256 assetsOfProxy = morphoVault.convertToAssets (morphoVault.balanceOf (almProxy));
115+ uint256 assetsOfAttacker = morphoVault.convertToAssets (morphoVault.balanceOf (attacker));
116+
117+ assertEq (assetsOfProxy, 1_500_000e18 + 1 );
118+ assertLt (assetsOfProxy, 2_000_000e18 ); // The proxy owns less than it deposited
119+ assertEq (assetsOfAttacker, 500_000e18 );
120+ }
87121
122+ function _doAttack () internal {
88123 Market memory market = morpho.market (marketId);
89124 assertEq (market.totalSupplyAssets, 36_095_481.319542091092211965e18 ); // ~36M USDS
90125 assertEq (market.totalSupplyShares, 36_095_481.319542091092211965000000e24 );
91126
92- deal (address (usds), mallory , 1_000_000e18 + 1 );
127+ deal (address (usds), attacker , 1_000_000e18 + 1 );
93128
94- vm.startPrank (mallory );
129+ vm.startPrank (attacker );
95130 usds.approve (address (morphoVault), 1 );
96- morphoVault.deposit (1 , mallory );
131+ morphoVault.deposit (1 , attacker );
97132 usds.approve (address (morpho), 1_000_000e18 );
98133 // Donation attack
99134 (uint256 assets , uint256 shares ) = morpho.supply (
@@ -105,8 +140,8 @@ contract ERC4626DonationAttack is ERC4626DonationAttackTestBase {
105140 assertEq (shares, uint256 (1_000_000e18 ) * market.totalSupplyShares / market.totalSupplyAssets);
106141 assertEq (shares, 1e30 );
107142
108- assertEq (morphoVault.balanceOf (mallory ), 1 );
109- assertEq (morphoVault.totalSupply (), 1 );
143+ assertEq (morphoVault.balanceOf (attacker ), 1 );
144+ assertEq (morphoVault.totalSupply (), 1 );
110145
111146 assertEq (morphoVault.totalAssets (), 1_000_000e18 + 1 );
112147 // Instead of performing shares * totalAssets / totalShares, aka
@@ -116,19 +151,6 @@ contract ERC4626DonationAttack is ERC4626DonationAttackTestBase {
116151 assertEq (morphoVault.convertToAssets (1 ), 500_000e18 + 1 );
117152
118153 deal (address (usds), address (almProxy), 2_000_000e18 );
119-
120- vm.prank (relayer);
121- try mainnetController.depositERC4626 (address (morphoVault), 2_000_000e18 ) {
122- // The deposit went through. The only time this is permissible is if the attack had no
123- // effect.
124- uint256 assetsOfProxy = morphoVault.convertToAssets (morphoVault.balanceOf (address (almProxy)));
125- assertEq (assetsOfProxy, 2_000_000e18 );
126- assertEq (morphoVault.balanceOf (address (almProxy)), 2_000_000e24 );
127- } catch Error (string memory reason ) {
128- // The deposit was correctly reverted.
129- assertEq (reason, "MainnetController/slippage-too-high " );
130- }
131154 }
132-
133155}
134156
0 commit comments