Table of Contents
Overview
Step-by-step Explanation
ELI5 Summary
Video Explanation
References
1. Overview
On January 3rd 2023, the GDS protocol was exploited by a flash loan attack, which resulted in a loss of $187,000. The exploit caused the price of GDS token to drop by 84%
2. Step-by-step Explanation
I have split the attack into 5 phases. I’ll be using Phalcon Explorer to track the flow of funds. To follow the invocation flow with me, click on the link on Phalcon Explorer and scroll all the way down until you see the words “Invocation Flow”
Phase 1: Getting the Flashloan, Lines 1-12
Phase 2: Minting of Liquidity Pool (LP) Tokens, Lines 13-60
Phase 3: The Attack, Lines 61-96
Phase 4: The Repeated Attack, Lines 96-2760
Phase 5: Repaying the Flashloan, Lines 2761-2798
Phase 1: Getting the Flashloan, Lines 1-12
Line 1: The Attacker calls the start() function of his malicious attacking contract
Line 3-7: The malicious contract, address 0x0B995… takes a 2,063,875 BSCUSD flashloan from Synapse pool
Line 8-12: The same contract takes another 315,517 BSCUSD flashloan from the DODO pool.
Total BSCUSD in 0x0B995 contract: 2,379,392 BSCUSD
Phase 2: Minting of Liquidity Pool (LP) Tokens, Lines 13-60
Line 13-34: The malicious contract swaps 600,000 BSCUSD for 3,448,741 GDS tokens.
Line 35-60: The contract mints 2,184,763 million BSCUSD-GDS LP tokens using 3,448,741 GDS tokens and 1,696,427 BSCUSD tokens. There are 82,965 BSCUSD tokens leftover.
Total BSCUSD in 0x0B995 contract: 82,965
Total GDS in 0x0B995 contract: 0
Total BSCUSD-GDS LP in 0x0B995 contract: 2,184,763
Phase 3: The Attack, Lines 61-96
Line 61-62: 0x0B995 transfers 2,184,763 LP tokens to a new contract, 0x0f8d7
Line 63: The function withdraw() is called in the new contract, 0x0f8d7
Line 64: withdraw() calls the transfer() function in the GDS contract, passing in the dead address and 10,000 as the parameter
The transfer() function calls the _transfer() function which calls the _afterTokenTransfer() contract. The _afterTokenTransfer() contract calls _refreshDestroyMiningAccount() which calls _settlementLpMining() [All these function calls can be found in the GDS contract].
SettlementLPMining() is the crux of the problem. Notice lines 426-427. If someone deposits their LP token into the contract, they are eligible for some rewards. This is quite devastating, since a user can deposit a lot of LP tokens at once at get a lot of rewards when _settlementLPMining() is called.
There is a caveat, however. Look at the if condition in line 419. The currentEpoch > lastEpoch check makes it such that the depositor cannot deposit and withdraw his funds in the same transaction to game the system. So, how did the attacker do it then? Let’s continue Phase 3.
Line 66-68: Note that the attacker has 2,184,763 LP tokens, and the total supply of LP tokens is 3,421,382. That’s almost 2/3 of the total supply! Also, notice that in the 0x0f8d7 contract, there is a very small sum of GDS inside (229 tokens).
Line 69-72: The attacker gets 196,173 GDS tokens as reward
Line 73-96: The LP tokens gets transferred back to 0x0B995, I assume that 196,173 GDS tokens gets transferred to 0x0B995 as well. This 196,173 GDS tokens is then swapped for 90,642 BSCUSD tokens (Line 87)
Total BSCUSD in 0x0B995 contract: 196,402
Total GDS in 0x0B995 contract: 0
Total BSCUSD-GDS LP in 0x0B995 contract: 2,184,763
Phase 4: The Repeated Attack, Lines 96-2760
If you look at the subsequent transactions carefully, it is just a repeated attack by transferring the 2,184,763 LP tokens from 0x0B995 to different contracts and repeating step 3. There’s one difference, though. If you pay close attention to the right side after every staticcall, you’ll notice that the BSCUSD balance of 0x0b995 increases, but it increases lesser and lesser each time. This is because the 196,173 GDS tokens taken as reward is always converted to BSCUSD. As a result, there is a heavy imbalance in the BSCUSD-GDS, which results in lesser GDS value.
First swap, earns $90,642 - Line 87
Second swap, earns $84,503 - Line 123
Third swap, earns $78,968 - Line 159
…
Last swap, earns $6,849 - Line 2751
Total earnings: 1,975,419 BSCUSD - original leftover which is 82,965 = 1,892,454
Total BSCUSD in 0x0B995 contract: 1,975,419
Total GDS in 0x0B995 contract: 0
Total BSCUSD-GDS LP in 0x0B995 contract: 2,184,763
Phase 5: Repaying the Flashloan, Lines 2761-2798
Line 2761-2785: The 2,184,763 LP tokens is burned, 444,825 BSCUSD and 11,948,714 GDS is returned
Total BSCUSD in 0x0B995: 1,975,419 + 444,825 BSCUSD = 2,420,244
Total GDS in 0x0B995: 11,948,714
Line 2786-2795: Attacker returns 315,517 BSCUSD to DODO (DODO flashloan has no fees!)
Line 2796-2798: Attacker returns 2,065,526 BSCUSD, paying 1,651 BSCUSD in fees
Total Earnings BSCUSD: 2,420,244 - 2,381,043 = 39,201
Total Earnings GDS: 11,948,714
So why did the attacker use so many contracts to gain 196,173 GDS tokens every time? Why can’t he just used one contract to repeat the process? My suspicion is that the attacker wants to bypass the epoch check, so he created 76 contracts beforehand, deposited some GDS tokens inside, and wait until the epoch changes to execute the attack. Since currentEpoch > lastEpoch, the function _settlementLpMining() will succeed. Also, once _settlementLpMining() is successfully called, the lastEpoch will reset to the currentEpoch, so that’s why I believe that the contract cannot be reused and thus 76 contracts have to be created
3. ELI5 Summary
Attacker gets 2mil BSCUSD from flashloan
Attacker swaps flashloan money to LP tokens (BSCUSD-GDS LP tokens)
Attacker deposits LP tokens to GDS contract
Attacker withdraws LP tokens from GDS contract
Attacker gets GDS tokens and repeat steps 3-4
Attacker repays flashloan
Attacker keeps the remaining money!
4. Video Explanation
Link:
Presentation Slides:
https://docs.google.com/presentation/d/1AQd-JrRJek66QEnGYyt5HYT8DU6uWDbd-vEvexanNps/edit#slide=id.g23165ab64b7_0_34
5. References
Phalcon Explorer: https://explorer.phalcon.xyz/tx/bsc/0x2bb704e0d158594f7373ec6e53dc9da6c6639f269207da8dab883fc3b5bf6694
Attacker Address:
https://bscscan.com/address/0xcf2362b46669e04b16d0780cf9b6e61c82de36a7
Attack Tx:
https://bscscan.com/tx/0x2bb704e0d158594f7373ec6e53dc9da6c6639f269207da8dab883fc3b5bf6694
GDS Contract:
https://vscode.blockscan.com/bsc/0xc1bb12560468fb255a8e8431bdf883cc4cb3d278