Skip to content

Commit f24d6ce

Browse files
committed
S Feat: Finalize morpho vault attack.
1 parent 1cc84d3 commit f24d6ce

File tree

1 file changed

+51
-28
lines changed

1 file changed

+51
-28
lines changed

test/mainnet-fork/ERC4626DonationAttack.t.sol

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@ pragma solidity >=0.8.0;
33

44
import { 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

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

1111
contract 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

Comments
 (0)