88MPH Theft Of Unclaimed MPH Rewards Bugfix Review
On September 25, 2022, 0xSzeth reported a high severity bug in 88mph’s
vesting03 smart contract via Immunefi. This bug allowed some malicious users to drain the vesting contract of unclaimed MPH rewards (88mph tokens).
While looking through the contract, the whitehat discovered that some users were able to steal most of the 88mph tokens generated from yield in the
vesting03 contract by depositing an asset and withdrawing the vested 88mph tokens immediately. In other words, users were able to withdraw deposits before the maturity date, receiving yield that they were not entitled to.
This is because while depositing an asset, the
vestRewardPerTokenPaid[vestID] was not updated with the current
The whitehat discovered that the address
0xe67d3b0BDfd1D853FBcE6C0898b464e332a67B18 actively exploited the contract by depositing ETH to the contract and withdrawing the vested tokens immediately:
- https://etherscan.io/tx/0x8c2541448077015e952077015337a4ede92e5f05bdb9daad80e3df9ef9e52621 (Deposit on Sep-23–2022 07:05:59 PM +UTC)
- https://etherscan.io/tx/0x830c08c3de094b68493c794b9390bf5561ccef1d77f2568451737b0410a5492e (Withdraw on Sep-23–2022 07:06:23 PM +UTC)
- https://etherscan.io/tx/0xbf823a243dc2316ce476327dd8d37e18c1ae03607107b9fed76d594e39dace01 (Deposit on Sep-23–2022 07:07:47 PM +UTC)
- https://etherscan.io/tx/0xd762dba091c7c2ce087f3b72467912599290207a49b627a13a8d25686b7aa070 (Withdraw on Sep-23–2022 07:08:23 PM +UTC)
Thanks to the whitehat’s investigation and a quick fix from the project, further losses were prevented.
The project paid out a $21,000 bounty for 0xSzeth’s find.
88mph’s functionality allows users to deposit their assets into the
DInterest contract, which mints the user an ERC721 token that contains information about the deposit amount and maturity of the deposit. It also makes an external call to the
MPHMinter contract to forward the call to the
vesting03 contract, which mints another ERC721 token. This second token contains information regarding vesting status, and
vesting03 further allows users to claim their reward in the form of 88mph tokens.
The vulnerability existed in the
vesting03 contract in the
createVestForDeposit() function. This function is responsible for storing initial information regarding user deposit and minting the
However, this function didn’t update the
vestRewardPerTokenPaid[vestID] with the current
rewardPerToken, which should actually be very low after an immediate deposit because not enough time has passed for the user to earn their full token reward. So, the
_earned() function in the
withdraw() function will calculate as though the user were eligible for the full
rewardPerToken from the amount that they deposited.
A flashloan is the most natural way to exploit this vulnerability.
Here are the steps to reproduce the attack:
- Flashloan WETH from Uniswap or AAVE.
DInterestcontract to deposit WETH.
vesting03contract by supplying the
vestIDobtained from depositing WETH.
DInterestcontract by supplying true as an argument, in order to withdraw the initial deposit before the maturity ends.
- Swap 88mph tokens obtained from step 3 to Balancer or Uniswap.
- Pay back the initial flashloan and fee.
POC (Proof Of Concept)
This POC is written for readers to study and test in Forge.
The steps to use this POC are as follows:
- Install https://github.com/foundry-rs/foundry.
Counter.solin the src folder with
Counter.t.solin the test folder with
- Change the alchemy API in the
WithFlashloan.t.solto your own API.
forge test — match-path test/WithFlashloan.t.sol -vvv
This POC will make a local fork at 15598022 which is before the first malicious transaction is executed, and it will show an attacker can steal around 3.66 ETH.
The initial deposit amount was calculated by simulating the deposit funds, using only 0.1 ETH and 1 month maturity, and then we calculated it by reversing this formula:
FullMath.mulDiv(accountBalance, rewardPerToken_ —
From this calculation, we can determine the exact amount that we should deposit to drain most of the 88mph tokens in the
vesting03 contract. Once we know the amount, we can continue to exploit the
vesting03 contract, using the POC above.
88MPH fixed the vulnerability by updating the
vestRewardPerTokenPaid[vestID] with the current
rewardPerToken when a user makes an initial deposit to the
We would like to thank 0xSzeth for doing an amazing job and responsibly disclosing such an important bug. Big props also to the 88mph team who responded quickly to the report and patched it.
Thanks also to Omik of Immunefi for writing this bugfix review.
If you’d like to start bug hunting, we got you. Check out the Web3 Security Library, and start earning rewards on Immunefi — the leading bug bounty platform for web3 with the world’s biggest payouts.
And if you’re feeling good about your skillset and want to see if you will find bugs in the code, check out the bug bounty program from 88mph.