Cointime

Download App
iOS & Android

How Safe is SafeMoon? Analyzing the FETA and BEVO Exploits

Validated Project

Recently, a number of deflationary tokens such as FETA and BEVO have suffered flashloan exploits. We noticed that our analysis contradicted a number of previously published reports.

According to these published analyses, one might get the impression that SafeMoon-type deflationary tokens are inherently vulnerable. Allegedly, if DEX Pair is not added to excludeFromReward(), an attacker can extract money from it. Such news would be quite disturbing given the popularity of SafeMoon clones.

In this post, we will show the real vulnerability conditions and bugged code.

Attack Flow

Exploit Transaction | Exploiter | Exploit Contract | Victim Pair | Token Contract

The attacker:

  • Flashloaned 18.5 WBNB at PancakeSwap
  • Swapped all of them for 156m FETA at victim DEX Pair. 100m FETA left
  • Used deliver() token method to burn 156 million FETA tokens (part of the normal SafeMoon implementation). Since a large number of tokens were burned, the deflationary nature of FETA increased all the balances, including the victim DEX pair. Its balance increased from 100 million to 313 million
  • Withdrew 205 million FETA tokens from DEX Pair via skim() method (part of the normal DEX Pair implementation)
  • Used deliver() to burn 205 million FETA tokens
  • Withdrew via skim() 1.2 billion extra FETA tokens from Pair
  • Swapped all FETA (1.8 billion) for 28.9 WBNB, leaving 0.355 WBNB in the DEX Pair
  • Repaid the flashloan of 18.5 WBNB, earning 10.3 WBNB profit

SafeMoon and DEX Pair Basics

SafeMoon

  1. SafeMoon is an ERC20 token, the balance of which grows over time.
  2. Each transfer incurs a burn fee of 5%. Send 100 tokens and receive 95. Token balances of all other rewardable accounts are automatically increased (5 tokens distributed pro-rata).
  3. To achieve balance auto-updating, the balance is not stored directly but as a Reflection. TokenBalance[owner] = Reflection[owner] / Rate.
  4. When Reflections are burnt, the Rate is decreased. All token balances increase automatically.a. Rate = ReflectionTotal / TokenTotal.b. For example, if you burn half of the Reflections, the Rate is halved, and everyone’s token balance is doubled.c. TokenTotal (the sum of all token balances) remains the same.
  5. Some accounts are excludedFromReward. Their token balances don’t grow automatically. That is useful for DEXs. The contract owner had the power to move addresses to and from that list.

DEX Pair

  1. DEX Pair holds liquidity: amountA of tokenA and amountB of tokenB.
  2. The user can swap one token for another.
  3. The price is determined by a ratio of the amounts. PriceA = amountB/amountA.
  4. The price is automatically adjusted so that amountB*amountA remains constant (though it is slightly adjusted by fee).
  5. For example, if you send to Pair 10*amountA of tokenA, you will get 10/11*amountB of tokenB. Because after the operation there will be 11 times more tokenA and 11 times less tokenB - the product is preserved.
  6. DEX Pair remembers its balances and allows everyone to extract surplus via skim() method to keep the reserves product the same as remembered.

Analysis

At first glance, the attack scenario seems clear:

  1. The author of another token forgot to add the DEX Pair WBNB-FETA address to the excludedFromReward list.
  2. Because of this, the DEX Pair's balance began to grow on its own as fees were burned. This is not ideal, because the DEX Pair is trying to keep track of its balances.
  3. This extra balance was considered the exchanger's contribution: they received more WBNB in return for their work.
  4. However, once the attacker burned a large number of tokens, then extracted the excess from the DEX Pair and repeated this action. The attacker got a lot of tokens.
  5. This allowed them to exchange a huge number of tokens for almost all WBNB in Pair.

From this superficial analysis, it turns out that the deflationary nature of the token and the forgetfulness of its deployer are to blame. It is necessary to add DEX Pair to excludedFromReward. If suddenly somewhere else there are such pairs of other deflationary tokens (which is very likely), then an attacker can also extract funds from them, bringing down the token price.

However, in reality, it is not possible to use the above approach. It is impossible to extract more from a Pair than was burned in shares. For example, if deliver() is applied to half of the total supply of tokens, then the Rate will drop two times, and the balance of the Pair will only double, which means that the skim() call will give only half of this. This is not a larger share than what was initially burned.

