Twindex — Full Incident Analysis of Flash Loan and Price Oracle Manipulation Attacks
Originally published in Valix Consulting’s Medium.
This report analyzes the incident of Twindex’s fractional-algorithmic synthetic asset system exploited on October 2, 2021. We provide a full analysis explaining all attack details. Moreover, we also give remediation solutions to address issues in this report.
Table of Contents
TLDR
Twindex’s fractional-algorithmic synthetic asset system was attacked on October 2, 2021. The attacker executed two attack transactions, using the flash loan and price oracle manipulation attacks, to gain a profit of approximately $538,110.
Each attack transaction comprised of six steps, including:
-
Borrowing Flash Loans From Twindex Pools
-
Minting Fractional Synthetic Asset tXAU
-
Draining the USDC Reserve of Dopple Tri Pool
-
Manipulating the KUSD Price Oracle
-
Redeeming the Minted Fractional Synthetic Asset tXAU
-
Paying Back the Loans and Taking the Profit
How Does the Twindex’s Fractional-Algorithmic Synthetic Asset Work?
Twindex’s fractional-algorithmic synthetic asset (TSA for short) is a token mintable from a combination of the stablecoin KUSD and the Twindex platform token TWX. TSA uses both on-chain and off-chain price oracle to keep its price on the peg with the real-world asset price.
Figure 1 shows an example of the TSA token with a collateral ratio of 69%. Whereas the collateral ratio is adjustable dynamically depending on the market price, the TSA minting and redemption processes rely on this collateral ratio. Refer to this link for more details.
The following is an excerpted description of the TSA minting process.
Minting is the process of creating new TSA tokens by supplying KUSD and TWX according to the Collateral Ratio. The value of assets needed to be minted will be worth the market price at the time of minting. The collateralized KUSD will be used to provide value, while TWX will be burned from the system.
And, the following is an excerpted description of the TSA redemption process.
Redemption is the process of retrieving KUSD and TWX by supplying the TSA to burn. The ratio of KUSD and TWX depends on the Collateral Ratio but will be worth the market price.
Some popular TSA tokens include tXAU, tTSLA, tAAPL, tJPY, and tEUR.
Associated Addresses
-
Attacker Address: 0xA0fbb60CE526d2E0e796bEa7f9A0DE06c8eF78a3
-
Attacker Contract #1: 0x9B77d2D4348c071DEB730b392eC67cfeE8930B5E
-
Attacker Contract #2: 0x9283aAc4FBfebA02A253140C0B88689EBb2ad092
-
SyntheticPool Contract: 0x59A40d1F67B9C819a680CabeF237030E482550Fd
-
StablePoolOracle Contract: 0x3d379d3F021c36173c14a0a77923Ee6fa0cD0b0F
-
CollateralReserve Contract: 0x40Ea52769FfaBa9a171d83f9f34972058314F223
-
Dopple Swap Contract: 0x2EADe35C49f3f1E041576aCE336f5A58C0Ad8968
-
TWX-KUSD Twindex Pool: 0x08422f6Cc26cCDa692a36a73A520Da6b0E6d3DE3
-
DOPX-KUSD Twindex Pool: 0x8d9aE514F1713cDbdC991e7D7494e4A91FD24416
-
tXAU-KUSD Twindex Pool: 0x12fc3AB288C4c53b85c702da60602DD4F03d0240
-
tJPY-KUSD Twindex Pool: 0xA4F1CD73093aC7976c05123E9D0f14bb985c707F
-
BSC-USD — BUSD LatteSwap Pool: 0x318B894003D0EAcfEDaA41B8c70ed3CE1Fde1450
-
KUSD-BUSD-USDC Dopple Tri Pool: 0x2bf718e5FA2106dc1998D3F964C1baea8Bda36E1
Associated Transactions
There were two attack transactions in this incident as follows.
-
Attack Transaction #1: 0x24180e59f48bb6291213c3960ad516c23701e9d501fd6105f4087789f0a8d74a (Profit: 142,602.550550984220288325 BUSD and 144,416.90793727401988751 USDC)
-
Attack Transaction #2: 0x153a0d0376579dab66e067f3c655506793e0373b4c39eaab6144f083dc7b1bd6 (Profit: 124,241.64692128795109328 BUSD and 126,849.352420297067674796 USDC)
The attacker profited approximately $538,110 from this incident.
Full Incident Analysis
This section analyzes the incident in detail based on the first attack transaction, 0x24180e…a8d74a. The second attack transaction is identical in attack details, though.
The attack transaction can be dissected into six steps as follows.
Step 1: Borrowing Flash Loans From Twindex Pools
The attacker
initiated the attack transaction by invoking Attacker Contract #1
, as illustrated in Step 1 in Figure 2. Then, Attacker Contract #1
performed Steps 2–5 to borrow flash loans from Twindex
pools, including:
-
Flash loan #1: borrowing 1,150,000 TWX (Step 2.1) and 290,000 KUSD (Step 2.2) from
TWX-KUSD
pool -
Flash loan #2: borrowing 305,000 KUSD from
DOPX-KUSD
pool (Step 3) -
Flash loan #3: borrowing 185,000 KUSD from
tXAU-KUSD
pool (Step 4) -
Flash loan #4: borrowing 141,000 KUSD from
tJPY-KUSD
pool (Step 5)
Figure 3 shows the evidence of tokens transferred during the flash loans by Attacker Contract #1
. Note that flash loan #1 was borrowing both the reserve assets of the TWX-KUSD
pool (Step 1 and Step 2 in Figure 3).
In summary,
Attacker Contract #1
borrowed 1,150,000 TWX and 921,000 KUSD by the flash loans in this attack step.
Step 2: Minting Fractional Synthetic Asset tXAU
After setting up the attack by borrowing the flash loans, Attacker Contract #1
supplied some of the borrowed TWX and KUSD tokens to mint the Twindex’s fractional synthetic asset — tXAU, as portrayed in Figure 4. This attack step can be briefly described as follows.
-
Attacker Contract #1
called themintFractionalSynth
function of theSyntheticPool
contract by supplying the borrowed 1,124,013.407442492934418687 TWX and 921,000 KUSD to mint tXAU tokens. -
SyntheticPool
contract contacted theStablePoolOracle
contract to read the KUSD (collateral) price from theKUSD-TWAP
(Time-Weighted Average Price) parameter. -
StablePoolOracle
contract returned the current KUSD price at that moment, $0.996647861433978465, to theSyntheticPool
contract. -
SyntheticPool
contract minted 606.195630970930049706 tXAU for theAttacker Contract #1
. In this step, the supplied (from Step 1) 1,124,013.407442492934418687 TWX was burned, while 921,000 KUSD was locked into Twindex’sCollateralReserve
contract.
Figure 5 shows the mintFractionalSynth
function of the SyntheticPool
contract. Attacker Contract #1
inputted the function arguments as follows.
-
Actual KUSD amount to lock as collateral: 921,000 KUSD
-
Maximum TWX amount approved to supply: 1,150,000 TWX
The following gathers the highlighted states at the minting time.
-
TWX price: $0.135709613434781496
-
KUSD price: $0.996647861433978465
-
tXAU price: $1,760.555
-
Collateral ratio: 85.75%
The mintFractionalSynth
function did some calculations (line no’s. 188–209). And, the following highlights some computation results.
-
Actual TWX amount to burn: 1,124,013.407442492934418687 TWX
-
tXAU amount to mint: 606.195630970930049706 tXAU
-
Minting fee: 1.824059070123159629 tXAU
After the calculation process, the mintFractionalSynth
function burned 1,124,013.407442492934418687 TWX from Attacker Contract #1
(line no. 213). The function then transferred and locked 921,000 KUSD as collateral from Attacker Contract #1
to the CollateralReserve
contract (line no’s. 214–218). Next, the function minted 606.195630970930049706 tXAU for Attacker Contract #1
(line no. 219). Finally, the function minted 1.824059070123159629 tXAU as the minting fee (line no. 220).
Noteworthy, the SyntheticPool
contract used the state variable lastAction
to keep track of every minting requestor (i.e., msg.sender) and minting block number every time each tXAU token is minted in line no. 211. In this attack transaction, Attacker Contract #1
minted the tXAU token at block number 11406815.
The lastAction
variable was used in the SyntheticPool
contract to prevent flash loan and reentrancy attacks. From our analysis, however, the use of the lastAction
variable turned out to be one of the bugs we found in the contract that made this attack incident possible. We will explain more about this bug in attack step 5.
The evidence of tokens transferred during the tXAU minting process is shown in Figure 6 above.
In summary,
Attacker Contract #1
supplied the borrowed 1,124,013.407442492934418687 TWX and 921,000 KUSD to mint 606.195630970930049706 tXAU in this attack step.
Step 3: Draining the USDC Reserve of Dopple Tri Pool
Next step Attacker Contract #1
began the attack by borrowing the final flash loan #5 of 180,000 BUSD from the BSC-USD — BUSD LatteSwap
pool (Step 1 in Figure 7). After that, Attacker Contract #1
swapped the borrowed 180,000 BUSD (Step 2) for 157,417.177469010566607885 USDC (Step 3) via KUSD-BUSD-USDC Dopple Tri Pool
.
Since the swapped-in amount is large enough to almost empty the USDC reserve in the pool (Step 4), you can see that Attacker Contract #1
got a high price impact here. The percentage change between the swapped-in and swapped-out amounts is by far a ≈12.546% decrease.
Figure 8 shows the swap
function of the LatteSwap
contract. LatteSwap
pool provided flash loan #5 of 180,000 BUSD to the Attacker Contract #1
(Step 1 in Figure 7) in line no. 384. Then, the function executed line no. 385 to trigger the callback function and redirect the control flow back to the Attacker Contract #1
.
Figure 9 displays the swap
function of the Dopple Swap
contract. This function swapped the borrowed 180,000 BUSD for 157,417.177469010566607885 USDC via KUSD-BUSD-USDC Dopple Tri Pool
(Steps 2 and 3 in Figure 7).
The evidence of tokens transferred during the USDC reserve draining process is presented in Figure 10.
In summary,
Attacker Contract #1
triggered this attack step to drain almost all the USDC reserve of theKUSD-BUSD-USDC Dopple Tri Pool
. This step is only the first phase of the KUSD price oracle attack.
Step 4: Manipulating the KUSD Price Oracle
Attacker Contract #1
manipulated the KUSD price oracle in this attack step, as described in Figure 11. This attack step can be briefly described step-by-step as follows.
-
Attacker Contract #1
executed theupdate
function of theStablePoolOracle
contract. -
StablePoolOracle
contract invoked thecalculateSwap
function of theDopple Swap
contract. -
Dopple Swap
contract called thecalculateSwap
function of theDopple Tri Pool
contract. -
Dopple Tri Pool
contract calculated the quoted (spot) price of 1 KUSD for USDC. In other words, the market price of 1 KUSD in exchange for USDC was calculated. Since the USDC reserve in the pool was drained in the previous attack step, the calculated quoted price was $0.145838385420664696 as a result. -
Dopple Tri Pool
contract returned the calculated quoted price, $0.145838385420664696, to theDopple Swap
contract. -
Dopple Swap
contract returned the quoted price to theStablePoolOracle
contract. -
StablePoolOracle
contract updated theKUSD-TWAP
parameter based on the received quoted price. TheTWAP
parameter was manipulated from $0.996647861433978465 to $0.145904042239672548 (≈85.3605% decrease).
Let’s understand how the KUSD-TWAP
parameter was manipulated in detail. The update
function of the StablePoolOracle
contract is presented in Figure 12.
The function invoked the calculateSwap
function of the Dopple Swap
contract (line no’s. 72–76), and the quoted price, $0.145838385420664696, was returned. Later, the received quoted price was used to calculate the TWAP
parameter (line no’s. 79–87). As a result, the TWAP
parameter was changed from $0.996647861433978465 to $0.145904042239672548.
From our analysis, we found that the TWAP
parameter was calculated from the quoted price, the market price of 1 KUSD in exchange for USDC in the Dopple Tri Pool
. And, this was the root cause of the price oracle manipulation.
Furthermore, we also found that the update
function allowed anyone to execute freely. After draining the USDC reserve, Attacker Contract #1
could execute the update
function to alter the TWAP
parameter.
In summary,
Attacker Contract #1
successfully manipulated the KUSD price oracle in this attack step because theupdate
function of theStablePoolOracle
contract calculated theTWAP
parameter based on the quoted price being manipulated in the previous attack step.
Step 5: Redeeming the Minted Fractional Synthetic Asset tXAU
After manipulating the KUSD price oracle, Attacker Contract #1
redeemed the previously minted tXAU tokens in this attack step. Figure 13 pictures the step-by-step redemption process as follows.
-
Attacker Contract #1
transferred the previously minted 606.195630970930049706 tXAU to another contractAttacker Contract #2
. -
Attacker Contract #2
invoked theredeemFractionalSynth
function of theSyntheticPool
contract to redeem the previously received tXAU tokens. Remarkably, theattacker
used theAttacker Contract #2
to perform this redemption step to bypass the flash loan prevention mechanism of theredeemFractionalSynth
function. We will explain the bypass again later. -
SyntheticPool
contract contacted theStablePoolOracle
contract to read the collateral (KUSD) price from the manipulatedTWAP
. -
StablePoolOracle
contract returned the collateral price, $0.145904042239672548, to theSyntheticPool
contract. -
SyntheticPool
contract used the obtained collateral price to calculate TWX and KUSD tokens to retrieve after the redemption. Consequently,SyntheticPool
contract minted 6,830,114.251646252786696562 TWX forAttacker Contract #2
. Next,SyntheticPool
contract unlocked and transferred 910,584.086600497979460971 KUSD from theCollateralReserve
contract toAttacker Contract #2
. -
Attacker Contract #2
transferred the obtained 6,830,114.251646252786696562 TWX and 910,584.086600497979460971 KUSD toAttacker Contract #1
.
The redeemFractionalSynth
function of the SyntheticPool
contract is shown in Figure 14. As mentioned earlier, this function was invoked by Attacker Contract #2
to bypass the flash loan prevention check in line no. 315.
Since Attacker Contract #1
had executed the mintFractionalSynth
function in the attack step 2 previously, Attacker Contract #1
could not execute the redeemFractionalSynth
function in the same block. Thus, the attacker
used Attacker Contract #2
to redeem the minted tXAU tokens instead.
The following gathers the highlighted states at the redemption time.
-
TWX price: $0.135709613434781496 (unchanged)
-
KUSD price: $0.145904042239672548 (changed from $0.996647861433978465)
-
tXAU price: $1,760.555 (unchanged)
-
Collateral ratio: 12.536% (changed from 85.75%)
The redeemFractionalSynth
function did some calculations (line no’s. 329–345). And, the following highlights some computation results.
-
tXAU amount to burn: 606.195630970930049706 tXAU
-
Redemption fee: 4.243369416796510348 tXAU
-
TWX amount to mint: 6,830,114.251646252786696562 TWX
-
KUSD amount to unlock and transfer: 910,584.086600497979460971 KUSD
After the calculation process, the redeemFractionalSynth
function burned 606.195630970930049706 tXAU from Attacker Contract #2
(line no. 365). Then, the function minted 4.243369416796510348 tXAU as the redemption fee (line no. 366).
Next, the function minted 6,830,114.251646252786696562 TWX for Attacker Contract #2
(line no. 367). As you can see, an excessive amount of TWX was minted in this step.
Lastly, the function unlocked and transferred 910,584.086600497979460971 KUSD from the CollateralReserve
contract to Attacker Contract #2
(line no’s. 368–372).
We found the root cause residing in the following functions:
collateralReserve.getECR
(line no. 318) andgetCollateralPrice
(line no. 327). These functions used the manipulatedTWAP
as the KUSD price oracle.
Figure 15 displays the getCollateralPrice
function, one of two associated functions that used the manipulated TWAP
as the KUSD (collateral) price oracle.
The evidence of tokens transferred during the tXAU redemption process is shown in Figure 16.
In summary, the
attacker
programmed theAttacker Contract #2
to redeem the previously minted tXAU tokens to bypass the flash loan prevention mechanism in this attack step.
During the redemption process, the manipulatedTWAP
was used as the KUSD (collateral) price oracle that resulted in minting the excessive TWX tokens.
Step 6: Paying Back the Loans and Taking the Profit
The final step is paying back the borrowed loans and taking the profit from the rest tokens, as described in Figure 17. Attacker Contract #1
performed several swaps to avoid the price impact and then paid back the loans borrowed from Twindex
and LatteSwap
pools (Steps 1–5).
After that, Attacker Contract #1
swapped the remaining tokens to 142,602.550550984220288325 BUSD and 144,416.90793727401988751 USDC (Step 6), and transferred the tokens to the attacker
(Step 7).
Figure 18 displays the evidence of tokens transferred during paying back the loans and taking the profit.
In summary,
Attacker Contract #1
paid back all loans borrowed fromTwindex
andLatteSwap
pools. Eventually, theattacker
took the remaining tokens as the profit.
Recommended Remediation
We found three significant bugs in Twindex’s smart contracts that enabled the attack incident from our investigation.
First bug: the KUSD price oracle (TWAP
) in the StablePoolOracle
contract was implemented incorrectly. The TWAP
used the spot price (quoted price) of KUSD instead of a cumulative price which is more reliable and resilient to flash loan and price manipulation attacks.
We recommend re-designing the affected price oracle using the cumulative price like Uniswap’s solution or considering using more reliable off-chain price oracle solutions (if necessary).
Second bug: the update
function of the StablePoolOracle
contract allowed anyone to execute for updating the TWAP
oracle freely.
We recommend applying a whitelist. Only the whitelisted addresses can successfully execute the update
function.
Third bug: the flash loan prevention mechanism (i.e., the state variable lastAction) in the SyntheticPool
contract had design flaws, which can easily bypass by another Attacker contract.
We recommend re-designing the affected prevention mechanism and making sure that the new design is security-proof.
Summary
The attacker used the flash loan attack to exploit Twindex’s price oracle. Our investigation found some flaws in the design and implementation of the platform’s price oracle, access control, and flash loan prevention mechanisms. To address issues, we also recommend remediation solutions in this report.
Author Details
Phuwanai Thummavet (serial-coder), Lead Blockchain Security Auditor and Consultant | Blockchain Architect and Developer.
See the author’s profile.
About Valix Consulting
Valix Consulting is a blockchain and smart contract security firm offering a wide range of cybersecurity consulting services. Our specialists, combined with technical expertise with industry knowledge and support staff, strive to deliver consistently superior quality services.
For any business inquiries, please get in touch with us via Twitter, Facebook, or info@valix.io.
Originally published in Valix Consulting’s Medium.