BSC-SCAM: Stealthy Liquidity Pool Block on PancakeSwap
A guide for crypto traders and a technical analysis of a popular
Binance Smart Chain smart contract scam resulting in an inability to swap
a token back due to transferFrom
error on PancakeSwap.
Yesterday I received a message from TokenSniffer regarding another token
SafeBinance with swapping issues and a clue that there is a weird
instruction restricting somehow a to
address in a transfer
function.
As a result users received a strange error message like the on in the screenshot.
As it turns out the token might be a part of a bigger scam campaign and cool TokenSniffer's feature listed multiple similar tokens deployed during the last few days. Here are a few examples:
- SafeRocket
- SafeCoinToken
- FairSafeApe
- SAFEMOUSE
- SAFEROCKET
- and many more (you can find TokenSniffer's similar token contracts feature, similarity score 0.9362 seems to match them quite well).
The initial #SCAMToken target was #SafeBinance, but when I checked the message and similar tokens it turned out there was a newer one (#SafeRocker) released a few mins earlier and looked like it is still active, so I decided to start with that one.
For quick info how to recognize this exact scam for non-tech people, go to for traders section.
Targets⌗
- initial target: #SafeBinance
- on-going scam similar to SafeBinance and analysed in this write-up: SafeRocket
TLDR;⌗
The scammer uses a condition in transferFrom
methods to block
transfers originating from PancakeSwap Liquidity Pool for the scam. The
address of the PancakeSwap LP is set during the very first interaction
of the pool with the contract via add liquidity at the beginning, so the
variable newun
is set exactly to the address of a Liquidity Pool for
that token. When the variable is set swaps back are not working as
contract blocks transfers from Liquidity pool to the users, wht results
in transferFrom
error message in PancakeSwap. The value of the coin
increases and due to the AMM mechanics the scammer to cash out pulls
back his LP token with a profit.
For Traders⌗
- the SCAMToken has a total supply of 1,000,000,000,000,000 tokens and 18 decimals and all of the similar ones have exactly the same total supply, but it's quite a weak indicator of this scam and can be easily modified by the scammers
- nearly all transactions are from
PancakeSwap: <SCAM TOKEN Liquidity pool>
to thevictim address
(swapping something to SCAMToken) and there are very few where the recipient is different indicating that people are buying tokens only and nobody is actually swapping it back (also typical for fresh tokens where people tend to hold at the beginning) - IMPORTANT go to BSCScan and the contract of the token, for
instance in this SCAMToken
https://bscscan.com/address/0x331bb194dc931c79351234733d69dc0f6b8b482a#readContract
and look fornewun
variable or any other variable (if scammer renames it) which is set to the address of the PancakeSwap Liquidity Pool of that SCAMToken - In the transactions log it looks like:
- the very first (oldest) transaction is as usual generate coins
from the
0x0
address to the contract's owner wallet - the next one (second oldest) is a regular transfer of a big part of the supply to a dead address (burn)
- then the scammer adds liquidity to PancakeSwap and sets the
newun
variable - from this moment users are able to swap to SCAMToken, but they are not able to swap-back, due to a blocked transfers from PancakeSwap Liquidity Pool
- the very first (oldest) transaction is as usual generate coins
from the
- to cash out the scammer pulls the liquidity with a profit
Technical Analysis⌗
Introduction⌗
To understand the scam a brief high-level knoweldge of a swap transaction performed by DEX like PancakeSwap is necessary. I'm neither solidity nor a blockchain developer, thus it's a pure analysis based on a notepad, bscscan.com and google. I read my own swapping transactions and the code of the contract and came up with a hypothesis how it works. Naturally, solidity developers are more than welcome to point out anything that is wrong in the proces I described. Just drop me a message.
PancakeSwap swap transactions⌗
Swap BNB->SomeToken⌗
Basically when somebody wants to buy a token on PancakeSwap or a similar decentralized exchange he or she needs to swap usually some BNB (in case of the Binance Smart Chain) for the final token he or she wants. Swapping different tokens than the main network's token are also possible, but it complicates the process, so BNB->SomeToken is a basic and the easiest example and will be the base for the analysis.
After a swap a new transaction should be present in the wallet and it can be reviewed in BscScan. The transaction's details consists of this the most crucial fields:
From: <buyer wallet address>
To : <contract address of PancakeRouter>
Tokens Transferred:
From PancakeSwap Router
to PancakeSwap: SomeToken Liquidity Pool (BNB buyer pays)
From PancakeSwap Some Token Liquidity Pool
to <buyer wallet address> (some number of SomeToken)
In the Logs tab in BscScan there is even more details regarding the transcation
and Transaction Receipt Event Logs
lists all events emitted by executed contracts' functions.
In short it lists the following 5 events:
1. Deposit BNB from buyer's wallet to PancakeSwap Router
2. Transfer deposited BNB from PancakeSwap Router to PancakeSwap Liquidity Pool for SomeToken
3. (IMPORTANT) Transfer form PancakeSwap Liquidity Pool for SomeToken to buyer's wallet
4. PancakeSwap Liquidity Pool sync.
5. PancakeSwap Liquidity Pool swap
Events 1 and 2 involve BNB contract (WBNB to be more precise in my
cases). Actions 3 involves SomeToken's contract (invoking
transferFrom
method from SomeToken's contract and generating Transfer
event). Actions 4 and 5 are involving
PancakeSwap Liquidity Pool for SomeToken
contract.
Swap SomeToken->BNB⌗
The swap back process looks very similar, but it first requires an Approval action, which takes only network's fee and allows to swap SomeToken to other token. After confirming that it will be visible in wallet's transactions list, however details or approval are irrelevant for this analysis. Then the actual swap can be done and when it is performed it will be also listed in wallet's transactions list as following (the most crucial fields):
From: <buyer's wallet address>
Interacted With (To): <contract address of PancakeRouter>
Transaction action: Swap
Tokens Transferred:
From <buyer's wallet> to PancakeSwap Liquidity Pool for SomeToken
From PancakeSwap Liquidity Pool for SomeToken to PancakeSwap Router
Again in details in Logs tab events can be previewed and in short it lists the following 6 events:
1. (IMPORTANT) Transfer SomeToken from buyer's wallet to PancakeSwap Liquidity Pool of SomeToken
2. (IMPORTANT) Approval
3. Transfer BNB from PancakeSwap Liquidity Pool to PancakeSwap Router
4. PancakeSwap Liquidity Pool for SomeToken sync
5. PancakeSwap Liquidity Pool for SomeToken swap
6. Withdrawal BNB
Actions 1 and 2 interact with SomeToken's contract (transferFrom), action 3 interacts with BNB (WBNB) contract, action 4 and 5 interacts with PancakeSwap Liquidity Pool and the last one does the withdrawal and it involves BNB (WBNB) contract.
Add liquidity (super short version)⌗
To make the description complete, there is a last puzzle in this whole process, so liquidity adding. In short to allow swapping a funds of both tokens of the pair has to be added to the liquidity pool. It can be seen in the transaction's log of the token as and entry with annotation in Transaction Action field like
Transaction Action: Add XXX SAFEROCKET And YYY BNB Liquidity To PancakeSwap
Wihtout diving into details the add liquidity generates 8 log events and in short they are:
1. PairCreated (Involving PancakeSwap factory contract) creating an address of PancakeSwap Liquidity Pool of SomeToken
2. (IMPORTANT) Transfer (Involving transferFrom method of SomeToken's contract) to transfer SomeToken funds of the person adding liquidity
3. Deposit BNB in PancakeSwap router (Involving BNB contract)
4. Transfer deposited BNB from PancakeSwap router to PancakeSwap Liquidity Pool of SomeToken
5 to 8 log events are handling PancakeSwap LiquidityPool tokens, sent to the person adding liquidity.
At the beginning action 1 creates a pari using PancakeSwap router and
then there is a transfer. This action 2 (Transfer) is the most important
in this analysis as it invokes transferFrom
method inside the
SomeToken's contract. Later there is a transfer of the second token and
Liquidity Pool token distribution.
The actual scam in SafeRocket (SafeBinance) and the whole family⌗
SafeRocket: https://bscscan.com/token/0x331bb194dc931c79351234733d69dc0f6b8b482a
With the basics behind swap described it should be easy to understand how this scam works in details.
During the swap process, there are a few places where the code of the
contract is invoked and that code was written by a malicious developer
(or copy-pasted), thus can cause the malfunction. In the previous
description all such places were marked as (IMPORTANT) and below you
can find the listing of the most crucial invoked methods:
transfernewun
, transfer
, approval
and transferFrom
.
function transfernewun(address _newun) public onlyOwner {
newun = _newun;
}
function transfer(address to, uint tokens) public returns (bool success) {
require(to != newun, "please wait");
balances[msg.sender] = balances[msg.sender].sub(tokens);
balances[to] = balances[to].add(tokens);
emit Transfer(msg.sender, to, tokens);
return true;
}
function approve(address spender, uint tokens) public returns (bool success) {
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
return true;
}
function transferFrom(address from, address to, uint tokens) public returns (bool success) {
if(from != address(0) && newun == address(0)) newun = to;
else require(to != newun, "please wait");
balances[from] = balances[from].sub(tokens);
allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens);
balances[to] = balances[to].add(tokens);
emit Transfer(from, to, tokens);
return true;
}
The first method can be used to manually set the newun
variable which
is unitialized at the beginning and only the owner of the contract can
invoke. This variable is indeed an important clue.
The initial clue regarding weird instruction and variable was about the
variable and the require(to != nwun, "please wait");
statement in the
second method - transfer
. That method is used to do normal transfers
between wallets. The require instruction is a common programming pattern
to ensure the conditions are met before the code is executed. If the
conditions are not met, the code should not proceed. In this case the
first transfer
method contains a simple check:
require(to != newun, "please wait");
The condition is fulfilled if to
does not equal newun
. So basically
it simply prevents sending funds to whatever address is behind variable
newun
. At the very beginning when the contract is deployed the newun
address is unitialized.
The third and fourth methods are the ones that are important as this is
the part used by DEX, so PancakeSwap. approve
and transferFrom
are
used to do the swap operation. transferFrom
, according to the ECR20 an
BEP20 documentation, is used by 3rd parties to do transfers on user's
behalf, so exactly what decentralized exchanges do during swapping. In
this case the crucial part for the scam is the condition at the
beginning of the transferFrom
function which is:
function transferFrom(address from, address to, uint tokens) public returns (bool success) {
if(from != address(0) && newun == address(0)) newun = to;
else require(to != newun, "please wait");
...
}
The code uses the condition to set newun
variable and require
instruction to block the processing of the function. To easier analyse
it here is a more readable (Java-like) snippet.
// If somebody (not 0x0) sends funds and newun is 0x0, initialize it to the address of the recipient
if(from != address(0) && newun == address(0)) {
newun = to;
}
else {
// Block if funds are transferred to address same as in newun
require(to != newun, "please wait");
}
What this code basicaly does can be described:
- when the
from
address is not0x0
andnewun
is set to0x0
, initialize thenewun
variable and set it toto
paramter- otherwise make sure the processing is stopped if the funds are
sent to the address stored in
newun
variable
- otherwise make sure the processing is stopped if the funds are
sent to the address stored in
The require statement is exactly the same mechanism as in the transfer
method, so to prevent sending funds to some address. However, that
address is nowhere in the code initially, instead it is set during the
peculiar moment in time, during the first invocation of the
transferFrom
method and when the from address is not 0x0
or at any
point in time when the owner of the contract invokes manually
transfernewun
, which only he can invoke. Those are two methods to set
the address to some value and block transactions.
With this information and after reviewing the first transactions of SafeRocket token the following happens.
- Contract is created and the initial transfer is done, so max supply
is transferred to the owner of the contract from
0x0
address. Solidity developer could help me out here as I'm guessing this action involvestransfer
method rather thantransferFrom
, but anyway the newun variable is not initialized, the recipient is the owner of the contract so the transfer is successful. - The owner burns tokens by sending them to 0x…dead address.
- The owner does the very first PancakeSwap operation which (add
liqudity), as described in general info about swapping, involves
transferFrom
of the SCAMToken's contract while thenewun
variable is not initialized. In this operation thefrom
address is not0x0
, it is set to the address of a wallet of the owner andto
is the address of the Liquidity Pool. It results in setting thenewun
address to PancakeSwap Liquidity Pool of SCAMToken's address. - From now on all
transferFrom
operations whereto
is set to PancakeSwap Liquidity Pool of SCAMToken's address will be blocked as this value is innewun
variable and statementrequire(to != newun, "please wait")
intransfrFrom
method is blocked.
Thanks & Final thoughts⌗
- thanks to TokenSniffer for giving a lead on this scam, screenshots and validating the flow
- thanks to Solidity devs: Alex Vinogradov for validating the approve-transferFrom flow during add liquidity
- msg @cryptot3ddybear on Twitter if you found a scam and want to know how it works or increase your chances to not fell for it again
Buy a coffee⌗
Found it useful? Learnt something? Saved your moeny? Buy a coffee:
MetaMask Wallet address (Etherum, BSC)
0xD9Da5Fd5B049Da96e8C51738Ea93501f2e414EE6