Let's look at an example:

  1. The attacker has 80% of the tokens. 10% is in the Pair, and another 10% is held by other users (possibly controlled by the attacker).
  2. Burning 80% of the tokens will reduce the Rate of the token by a factor of five. The pair will have 50%, and the other 50% are held by other users.
  3. The attacker can extract four-fifths of the balance from the Pair, or 40%.
  4. The final distribution of tokens is 40% for the attacker, 10% for the Pair, and 50% for other users.
  5. There is no benefit in repeating the scenario because the attacker's position has worsened.

Investigation

Now let’s take a closer look at the attack with figures:

  1. At the start of the attack:a. rTotalis 66700 (the actual figure was 66784287767507956726405749082784862500447315773962267311622542245534674271656, we will cut all reflection balances for simplicity).b. The attacker has 156 million tokens (30500 reflections).c. The Pair has 100 million tokens (19500 reflections).d. And 21700 reflections were excludedFromReward.e. The Rate = (rTotal - rExcludedFromReward) / tSupply = 66700 - 21700) / tSupply = 195.5.
  2. After the first deliver()of 156 million tokens (30500 reflections):a. The attacker has 0 tokens (0 reflections).b. The Pair has 313 million tokens (same 19500 reflections).c. rTotal is now 66700 - 30500 = 36200.d. The Rate becomes Rate = (36200 - 21700) / tSupply = 62.6, that is 195.5 / 62.6 = 3.12 times lower than initially. This is suspicious.
  3. The attacker skimmed 213 million from Pair (some were burned during the transfer).a. The attacker has 205 million tokens (13000 reflections).b. The Pair has 100 million tokens again (6500 reflections).c. rTotal and Rate have lowered due to fees.
  4. The attacker calls deliver()for the second time and burns 205 million tokens (13000 reflections).a. rTotal = 36200 - 13000 = 23200b. Rate = (23200 - 21700) / tSupply = 4.83. That is 62.6 / 4.83 = 13times less.Suspicious again.c. The pair balance becomes 1.3 billion tokens with a small Rate (still 6500 reflections)
  5. Now the attacker can skim again and swap tokens back for WBNB.

It’s obvious now that in our case the Rate declines much faster than expected. Why does this happen?

Vulnerability

Despite the fact that rTotal was initially 66700 according to the FETA token’s logic, in reality there were 73300 reflections circulating. With extra reflections at the attacker’s disposal, they can practically burn “all” the supply but still keep a lot. Or burn 99% of rTotal and get x100 to 6600 (10%) unaccounted “shadow” reflections. That is exactly what happened.

rTotal state field is updated each time reflections are burned, for example in the function deliver() we get:

  function deliver(uint256 tAmount) public {
        address sender = _msgSender();
        require(!_isExcluded[sender], "Excluded addresses cannot call this function");
        (uint256 rAmount,,,,,,) = _getValues(tAmount);
        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _rTotal = _rTotal.sub(rAmount);
        _tFeeTotal = _tFeeTotal.add(tAmount);
    }

In the standard SafeMoon clone is the function _reflectFee(). It adjusts rTotal according to the amount that was burnt by transfer().

However, in our modified FETA token code the authors have split the fee into rFee, rBurn, rCharity and forgot to adjust rTotal with rCharity. Below is the code with our comments:

    function _transferBothExcluded(address sender, address recipient, uint256 tAmount) private {
        uint256 currentRate =  _getRate();
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, 
            uint256 tFee, uint256 tBurn, uint256 tCharity) = _getValues(tAmount);
        uint256 rBurn =  tBurn.mul(currentRate);
        uint256 rCharity = tCharity.mul(currentRate);
        // sub 100% of rAmount from sender, add 91% to receiver
        _bothTransferContent(sender, recipient, tAmount, rAmount, tTransferAmount, rTransferAmount);    
        // add 3% to _rOwned[charity]
        _sendToCharity(tCharity, sender); 
        // sub 9% of rAmount from rTotal
        _reflectFee(rFee, rBurn, rCharity, tFee, tBurn, tCharity);
        emit Transfer(sender, recipient, tTransferAmount);
    }

In _sendToCharity(), 3% of transferred reflections (rAmount) are added to the charity account (_rOwned[charity]), but _reflectFee() still “burns” 9% (all three fees):

_rTotal = _rTotal.sub(rFee).sub(rBurn).sub(rCharity);

