SlowMist: An Analysis of the Attack on SafeDollar (Released in 2021)
According to SlowMist Zone, on June 28, 2021, the algorithmic stablecoin project SafeDollar on Polygon suffered a flash loan attack. The SlowMist Security Team immediately intervened and analyzed the attack, and the results are as follows:
Attack Details Analysis
Phase 1
Firstly, the attacker created a contract several hours in advance, and exchanged MATIC for PLX tokens through Polydex.
Subsequently, the attacker deposited the obtained PLX tokens into the SdoRewardPool contract of the SafeDollar project through this contract. The architecture of the SdoRewardPool contract is designed with reference to SUSHI’s MasterChef contract, allowing users to earn SDO token rewards by staking specified tokens in the SdoRewardPool contract.
The attacker deposited PLX tokens in the SdoRewardPool contract several hours in advance to prepare for obtaining staking rewards later.
Phase 2
After depositing for several hours in Phase 1, the attacker deployed another contract, which is the main attack contract. In this attack contract, the attacker used multiple pools on Polydex to borrow a large amount of PLX tokens through flash loans.
Then, in the same transaction, the attacker continuously performed deposit-withdrawal operations in the SdoRewardPool contract with the obtained PLX tokens.
Since we know that the SdoRewardPool contract architecture is designed with reference to SUSHI’s MasterChef contract, the attacker cannot receive SDO token rewards by repeatedly depositing and withdrawing in the same transaction. So how did the attacker profit in the end?
Through on-chain analysis, we found that the attacker obtained a large amount of SDO token rewards through the first contract deployed in Phase 1.
So why could the attacker obtain a large amount of SDO token rewards with only simple staking operations in Phase 1? What is the connection between this and the “deposit-withdrawal” operations that the attacker continuously performed in the second attack contract? With these questions in mind, let’s analyze step by step:
Firstly, we examined the transaction where the attacker obtained a large amount of token rewards. In this transaction, the attacker only performed the updatePool operation on the SdoRewardPool contract and withdrew tokens through the withdraw function. It was during the withdrawal that the SdoRewardPool contract minted a large amount of SDO tokens and gave them to the attacker.
Since a larger-than-expected number of tokens were minted and given to the attacker, there must have been a problem with the reward calculation. The _harvestReward function in the SdoRewardPool contract is responsible for minting SDO token rewards. Let’s follow this function:
From the above image, we can see that the number of tokens minted depends on the calculated _claimableAmount parameter, which is affected only by the user’s staking amount (user.amount), the pool’s reward amount (pool.accSdoPerShare), the precision 1e18, and the user’s debt (user.rewardDebt). Direct analysis of on-chain data shows that when the user’s staking amount is multiplied by the pool’s reward amount, pool.accSdoPerShare is a very large value, which causes the final calculation result to become very large.
The pool’s reward amount pool.accSdoPerShare is updated in the updatePool function. Let’s follow this function:
It can be easily seen that pool.accSdoPerShare is mainly determined by the cached accSdoPerShare, _sdoReward, and lpSupply. By analyzing the on-chain data of this logic, we can find that lpSupply at this time is only 2! This causes the calculation of pool.accSdoPerShare to result in an extremely large number due to the very small lpSupply.
However, lpSupply is the balance of the pledged PLX token in the SdoRewardPool contract. But since there are a large number of PLX tokens pledged in the SdoRewardPool contract, why is the balance only 2 when it is finally obtained? We can easily guess that there is a problem with the quantity of PLX tokens obtained. We follow up on the analysis of the PLX token:
We can see that the balance is obtained from the _balances variable, which is changed when users transfer tokens. We can follow up on its transfer function and find that it is a deflationary logic. In other words, when User A transfers tokens to User B, the amount received by User B is less than the amount sent by User A!
At this point, the core of the attack is revealed! Because we know that The architecture of the SdoRewardPool contract is designed with reference to SUSHI’s MasterChef contract and this architecture allows users to withdraw the same number of tokens regardless of how many tokens they have pledged. We follow up on the deposit and withdraw functions of the SdoRewardPool contract for verification:
The pledge and withdrawal logic is as expected. Users can withdraw the same number of PLX tokens they deposited, which means that if a user deposits 100 PLX tokens, they can withdraw 100 PLX tokens.
Through the above analysis, we can know that the PLX token is a deflationary token, and when a user pledges 100 tokens, the SdoRewardPool contract receives less than 100 tokens, but when the user withdraws, the SdoRewardPool contract gives the user 100 tokens. It’s like the user deposited less but withdrew more. Therefore, the attacker used this compatibility issue between the deflationary token and the SdoRewardPool contract to repeatedly pledge and withdraw tokens, ultimately consuming the PLX token balance in the SdoRewardPool contract to 2, which resulted in the SdoRewardPool contract minting a large amount of SDO tokens during reward distribution.
Attack process
1. The attacker first used “Attack Contract 1” to pledge PLX tokens in the SdoRewardPool contract to obtain SDO token rewards later.
2. The attacker used the compatibility issue between the deflationary PLX token and the SdoRewardPool contract to repeatedly pledge and withdraw tokens in the SdoRewardPool contract using “Attack Contract 2”, ultimately consuming the PLX token balance in the SdoRewardPool contract to a very small amount of 2.
3. Then the attacker used “Attack Contract 1” to withdraw tokens from the SdoRewardPool contract to obtain SDO token rewards. The reward calculation divides the PLX token balance in the SdoRewardPool contract, which is a very small amount of 2 at this point. Therefore, the resulting reward was a huge number.
4. After obtaining a large number of SDO token rewards, the attacker directly sold them on the market and left with the profits.
Summary
The core issue of this attack is the incompatibility between the “deflationary token” and the MasterChef contract, which is used as a reference for the SdoRewardPool contract in the SafeDollar project. This compatibility issue caused the malicious depletion of the PLX token in the SdoRewardPool contract, which is used to calculate the SDO token reward, resulting in a flash crash of the SDO token price. As current DeFi projects require interaction between multiple contracts, SlowMist Security Team recommends that compatibility issues between different contracts should be fully considered during the design process.
Reference transactions:
[1] https://polygonscan.com/tx/0x55dad44a7ed31d1637e70879af66e02290d39aea54554f8411e6ec19c03a074b
[2] https://polygonscan.com/tx/0x4dda5f3338457dfb6648e8b959e70ca1513e434299eebfebeb9dc862db3722f3
[3] https://polygonscan.com/tx/0xd78ff27f33576ff7ece3a58943f3e74caaa9321bcc3238e4cf014eca2e89ce3f
[4] https://polygonscan.com/tx/0x1360315a16aec1c7403d369bd139f0fd55a99578d117cb5637b234a0a0ee5c14
About SlowMist
SlowMist is a blockchain security firm established in January 2018. The firm was started by a team with over ten years of network security experience to become a global force. Our goal is to make the blockchain ecosystem as secure as possible for everyone. We are now a renowned international blockchain security firm that has worked on various well-known projects such as Huobi, OKX, Binance, imToken, Crypto.com, Amber Group, Klaytn, EOS, 1inch, PancakeSwap, TUSD, Alpaca Finance, MultiChain, O3Swap, etc.
Website:
https://www.slowmist.com
Twitter:
https://twitter.com/SlowMist_Team
Github:
https://github.com/slowmist/
