SafeMoon — Is it safe though? A detailed explanation of frictionless yield bug

SafeMoon is perhaps the most prominent of many projects that includes the gasless holder yield (frictionless yield) feature, a feature that is first brought out by Reflect Finance (RFI). We can briefly define the gasless holder yield as follows; A certain percentage of deductions are taken from each on-chain transaction and this amount is automatically reflected in the balances of the users who hold the token at the time the transaction is made. In a sense, we can think of this feature as auto-staking. Thanks to this structure, users are able to earn yields by simply holding their tokens without staking them. Gasless holder yield attracted a lot of attention in the DeFi space, and the RFI smart contract was forked in maybe more than over a hundred projects’ underlying structure. However a critical bug in the RFI smart contracts was also forked by all of these projects. This bug can only be exploited by the contract owner and when used, a certain amount of tokens from the balances of all holders are instantly transferred out, causing them to lose their funds. In the diagram below, you can find how the bug can be exploited and its consequences.

SafeMoon Smart Contract Bug — A detailed explanation

Demonstration of the bug in the SafeMoon smart contract on BSC mainnet

How does the gasless holder yield (frictionless yield) distribution work?

The automatic distribution of holder rewards is accomplished by weighting the wallet balances of the holders by a coefficient (k). While the distribution mechanism seems quite complex on the smart contract, the underlying math is quite simple. Basically, the ratio between the amount of holder reward received from the last transaction (r) and the amount of token held by the holders (h) to whom this reward will be distributed is calculated (r/h). This ratio is then used to update the coefficient (k) that is used to weight the holder balances after each transaction. Here’s a brief explanation of how the math behind the gasless yield works:

Figure-1: Logic map of the gasless holder yield feature. The smart contract provides the distribution of holder rewards by following these steps after each on-chain transaction.

Accounts that are Excluded from Holder Rewards

However not every user can benefit from the gasless holder yield feature. By using the excludeFromReward function in the SafeMoon (or other RFI forks) smart contract, the desired accounts can be excluded from receiving holder rewards. For example, by excluding the contract owner or the address where the exchange liquidity is allocated, these accounts can be prevented from benefiting from the holder rewards.

Figure-2: Functions used to exclude accounts from receiving SafeMoon gasless holder yields. _tOwned represents the balance of excluded accounts where _rOwned is the balance of accounts that are not excluded. currentRate is the weighting coefficient, k as we mentioned earlier.

For balance calculations to work properly, the following equation must always be satisfied between _tOwned and _rOwned:

currentRate = _rOwned / _tOwned

The balance calculation explained in Figure-1 is applied only to the accounts that are not excluded. If the account is excluded, balance calculations are made as in a standard ERC-20 contract. To achieve this, the balances of the accounts to be excluded are updated by dividing the user’s balance with the current value of the weighting coefficient. This is done within the tokenFromReflection function.

Inside the SafeMoon Bug — Reason & Consequences

The exclusion operation is reversible. Using the includeInRewards function in the SafeMoon smart contract, previously excluded accounts can be included again and continue to receive holder rewards. excludeFromRewards and includeInRewards functions can only be called by the contract owner. The bug is exactly at this stage, when the excluded accounts are included again.

Figure-3: During the inclusion of accounts, the rate of “currentRate = _rOwned / _tOwned” is lost due to the missing of 2 lines of code that should update the value of _rOwned.

In summary, not making the necessary updates when switching between excludeFromReward and includeInReward functions is the reason behind the bug. The variable that is not updated causes the included accounts to have excess tokens in their accounts and all other holders to lose their funds. The accounts included by the contract owner siphons off the tokens out of the balances of all accounts that are currently holders.

List of Some of the Projects with the Bug

Some of the projects involving the same bug in their smart contracts. The projects we have included in the list here are those with the highest daily volume and number of holders amongst the other projects that integrated the same RFI structure into their smart contracts. Since the exploitation of this bug causes holders to lose their funds, more users will be affected by this bug for projects with a higher number of holders.

We would like to emphasize once again that this bug can only be exploited by the contract owner. The fact that the include and exclude functions cannot be called in smart contracts that have admin keys burned or have no owner prevents this bug from being exploited. However, the exclude function has to be used to prevent certain contracts or accounts from receiving holder rewards, especially when these tokens are listed on centralized exchanges or in case of an unforeseen emergency. Ownerless contracts lose this flexibility and it can be considered as a another security deficiency regarding third party risks in this case.

Proper Implementation of the Functions on the PERA Token Smart Contract

The PERA Token smart contract also includes the gasless holder yield feature. During the development of PERA Token, we noticed the bug in the smart contract of RFI and other projects that forked this structure. After finding the cause of the bug, we integrated the frictionless yield feature into the PERA smart contract as it should be. You can see the appropriate use of exclude and include functions in the snippet below.

Implementation of exclude and include functions in the PERA smart contract.

As can be seen from the PERA smart contract, the balance update in the include function, which is missing in the RFI code, is made by multiplying the user’s balance with the current value of the weighting coefficient.

The proper implementation of the includeInReward function in the SafeMoon smart contract should be as follows, as opposed to the faulty application given in Figure 3.

Smart Contract Audits

This incident highlights the need for comprehensive pre-launch security audits of blockchain-based platforms. The reason why this error has been overlooked by many audit firms may be attributed to the fact that these firms are performing security scans beyond their capacity. However, only audit firms cannot be shown as responsible for this situation. Many people’s funds are put at risk when the developers are in a hurry without thoroughly examining their own code or realizing what they are copying when forking other projects. We strongly suggest smart contract developers to be as familiar with their code as possible. When they think that everything is correct, they should prepare logic maps and analyze all the situations that may occur and how their code should behave in which situation. Although there are very high-quality audit firms in the market, nobody can know your code as intuitively as you when your code gets too complex.

PERA token smart contract is audited by Halborn, a leading blockchain cybersecurity firm. You can read the PERA Token smart contract security audit report from the link below:

https://pera.finance/info/PeraSmartContractAuditReport.pdf

House of the world’s first decentralized trading competition. Trophy DeFi 2.0 is here to bridge the gap between CEX and DEX: Trader incentives.