Via this bug, ~10% of extra reflections appeared in the system which were not accounted for by rTotal. This is what finally led to the exploit execution.

Conclusion

In order to find similar contracts in time, draw conclusions, and make recommendations for future projects, it is important to fully understand the causes and circumstances of this attack.

Adding a Pair to the excludedFromReward list closes the attack vector and should always be done.

However, as we have shown, the "standard" clones of the SafeMoon project are not affected by the attack. The specific bug in rTotal management was uncovered in FETA/BEVO code modification.

At CertiK we strongly recommend that all projects undergo a thorough code audit, even if the code is mostly forked from an audited project and changes appear (on the surface) to be minimal.

Read more: https://www.certik.com/resources/blog/54xxSWllEyCCKBgMKh32AW-how-safe-is-safemoon-analyzing-the-feta-and-bevo-exploits

Comments

All Comments

Recommended for you

  • BTC Surpasses $81,000

    Market data shows that BTC has surpassed $81,000, currently priced at $81,002, with a 24-hour decline of 2.94%. The market is experiencing significant volatility, so please ensure proper risk management.

  • US Spot Bitcoin ETF Sees $261.4 Million Net Outflow Yesterday

    On October 15, according to monitoring data from Farside Investors, the US spot Bitcoin ETF experienced a net outflow of $261.4 million yesterday.

  • Over $476 Million Liquidated in the Last Hour, Primarily Long Positions

    Data shows that in the past hour, a total of $476 million was liquidated across the network, with long positions accounting for $457 million and short positions for $19.7945 million, primarily affecting long positions. Among these, $200 million in ETH and $116 million in BTC were liquidated.

  • ETH Falls Below $2400

    Market data shows that ETH has fallen below $2400, currently priced at $2395.62, with a 24-hour decline of 4.47%. The market is experiencing significant volatility, so please ensure proper risk management.

  • BTC Drops Below $81,000

    Market data shows that BTC has fallen below $81,000, currently priced at $80,989.9, with a 24-hour decline of 2.87%. The market is experiencing significant volatility, so please ensure proper risk management.

  • US Considers Easing Sanctions on Shadow Fleet to Lower Shipping Rates

    On October 15, a US official stated on Wednesday that the Trump administration is considering easing sanctions on vessels known as the 'shadow fleet.' These ships have previously transported oil and petroleum products from countries such as Russia, Iran, and Venezuela. This move aims to reduce the currently historically high global shipping rates. Shortly before this, President Trump had temporarily lifted sanctions on Russian diesel, a decision that drew strong criticism from Ukrainian President Zelensky, several European governments, and some Republican senators. The US official revealed that current discussions focus on whether the US will allow previously sanctioned vessels to transport 'legitimate oil.' Here, 'legitimate oil' refers to crude oil or petroleum products not subject to US sanctions, rather than oil from Russia and Iran that is under sanctions. The official stated that this move aims to facilitate the export of more refined products and crude oil from the Gulf region.

  • Dollar Index Erases All Gains Since CPI Report Release

    On October 15, the Dollar Index (DXY) slowly declined in the Asian session, completely erasing all gains since the release of the U.S. September CPI data yesterday (which had peaked with a cumulative increase of about 70 points), now reported at 102.16. The British Pound against the U.S. Dollar (GBP/USD) and the Euro against the U.S. Dollar (EUR/USD) both rose about 0.3%, currently reported at 1.3231 and 1.1197, respectively. (Jin10)

  • South Korean Stock Index Opens Down Over 5%

    On October 15, the South Korean KOSPI index fell below 6000 points, dropping 5.59% during the day. SK Hynix fell over 7%, while Samsung Electronics dropped over 6%. (Jinshi)

  • Fed's Daly: Rate Hikes May Be Necessary, But Not Urgent

    On October 15, Mary Daly, President of the San Francisco Federal Reserve, stated on Wednesday that the Fed may need to further raise short-term interest rates to curb excessive inflation. However, she also pointed out that there is no need to take such action at the Fed's meeting on October 27-28. "It is very likely that we will need to make further adjustments, but regarding the timing of those adjustments, I think we are still deliberating," Daly said. "I don’t feel there is a need to rush into a decision with limited information. I believe we have enough time."

  • BTC Falls Below $82,000

    Market data shows that BTC has fallen below $82,000, currently priced at $81,984.48, with a 24-hour decline of 2.01%. The market is experiencing significant volatility, so please ensure proper risk management.