Merlin Lab’s Incident Analysis — Improper Mint Amount Calculation on mintFor() Function
Starting from Jun 29, 2021, 07:24:29 AM UTC, attacks were done on Merlin’s Alpaca Vault due to improper mint amount calculation. In this article, we would describe the technical details of this issue step-by-step.
- Attacker’s Contract
Please consider the following transaction to be the example of this incident analysis:
- Deposited 0.1 $WBNB to Merlin’s Alpaca Vault
2. Transferred 546 $WBNB and 1 $ALPACA (for bypassing the reinvest condition check when executing the
harvest() function) to the vault as the reward
3. Harvested the reward to force the Merlin’s Alpaca Vault to reinvest
WithdrawAll() which resulted in the performance fee that will be deducted from the reward and $MERL will be minted with function
mintFor() as a compensation
5. In function
mintFor(), the amount of $MERL that must be minted will be calculated with the performance fee multiplied by a static variable (
merlinPerProfitBNB) which equals to “20 * 10^18” led to an excessive amount of minted $MERL compare to the actual $MERL price
6. Swap all minted $MERL to $WBNB to make a profit
In this transaction, the attacker deposited 0.1 $WBNB and transferred 546.81 $WBNB to Merlin’s Alpaca Vault. Then withdrew 382.70 $WBNB and swapped the 5,625.69 $MERL minted to be 246.29 $WBNB. Therefore, the attacker gained 82.78 $WBNB as shown in the following calculation:
246.29 + 382.70 - 546.81 - 0.1 = 82.78 $WBNB
The attack started at the
MerlinStrategyAlpacaBNB.deposit() function, which calls the internal
BaseMerlinStratrgy._deposit() function. This function receives the token from the user and records the user’s shares and principal.
The attacker manually transferred $WBNB and 1 $ALPACA to the contract and called the
harvest() function. The function harvests the pending reward then executes the
In the normal case, the
_tryReinvest() function swaps the reward claimed from the
harvest() function to the
stakingToken and reinvest to the vault using the
_depositUnderlying() function. However, as the attacker directly transferred $WBNB into the contract, the balance deposited was seen as a reward.
The 1 $ALPACA was transferred to the contract to fulfill the condition in line 209.
After that, the attacker called the
withdrawAll() function to trigger the final step of the attack. The token is withdrawn using the
_withdrawUnderlying() function, and the
profit is calculated by comparing the amount withdrawn to the principal in line 159.
performanceFee is then calculated from the
profit, and then used in the
merlinMinter.mintFor() function on line 169.
mintFor() function calculates the
contribution using the
performanceFee passed into the function, which is then used to calculate the amount of $MERL to mint using the
The amount is multiplied by
merlinPerProfitBNB, a variable specified in the contract, to get the amount of $MERL and mint for the user.
merlinPerProfitBNB variable is defined in the contract and does not reflect to the real value of $MERL, so the minting is done excessively.
From this attack, using the flaw explained above, the same attack technique was performed 22 times and the attacker gained a total of 1,056.308843 $WBNB.
For the full detail of profit in each transaction, please visit:
Inspex is formed by a team of cybersecurity experts highly experienced in various fields of cybersecurity. We provide blockchain and smart contract professional services at the highest quality to enhance the security of our clients and the overall blockchain ecosystem.