@@ -3,15 +3,26 @@ pragma solidity >=0.8.0;
33
44import { IMetaMorpho, Id } from "metamorpho/interfaces/IMetaMorpho.sol " ;
55
6- import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol " ;
7- import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol " ;
6+ import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol " ;
7+ import { IMorpho, MarketParams, Market } from "morpho-blue/src/interfaces/IMorpho.sol " ;
88
99import "./ForkTestBase.t.sol " ;
1010
1111contract ERC4626DonationAttackTestBase is ForkTestBase {
1212
1313 IMetaMorpho morpho_vault = IMetaMorpho (0xe41a0583334f0dc4E023Acd0bFef3667F6FE0597 );
1414
15+ IMorpho morpho;
16+
17+ MarketParams marketParams = MarketParams ({
18+ loanToken: Ethereum.USDS,
19+ collateralToken: address (0x0 ),
20+ oracle: address (0x0 ),
21+ irm: address (0x0 ),
22+ lltv: 0
23+ });
24+ Id marketId = MarketParamsLib.id (marketParams);
25+
1526 address curator = makeAddr ("curator " );
1627 address guardian = makeAddr ("guardian " );
1728 address fee_recipient = makeAddr ("fee_recipient " );
@@ -21,13 +32,15 @@ contract ERC4626DonationAttackTestBase is ForkTestBase {
2132 function setUp () override public {
2233 super .setUp ();
2334
35+ morpho = morpho_vault.MORPHO ();
36+
2437 bytes32 depositKey = RateLimitHelpers.makeAddressKey (mainnetController.LIMIT_4626_DEPOSIT (), address (morpho_vault));
2538 bytes32 withdrawKey = RateLimitHelpers.makeAddressKey (mainnetController.LIMIT_4626_WITHDRAW (), address (morpho_vault));
2639
2740 // Basic validation
2841 assertEq (keccak256 (abi.encode (morpho_vault.symbol ())), keccak256 (abi.encode ("sparkUSDS " )));
29- assertEq (morpho_vault.totalAssets (), 0 );
30- assertEq (morpho_vault.totalSupply (), 0 );
42+ assertEq (morpho_vault.totalAssets (), 0 );
43+ assertEq (morpho_vault.totalSupply (), 0 );
3144
3245 // Initialization
3346 vm.startPrank (Ethereum.SPARK_PROXY);
@@ -37,16 +50,6 @@ contract ERC4626DonationAttackTestBase is ForkTestBase {
3750 morpho_vault.setIsAllocator (allocator, true );
3851 morpho_vault.setSkimRecipient (skim_recipient);
3952
40- // Choose the Morpho market you want to allocate to
41- MarketParams memory marketParams = MarketParams ({
42- loanToken: Ethereum.USDS,
43- collateralToken: address (0x0 ),
44- oracle: address (0x0 ),
45- irm: address (0x0 ),
46- lltv: 0
47- });
48- Id marketId = MarketParamsLib.id (marketParams);
49-
5053 morpho_vault.submitCap (marketParams, 10_000_000e18 );
5154 skip (morpho_vault.timelock ()); // Wait the timelock
5255 morpho_vault.acceptCap (marketParams);
@@ -57,8 +60,8 @@ contract ERC4626DonationAttackTestBase is ForkTestBase {
5760 morpho_vault.setSupplyQueue (supplyOrder);
5861 vm.stopPrank ();
5962
60- assertEq (morpho_vault.curator (), curator);
61- assertEq (morpho_vault.guardian (), guardian);
63+ assertEq (morpho_vault.curator (), curator);
64+ assertEq (morpho_vault.guardian (), guardian);
6265 assertEq (morpho_vault.feeRecipient (), fee_recipient);
6366 assertTrue (morpho_vault.isAllocator (allocator));
6467
@@ -80,29 +83,49 @@ contract ERC4626DonationAttack is ERC4626DonationAttackTestBase {
8083 function test_donationAttackERC4626_usds () external {
8184 address mallory = makeAddr ("mallory " );
8285
86+ Market memory market = morpho.market (marketId);
87+ assertEq (market.totalSupplyAssets, 36_095_481.319542091092211965e18 ); // ~36M USDS
88+ assertEq (market.totalSupplyShares, 36_095_481.319542091092211965000000e24 );
89+
8390 deal (address (usds), mallory, 1_000_000e18 + 1 );
8491
8592 vm.startPrank (mallory);
8693 usds.approve (address (morpho_vault), 1 );
8794 morpho_vault.deposit (1 , mallory);
88- usds.transfer (address (morpho_vault), 1_000_000e18 ); // Donation attack
95+ usds.approve (address (morpho), 1_000_000e18 );
96+ // Donation attack
97+ (uint256 assets , uint256 shares ) = morpho.supply (
98+ marketParams, 1_000_000e18 , 0 , address (morpho_vault), hex ""
99+ );
89100 vm.stopPrank ();
90101
102+ assertEq (assets, 1_000_000e18 );
103+ assertEq (shares, uint256 (1_000_000e18 ) * market.totalSupplyShares / market.totalSupplyAssets);
104+ assertEq (shares, 1e30 );
105+
106+ assertEq (morpho_vault.balanceOf (mallory), 1 );
91107 assertEq (morpho_vault.totalSupply (), 1 );
92- // The following two would succeed in a regular (susceptible) vault:
93- // assertEq(morpho_vault.totalAssets(), 1_000_000e18 + 1);
94- // assertEq(morpho_vault.convertToAssets(1), 1_000_000e18 + 1);
95108
96- deal (address (usds), address (almProxy), 2_000_000e18 );
109+ assertEq (morpho_vault.totalAssets (), 1_000_000e18 + 1 );
110+ // Instead of performing shares * totalAssets / totalShares, aka
111+ // 1 * (1_000_000e18 + 1) / 1 == 1_000_000e18 + 1, the vault actually adds 1 to the
112+ // numerator and denominator, so we get 1 * (1_000_000e18 + 1 + 1) / (1 + 1)
113+ // == (1_000_000e18 + 2) / 2 == 500_000e18 + 1.
114+ assertEq (morpho_vault.convertToAssets (1 ), 500_000e18 + 1 );
97115
98- vm.startPrank (relayer);
99- mainnetController.depositERC4626 (address (morpho_vault), 2_000_000e18 );
100- vm.stopPrank ();
101-
102- // These would fail for a susceptible vault, but succeed here:
103- assertEq (morpho_vault.convertToAssets (morpho_vault.balanceOf (address (almProxy))), 2_000_000e18 );
104- assertEq (morpho_vault.balanceOf (address (almProxy)), 2_000_000e18 );
116+ deal (address (usds), address (almProxy), 2_000_000e18 );
105117
118+ vm.prank (relayer);
119+ try mainnetController.depositERC4626 (address (morpho_vault), 2_000_000e18 ) {
120+ // The deposit went through. The only time this is permissible is if the attack had no
121+ // effect.
122+ uint256 assetsOfProxy = morpho_vault.convertToAssets (morpho_vault.balanceOf (address (almProxy)));
123+ assertEq (assetsOfProxy, 2_000_000e18 );
124+ assertEq (morpho_vault.balanceOf (address (almProxy)), 2_000_000e24 );
125+ } catch Error (string memory reason ) {
126+ // The deposit was correctly reverted.
127+ assertEq (reason, "MainnetController/slippage-too-high " );
128+ }
106129 }
107130
108131}
0 commit comments