PancakeSwap has an exploit that allows stealing tens of thousands of dollars of liquidity.
- source-code of Uniswap v2 dex allows the possibility of stealing funds which directly sent to the pool contracts before calls of sync function
- we found an exploiter who steals tens of thousands of dollars from PancakeSwap (clone of uniswap on binance smartchain) and currently active for 3 weeks
- in this article, we show details on the exploit and how it’s possible to void it, Sers
Recent news about a Uniswap/SushiSwap clone on a binance smart chain called Uranium (link) for 55M highlighted a question: what other DEX clones are possessing a danger for users?
As a warm-up for dex-newcomers, this is how the simplest farming rug pull works (Uranium is a more complicated case via introduced update dex logic bug). The typical farming workflow works like this:
Users can deposit money on Uniswap Decentralized Exchange and earn commission from every swap utilizing this money. 0.3% of every swap resulted up to 5–20% in annual percentage yield (APY) depending on the pool. When the user deposits liquidity on uniswap — instead of having an account there, the user receives a special share which proves ownership of a certain percentage of the pool. This share is called a liquidity pool token, or LP for short.
SushiSwap deploys fork of Uniswap dex, and offered a deal: if you deposit liquidity at SushiSwap dex, and stake this LP token on the special contact called MasterChef — on the top of swap commissions you will receive a certain number of shares of the project (SushiToken). Which in practice meant a way bigger annual percentage yield on the capital.
In addition to that, they offered a deal to existing uniswap liquidity providers: stake your uniswap LP token to the same MasterChef contract and also get a sushi token as a reward. When people haven’t staked much lp tokens APY was huge, thousands of percents. But why just give a token to users who provide liquidity for another project, a competitor as a matter of fact? The answer is this function:
What this function does, if it used properly (and how it was used by sushi team):
- it takes all uniswap LP tokens from the contact
- cashout all liquidity from uniswap which this token entitled to
- deposit this liquidity to sushiSwap dex,
- and give this new LP token from sushiSwap instead of stacked uniswap LP.
This is a famous vampire attack, you can read more in detail about it here (link).
A very important detail in the schema above might be not obvious to newcomers to DeFi space: “if migrators use properly”. Migrator gives full access to all users’ funds to the owner of the contact. And if used improperly, the owner on stage 2) “cash out all liquidity from dex” just steal it. That’s it. And this is exactly what happened in the Uranium case, and many others.
What else might be in danger
Since it’s such a simple and common rug pull vector we decide to check around: what other projects have this migrator function. The obvious candidate is the largest Uniswap fork on binance smart chain Pancake swap (link). We check checked Pancake Swap rewards contact (link) and it also has exactly the same migrator
This fact, apparently, doesn’t concern users a lot, as I discovered on Twitter (link), because binance smart chain is centralized blockchain anyway, and CZ with binance team and render any wallet frozen. And since pancakeSwap is allegedly a bianance affiliated project — no rug pull is expected from the pancake team, despite unchecked ability to steal all user’s money at any moment of time.
- migrator is able to take any staked liquidity token and do basically whatever contract owner wants
- if the owner acts in the best interest of the user — migrator converts Lp tokens, steals liquidity from the original source and deposits this liquidity in a new pool, and changes lp token for the farming contract.
- What’s the point of defi, where the owner can steal all money just as on CEX but with fewer consequences is another point.
A common concern that there’s little point of *Decentralized*Finance that is centralized owned and controlled was addressed by coining the new term (Ce)DeFi — kinda DeFi-like product before regulators will come.
So while checking the contacts of pankeswap we found something else interesting.
A typical transaction looks like this:
There are three options:
- user sends coin A to the swap contact, and gets an equivalent value of coin B.
- send LP tokens and get liquidity back in form or A+B tokens
- stakes token A+B — to get LP token
But look on this transaction:
the caller gets a token seemingly out of nowhere!
In every of these transactions the caller send’s 0 but gets thousands of dollars back!
Clearly PancakeSwap has some sort of an exploit, but it’s a different problem than migration or uranium bug. Also this attack worked on different pools of Pancake Swap, no it doesn’t look like a particular token exploit either.
What’s going on here exactly? Let’s check the code
what normal usage of pancake looks like:
- there’s a BNB/tokenX pair on Pancake
- I want to swap 5 tokens to BNB,
- I call routerSwap
- routerSwap takes 5 tokenX from my wallet and calls low-level swap function
- this low level swap function works not with real erc20 balances but with “virtual” reserve balances (line 458)
- now difference between real erc20 and reserve is 5 of tokenX (line 473–474)
- logic calc how much BNB should I get based on 5 tokenX
- sends me right amount of BNB tokens
- there’s unsync balance of 5 tokenX
- An attacker wants to steal 5 tokens worth of BNB,
- Attacker calls low level swap function from with own fakeRouterSwap
- fakeRouterSwap doesn’t withdraw anything and calls low level swap function
- inside swap before 469 there’s no logit on taking away my tokenX
- 469 real balance didn’t increase
- 475 — require amountIn > 0 — it compare real balance with reserve
- difference between real erc20 and reserve is already 5
- logic calc how much BNB should I get based on 5 tokenX
- sends 5 tokenX worth BNB tokens to the attacker
It happens only if the value stored in the balance variable is less than actual token balance on the contact. The contract has a function “sync” which is designed to address that.
Which is called inside all functions interacting with balances. But in situations when the balance of contact gets increased without calling the sync, it’s possible to call swap with 0 and get all donated tokens and it happened in the transaction above.
So the attacker can simply call the swap function of pancake with right arguments without calling sync function and the difference between actual balance and reserves will be transferred to the caller.
Why would someone send money directly to the contact? Are there use-case of sending money directly to contact, without calling sync function?
Yes, there are. Aside of mistake which some defi newcomers might make by sending money directly to the contract instead of properly deposit liquidity, there’s another popular one:
Donate liquidity of token during the launch event. When some new farming project (especially with fair launch) when one has any tokens, the only way to get it is to buy at a liquidity pool. To prevent skyrocketing prices to the x1000 and falling back to -10000% some projects donate liquidity to the pool by simply sending to the pool contact. Because the only simple alternative way to provide more token to the pool — is to sell it there which might seem like rug-pull from the team. So in that case, if donation happened, it’s possible to the attacker to steal it.
steps to avoid
At the time of wiring this article an attack is still active. It’s ongoing continuous attack happens every hour for the last 21 days. The attacker likely has a script which automatically tracks funds transfer to the pool and immediately executes the attack.
- if you don’t send token to the pool — all funds are SAFU (except all other possible attacks, as migrator for example), no activation is needed;
- if you donate a token to the pool — simple call sync after sending funds. make swaps after it.
Since Pancake Swap is simply a fork of uniswapv2 on a binance smart chain, uniswap v2 absolutely has same “issue”, and all other fork of uniswap. We didn’t have a time to know how much profit such attacks gaied in total on uniswap, but if you follow the steps to avoid — we hope that this gaids will fall to zero.
Stay safe, and do your own research.
Update #1: as https://twitter.com/k06a mentioned, this is also true for any rebase token such as AMPL from ampleforth: every time rebase called it creates the difference between early saved “virtual balance” and actual result erc20.balanceOf(). So if rebase go up — it’s possible to steal difference, if it goes down — swap won’t work until sync is manually called. Therefore every rebase should be accompanied by call of sync function to avoid problems and possible loss of funds.
— please consider to give us a like or share if you think it was worth reading. Thanks!