Bug Fix Review and Postmortem

Bug Fix Report

Bug Fix Review & Postmortem

In working with the FairySwap team, Discreet Labs has composed the following postmortem to provide information on a vulnerability that impacted their users and how the team at Discreet Labs implemented a fix. We appreciate the FairySwap team for coming to Discreet Labs for help on the below-mentioned issue.

FairySwap is a valued partner in the Findora ecosystem and is working to reimburse anyone adversely impacted by the exploit. Our understanding is that FairySwap will be publishing a reimbursement plan in the coming weeks.

1. Summary

FairySwap reported a vulnerability in Findora on December 17, 2022. The FRA precompiled contract on the EVM chain had a vulnerability that enabled the delegate_call function. The delegate_call function is not commonly used and shouldn’t be supported in the FRA pre-compiled contracts. In other networks, e.g., Polygon, for example, only reserves basic functions for the WMATIC contract. This vulnerability had allowed a hacker to steal ~57,353,000 FRA (as of December 27th, the date of fix) from FairySwap’s LP contracts (would have enabled the hacker to rug all FRA tokens in any FRA<> liquidity pools). 

Following the report from FairySwap, Findora immediately acted to fix the bug. FairySwap assisted in investigating blockchain activity, validating the fix, and advising the hard fork operation (0.3.37 mandatory upgrade).

While Findora was developing and implementing the fix, a second hacker utilized the liquidity pool exploit to steal another large amount of bridged tokens from the affected liquidity pools on December 22nd.  

Here is a detailed breakdown of the losses in the second attack.











Fairy (Fariyswap’s governance token)


2. Vulnerability Analysis

2.1 Delegate_call Vulnerability

In the EVM, besides the basic call function, there are two functions: delegate_call and static_cal. The EVM will set up the context for the basic call function before the smart contracts execute. The context includes the following:

  • Value
  • Sender: the caller of the smart contract
  • Address: the address of a smart contract where the code’s side effect executes.

For example, when the smart contract 0xa calls the smart contract 0xb ‘s function by 1 Wei, the context will be:

Sender: 0xa

Value: 0x1

Address: 0xb

And if the 0xb calls 0xc without any fees, the context for 0xc execution is:

Sender: 0xb

Value: 0x0

Address: 0xc

For the delegate_call, it does not change the context while the code executes. Taking the previous example, if a function of smart contract 0xd is called by delegate_call in the smart contract 0xc, the context while 0xd executes will be:

Sender: 0xa

Value: 0x1

Address: 0xb

In general, the delegate_call is used for upgrading the smart contract’s logic flow without changing smart contract storage.

Back to the vulnerability attack, the core of the problem was the implementation of the FRA pre-compiled contract. To interact with FRA with the ERC20 standard interfaces, Findora provides the pre-compiled interfaces to the developers. However, in the implementation of the pre-compiled contract, it lacks the check for delegate_call. The result is the code’s side effect of executing on both the target contract and the called contract by the delegate_call function.

With this vulnerability, the hacker (0x3d4d08292B58b715b5908753908cb9AA54B2Aa24) performed the attack as follows:

  1. Deployed a malicious smart contract utilizing the delegate_call vulnerability.
  2. Implemented the FairyCall function in this malicious contract:

a. In the FairyCall function, performed the delegate.call (approve(hacker’s wallet address))

b. An unexpected Approval Event was found. In this event, the topics[0] is 0x8c5b…, the topics[1] is the target’s address, and the topics[2] is the hacker’s address with a value of maximum unit256.

c. After the execution of the smart contracts, the allowance of the target’s address for the hacker’s address became an infinitely large number.

In step 2a above, the side effect of approve function is:

address.allowance[sender][spender] = amount 

. . .where the sender should be the target’s address, and the address should be the hacker’s smart contract. Without the vulnerability of the FRA pre-compiled contract, this should be executed as:

Hacker’s address.allowance[victim][spender] = amount 

. . .and it should not affect the FRA pre-compiled contract. However, the fact is that by this vulnerability, the side effect becomes:

FRA pre-compiled contract.allowance[victim][spender] = amount 

The hacker grants infinite permissions to the victim’s address in the pre-compiled contract. And with this, the hacker can easily withdraw the assets from the victim. 

Here are all the victim LP contracts on the FairySwap:

  • 0x270E323ad4d38811D7ab8592Cd5209399dfC97b7
  • 0x2D3a68C45ffF21A77b398aD756E43191b6685c12
  • 0xbD0b8eF52f72Ecb51ef800c748558D8Cd7A7dC1E
  • 0x759387452688b526B497B711D57eF312BEf80B3E
  • 0x00177706924b66b4DE0878345f14Db592603a0B9

2.2 Liquidity Exploit

After the first attack by the delegate_call vulnerability, almost all of FRA was drained from one side of the affected LP pools on the FairySwap. Taking the FRA/USDT.b as an example, the FRA in this pool became 0. The Swap/AddLiquidity/RemoveLiquidity was not functional because Uniswap V2 (used in creating FairySwap) uses its own function to replace the balanceOF function. In other words, the reserves of liquidity pairs were inconsistent with the actual holdings. It can lower the gas fee when loading the liquidity pairs. When the pair becomes out of balance, the operation through the general router will always fail. 

However, when the liquidity pair is directly synchronized by manual method, the reserves will be synchronized with the ERC20.balanceOF. And after the execution of Pair.sync() function, the FRA gains a discount for exchanging the tokens(USDT, ETH, etc.) on the other side of these affected LP pools, meaning the FRA has infinite value in these pools. The other side of these pools can be drained out with just a little FRA. A second hacker utilized this exploit to steal all tokens on the other sides of the affected pools by calling the sync() function to the affected LP pools.

3. Vulnerability Fix

Findora removed the delegate_call support in the FRA pre-compiled contract. This fix is available on the merged change here, viewable on GitHub.

To prevent further loss, the FairySwap team immediately transferred all remaining tokens to a safe wallet from all compromised LP pools by utilizing the delegate_call vulnerability and liquidity exploit after the second attack. 

Here is the list of all compromised LP contract addresses:

  • 0x658c7ae1910A8f2494b69f9A020E7711f3441c8b
  • 0x0f7855D3fa7739eA8cc354F3dab280Fcb0Cc77e3
  • 0xaF64b8DC86679CCb0BC60331d324bb6c60f38B6b
  • 0xdb9Bb9f766834EF3eB19acB43ecEC5b4b8543852
  • 0xef14dEB182142713BB38e6e6787345adCb07Aa22
  • 0x432920df2386D594983D81EDAb4F6D5f01eAC75D

4. Next Steps and Reimbursement

We’re in communication with the FairySwap team and have been told they have a full list of impacted user addresses and will coordinate a reimbursement strategy to ensure its community is made whole. More on that strategy will be communicated by the FairySwap team in the coming weeks. 

For questions, please visit the FairySwap Telegram chat.

About Findora

Findora is a Layer-1 protocol delivering zero-knowledge solutions to Web3.

Findora integrates two ledgers into a single chain: an EVM ledger for interoperability and a UXTO ledger optimized for zk operations. This dual-layer architecture lets Findora encrypt blockchain data for programmable transparency and public use. By providing new use cases, Findora’s zk tech prepares Web3 for real-world adoption.

We appreciate our developers and would love to onboard you to the Findora ecosystem! Please reach out, and join our social channels for more.

Discord | Twitter | Reddit | Telegram | Youtube | LinkedIn | Facebook | Newsletter