March 11, 2022
Beosin Research Series: Are Decentralized Exchanges (DEX) Safe Enough?
Decentralized exchanges (DEX) are a type of cryptocurrency exchange which allows for direct peer-to-peer cryptocurrency transactions. Our previous blog has analyzed the token-level issues, today we would like to talk about security issues that may arise in DEX’s process of swapping tokens. There are mainly two types of security issues in DEX:
(1) Security issues within DEX itself.
(2) Security issues that arise when interacting with other DEFI projects as a third-party protocol.
This blog will analyze the first situation.
The reentrancy vulnerability is a classic type that needs to be prevented from. Compared to ordinary token’s reentrancy, the main manifestation of Uniswap’s reentrancy vulnerability is that an attacker can initiate a second swap before Uniswap updates the price in a single transaction, which makes the amount of tokens that can be redeemed in the second swap more than the normal swap as Uniswap has not updated its price yet. In addition, in the reentrancy attack of Uniswap, the attacker may only gain a small profit in a single transaction, so flashloan or circular arbitrage is commonly used to expand the profit.
Take the imBTC attack incident as an example, which was due to Uniswap V1 not fully considering contract callbacks when calling ERC777 series tokens.
This is shown as follows: when the attacker uses imBTC tokens to swap ETH (as in Figure 1), the contract first calculates the correct amount of ETH through the self.getInputPrice function and sends the ETH to the target address, then when the self.token.transferFrom function is called, it calls the _callTokensToSend function of imBTC contract (as in Figure 2), and the _callTokensToSend function will call the contract that the user specifies to store the imBTC tokens. Therefore, if the attacker deploys the storage contract and rewrites the TokensToSend function in it, then when the tokens are swapped, the pair (a transaction pair composed of two tokens) contract calls the storage contract deployed by the attacker and can call back the pair for a second swap. As the pair contract’s ledger is not yet updated at the time of the second swap, the calculated ETH amount will be more than the normal way, thus profiting.
Figure 1 Uniswap’s tokenToEthInput function
Figure 2 imBTC’s transferFrom function
Figure 3 imBTC’s _callTokensToSend function
The detailed attack process is as follows:
Figure 4 ETH-imBTC incident procedure
So, why is it that the second call of the tokenToEthSwapInput function sends more ETH than a normal swap? The reason can be explained by the following formulas:
First, under normal swap, the getInputPrice function calculates the amount of swappable ETH as:
The amount of ETH that can be swapped the second time normally is:
However, the amount of ETH available for swapping the second time after reentrancy is:
It can be seen that only the reserve of ETH have decreased in the second swap after reentrancy, while the reserve of imBTC does not increase. This results in an equal amount of imBTC being redeemable for more ETH without an increase in the denominator.
When contracts involve asset transfers, the logic is handled using the “check-validate-interact” model, and business-critical operations can be modified using OpenZeppelin’s official ReentrancyGuard.
The swap function does not check the K value
The core of Uniswap is the constant product formula: K=x*y, where the K value is the product of the amount of tokens held by the pair contract and requires that the K value must increase after each subsequent transaction is completed (considering the fee). Therefore, without K-value verification, it will easily get exploited.
Figure 5 Price volatility of Uniswap
In the case of Impossible Finance, two functions are implemented for swapping tokens: cheapSwap and swap. Among them, cheapSwap lacks the check of k-value (see Figure 6), but the project party is aware of the consequences of missing the k-check and has added the modifier onlyIFRouter to the cheapSwap function to restrict the cheapSwap function to be called only by the specified Router contract.
Figure 6 The cheapSwap function without checking the k-value
Under normal circumstances, when a user redeems tokens using a Router contract, the getAmountsOut function is first used to calculate the correct tokens amounts; then safeTransferFrom is called to transfer the user’s redeemed tokens to the target pair contract; and finally, the _swap function is called internally to execute
the cheapSwap function to transfer the redeemed tokens to the target address.
Figure 7 Router01 contract’s swapExactTokensForTokens function
However, since the cheapSwap function lacks a K-value check, if an attacker deploys a malicious token contract and calls back the normal pair contract for swapping the same type of tokens when the safeTransferFrom function is called by the Router contract, it will result in a profit by redeeming the target token at the wrong price, as the amounts used for swap after callback are still the updated data, which no longer meet the validation after changing the state of the ledger.
Figure 8 Swap function of contract for k-value check
The specific steps of the attack in this incident are as follows:
1. In the preparation phase the attacker deploys the AAA token contract and borrows 1000 WBNB through flash loan in exchange for 65,140 IF tokens from the project party.
2. Use half of these IF tokens (32,570) to build an IF-AAA trading pool with the attacker’s own deployed AAA tokens.
3. Execute the token swap in the AAA-IF-BUSD path and execute the attacker’s malicious code when the Router contract calls the transferFrom function of the AAA token contract, reentering to the pair contract of IF-BUSD and swapping the other half of the IF tokens for 221,897 BUSD normally.
4. Return to the AAA-IF-BUSD path of swapping, pass in the previously calculated amounts value into the _Swap function to execute this swap, and use half of the IF to swap another 2,521,897 BUSBD.
5. Return the flash loan to complete the attack.
Figure 9 Incident procedure
Checking the K value in critical redeem functions is a must. Do not rely on external validation for k-value just to save gas and code volume.
Deflationary tokens do not set pair for dividend exclusion
Deflationary tokens will incur additional dividends and fees when they are traded. If such tokens are included in a trading contract and are not specially handled, this may result in a discrepancy between the token reserve recorded in the pair contract and the actual available balance of tokens.
Take the XSquid incident as an example. XSquid is a deflationary token which fails to add the pair contract address to the rewards exclusion list, and this results in the pair contract holding excess XSquid dividend reward tokens in addition to the normal token swap and liquidity storage. Therefore, the attacker can call the Swap function to convert the excess XSquid tokens in the pair contract to WHT to withdraw (as in Figure 11), or withdraw the excess XSquid tokens directly through the skim function (as in Figure 12).
Figure 10 XSquid trading pair contract without adding rewards exclusion
Figure 11 Swap function can swap excess WHT tokens
Figure 12 skim function can withdraw the part greater than reserve
The handling fees and dividends needs to be carefully considered when adding deflationary rewards tokens in DEX. When creating a deflationary token pair, a rewards exclusion can be added to avoid dividend issues for these tokens.
In addition, the following two categories are not security issues of DEX itself, but can be used by project parties to commit fraud leveraging DEX features.
Trading pool scam
This type of problem mainly refers to the project owner leaving a backdoor in the tokens they issue, creating a trading pool with mainstream tokens, enticing investors to buy the project side’s tokens using the tokens that have value in their hands, and constantly pulling the price to cheat the investors.
Take the following TRTC project as an example, a trading pool of ETH-TRTC is created by the project owner. However, the token contract of TRTC has a restriction on the transferFrom function, which requires the transfer-out party to be owner or Uniswap. For investors, they can only buy TRTC tokens through Uniswap, but cannot sell TRTC tokens. Finally, the project owner withdraws the ETH from investors and runs away, which brings huge losses to investors.
Figure 13 Transfer function of the TRTC contract
Figure 14 TRTC contract with ensure modifier
Figure 15 TransferFrom function of TRTC contract
A rug pull is a malicious maneuver in the cryptocurrency industry where crypto developers abandon a project and run away with investors’ funds, and has now become the biggest type of scam in the DeFi ecosystem, where project owners deliberately create the illusion of soaring token prices or high returns for investors who provide liquidity. Once a large amount of capital is gathered, the liquidity from the pool is removed or tokens are rolled up. Such examples are seen in projects like AnubisDAO, Meerkat Finance, TurtleDEX, Squid token Squidcoin, etc. The project owners have deleted their social media accounts and disappeared after running away with the funds, resulting in huge losses for investors.
Beosin recommends that project parties use lockups and multisig to control token liquidity to avoid a rug pull. Investors should not be intrigued by huge profits to avoid being rug pulled.
Related Project Secure Score
Guess you like
Attacked 40 Times and Lost Around $1.7 Million: An analysis of Paraluni’s Exploit
March 14, 2022
Beosin’s Analysis of the Arbitrum-based TreasureDAO exploit
March 03, 2022
Beosin: More than 19 typical security incidents Occurred in February 2022
March 01, 2022
Beosin’s Full Analysis of Build Finance’s Governance Takeover Incident
February 15, 2022