Skip to main content

A bug in the uXTZ/XTZ LP token yyXTZ

Date: 2023-09-28

Authors: Markus, Florin Status: ongoing

Summary:#

The liquidity token contract of the recently deployed new uXTZ/XTZ contract (KT1BFXgczFte2zftCTg7tL6Qk2capsFg6UFS), the yyXTZ token contract, has a bug. As a result, yyXTZ tokens which are deposited into smart contracts can get locked. This happened when a user created a Quipuswap pool with yyXTZ and uXTZ, resulting in currently locked tokens on that contract (see details below).

The youves core contributors are already working on a solution and there will be a way to recover these locked funds, which we will explain at a later stage. For now it is important for you to not send yyXTZ to a smart contract. Holding it in your wallet, sending it to another tz1.... address is not a problem. Leaving liquidity in the uXTZ/XTZ pool is also not a problem.

Youves core contributors will soon deploy a new contract for uXTZ/XTZ with a fixed LP token contract, which will replace the current contract and submit a YIP to rewire the interest rate payments to this new pool. Then we will ask you to withdraw your liquidity from the youves uXTZ/XTZ pool. After that, recovery of the currently locked funds will be prepared, details will follow.

TL;DR: Avoid sending yyXTZ to smart contracts for now. You can safely hold it in your wallet or send it to other tz1 addresses. Keeping liquidity in uXTZ/XTZ pool is also fine. Stay cautious!

Detection:#

On Wednesday September 27, 2023 - 14:35 CET, a 3route contributor reached out a to youves contributor pointing out that any trades from uXTZ to yyXTZ will fail on the new Quipuswap pool and gave a hint to what the problem could be.

Root causes:#

An existing bug in the LP token contract of uXTZ/XTZ, yyXTZ, with the address KT1NtfNBPAo8UrcMexMyrKR5WCHb3VRiocvx:

For all transfer operations of the yyXTZ token, an approval of the transferring address needs to be requested from the yyXTZ token contract by calling the approval entrypoint. Most wallets like Temple request this approval by default when creating a transfer operation. So transferring it from a Tezos user wallet (tz1....) would just work because a user can always ask for approval for the yyXTZ token his own address is holding. The problem starts when a Tezos smart contract is trying to send the yyXTZ token: The smart contract would also need to request approval by calling the approval entrypoint on the yyXTZ token contract. A smart contract that is not prepared to do so, will not be able to move the yyXTZ.

Trigger:#

The said bug already existed in the ligo template (repo here) used for the yyXTZ token. The bug was even discovered during testing shortly before the flat curve and LP token contracts were deployed on mainnet. But then, the full impact was not understood and it was assumed that it would always be possible to get allowance - which in hindsight was obviously not the case.

Impact:#

On September 24, 2023 09:06 am, a user created a new pool with the token pair yyXTZ and uXTZ on Quipuswap (KT1VVLLnLLZEwewB6g8fj7BMDsnsaU72Vatx). A pool contract was instantiated and the user deposited yyXTZ and uXTZ as liquidity to the contract. But as the Quipuswap contract can not transfer the yyXTZ, it is 1.) not possible to swap uXTZ for yyXTZ and even worse, 2.) it is not possible to remove the liquidity again. So currently, these yyXTZ tokens are locked inside the Quipuswap contract.

Resolution:#

Deployment of a new uXTZ/XTZ pool#

With YIP-3, a new uXTZ/XTZ flat curve pool with rewards was deplyoed. It's liquidity token was rebranded to yXTZ, in order to avoid confusion. The new pool is available via Youves frontend https://app.youves.com/swap/uXTZ-tez/2 and has the contract address KT1SPUvH5khHtirTEVeECiKrnh4FFXxWZ6ui on mainnet. Users were asked to move their liquidity, before a recovery operation of the trapped funds was prepared and executed:

Recovery:#

Youves contributors could sucessfully recover the trapped funds of the uXTZ7XTZ (yyXTZ) dex (KT1BFXgczFte2zftCTg7tL6Qk2capsFg6UFS) in a recovery operation:

youves core contributors came up with a solution which allowed to recover nearly all of the trapped funds, but it involved an uncommon operation, which was essentially draining the youves uXTZ/XTZ (yyXTZ) dex KT1BFXgczFte2zftCTg7tL6Qk2capsFg6UFS. After draining the funds into a 2/3 multisig wallet controlled by core contributors Florin Avram, Markus Laeng and Alessandro De Carli, the funds were distributed pro rata between all the remaining yyXTZ liquidity token holders, including those who deposited their yyXTZ into the Quipuswap contract. The pro rata distribution is based on the block level of the flashloan execution.

To conduct this controlled drainage, a special smart contract was developed, which we call the "flashswap" contract. This contract was first deployed alongside with an identical pool, including the buggy LP token contract, on ghostnet and was sucessfully tested under realistic conditions.

Flashswap contract, on ghostnet: KT1Sf3qxVErauSyk4oegpMjvf85ddx9Uo8eB (BCD) uXTZ/XTZ pool contract, on ghostnet: KT1Vc1jG85EHH9SLqWCpH2DdtTtnCnCHor55 (BCD) Buggy LP token contract, on ghostnet: KT1WKYXzTjj64nU6vF8Vxht9eZFC1bga8ZKq (BCD)

Once tested, the identical contract was deployed to mainnet:

Flashswap contract, on mainnet, KT1VDhsECGmQdqeagzbtzAHMsdSxVWdwsNjh (BCD) uXTZ/XTZ (yyXTZ) pool contract on mainnet: KT1BFXgczFte2zftCTg7tL6Qk2capsFg6UFS (BCD) Buggy LP token contract (yyXTZ), on mainnet KT1NtfNBPAo8UrcMexMyrKR5WCHb3VRiocvx (BCD)

With the admin and only entity able to execute, the 2/3 youves operations multisig wallet KT1KAr9hnFEEFXmJ1EUS4ZYMy9G8eK7bHbQM, using the tzsafe multisig implementation from Marigold.

This is how the operation worked:

Step 1: Preparation (Keyholder signature):#

The flashswap contract needed to be made administrator of the synthetic asset token contract in order to have the right to mint unlimited uXTZ. On Mainnet that is KT1XRPEPXbZK25r3Htzp2o1x7xdMMmfocKNW with tokenID 3. Keyholder signatures or DAO acceptance were required to set the flashswap contract admin on the synthetic asset token contract.

We decided to request signatures from the keyholders in this case, because a DAO vote would have taken 7 days to conclude in which malicious actors could try to find attack vectors. To our best knowledge there are none and the contract has been tested multiple times, but we preferred to conduct this fast. Additionally, we already pointed out in the post mortem, that we will come up with a solution that includes controlled drainage of the pool: https://docs.youves.com/postmortems/postmortem_incident_8#resolution.

Also, a DAO vote could have been rejected, because the majority of youves users were not affected by the bug, but they still would have profited of the trapped funds because these continue to accrue interest rate payments. A rejection would have been unfair towards those users who did deposit their yyXTZ on the Quipuswap contract in order to help youves grow.

We are aware of the impact of this decision on the governance of youves and we are open to discuss on how to handle such situations in the future on how to move forward with the decentralized governance of youves.

After intense discussion, the youves keyholders agreed to set the flashswap contract admin on the main youves synthetic asset contract, which happened on block level 4439986 (https://better-call.dev/mainnet/KT1XRPEPXbZK25r3Htzp2o1x7xdMMmfocKNW/operations). After the flashswap operation was executed, the admin rights of the flashswap contract were immediately removed again on block level 4440021 (https://better-call.dev/mainnet/KT1XRPEPXbZK25r3Htzp2o1x7xdMMmfocKNW/operations).

Step 2: Execution (2/3 Multisig executes flashswap contract)#

Once the flashswap contract was set as an admin of the synthetic asset contract, the designated admin of the flashswap contract, the receiving 2/3 multisig wallet controlled by Alessandro, Florin and Markus (KT1KAr9hnFEEFXmJ1EUS4ZYMy9G8eK7bHbQM) was allowed to call the entrypoint flash_swap of the flashswap contract. Calling it with an amount of synthetic assets to be minted, a series of operations was triggered of which the relevant ones were:

  1. flash_swap: called with parameter 99999999000000000000 which will mint 99'999'999 uXTZ

  2. mint: The amount of uXTZ which was defined as parameter during the call of flash_swap was minted from the synthetic asset contract, with the flashswap contract as owner of these tokens.

  3. mint: The identical amount of uXTZ which the dex pool had before flash_swap was triggered was minted to the receiver address, which was set to the same 2/3 multisig address controlled by Alessandro, Florin and Markus (KT1KAr9hnFEEFXmJ1EUS4ZYMy9G8eK7bHbQM) upon deployment of the flashswap contract (more info about this multisig below).

  4. tokenToCash: The minted amount of 99'999'999 uXTZ was swapped against XTZ on the uXTZ/XTZ (yyXTZ) pool, the outgoing XTZ was sent to the same 2/3 multisig receiver address as mentioned above (KT1KAr9hnFEEFXmJ1EUS4ZYMy9G8eK7bHbQM).

  5. burn: The complete remaining uXTZ balance of the dex pool was burned, including the additionally minted/swapped and the previously present balance, setting the curculation supply of uXTX back to the exact amount it had before step 0.

This all happend in one operation, so either all steps had to succeed or all steps would have failed. There is was no way the operation group could have stopped inbetween and leave the contracts in unwanted states.

As a result, nearly all of the XTZ (except 0.000001 XTZ and all of the uXTZ were moved the 2/3 multisig wallet KT1KAr9hnFEEFXmJ1EUS4ZYMy9G8eK7bHbQM and the pool was left with 0.000001 XTZ.

To wrap it up, here's a comparison of before and after the execution of the flashswap contract:

Before: dex pool has 60250.093562 XTZ and 159,643.297411272391 uXTZ receiver has 0 XTZ and 0 uXTZ

After: dex pool has 0.000001 XTZ and 0 uXTZ receiver has 60250.093561 XTZ and 159,643.297411272391 uXTZ

The global balance of uXTZ before and after the execution is exactly the same as the same amount was burned as was minted during the recovery operation.

Step 3: Distribution (2/3 Multisig distributes recovered funds)#

As the uXTZ and XTZ have been safely received by the 2/3 multisig wallet controlled by Ale, Florin and Markus, the exact amount of XTZ and uXTZ for redistribution have been calculated, based on the yyXTZ holdings at the block level when the operation was executed. Every holder (or depositor of yyXTZ to quipu) received his/her pro rata share, sent from the multisig wallet.

Here's a breakdown of the distribution and links to the transactions:

https://docs.google.com/spreadsheets/d/1FuHTqpBbf-JxiRonSzwFBsjAvuXdLSTzdkSy1rSpEEQ/edit?usp=sharing

All recovered funds were distrbuted. If it was necessary to round, roundings were made in favor of the recipients.

The Youves Operations Multisig wallet is KT1KAr9hnFEEFXmJ1EUS4ZYMy9G8eK7bHbQM and was set up using tzsafe from marigold, which we already use for operational purposes like sending funds to processors etc.

Action Items:#

What steps were done to fix or mitigate the problem? Where are we at, currently?

Action ItemTypeOwnerState
Alert contributors, Identfiy reason for failuremitigateMarkus, Florin, AleDONE
Inform community to not deposit yyXTZ into smart contractsmitigateMarkusDONE
reach out to Quipuswap to pause the contract and remove the pool from the Quipuswap frontendmitigateMarkusDONE
Deploy a flat curve and fixed LP token contract to ghostnetfixflorinDONE
Test new flat curve and fixed LP token contract on ghostnetfixMarkusDONE
Deploy a new flat curve and fixed LP token contract to mainnetfixflorinDONE
Rewire tez collateral engines to send interest rate to new pool, set trading fees on old pool to 0%fixYIPDONE
Develop and test migration contract (the flashswap contract)recoverFlorinDONE
Execute migration contractrecoverYouves keyholders, Markus, AleDONE
Distribute recovered funds those entitled for recoveryrecoverYouves Operations Multisig: Ale, MarkusDONE

Timeline:#

What was the exact timeline of the relevant events/actions?

(all times CET)

2023-09-24 09:06 User deploys a new yyXTZ/uXTZ contract on Quipuswap and deposits funds 2023-09-27 14:35 3Route contributor notifies youves contributors via Telegram 2023-09-27 18:35 Message is acknowledged, investigations start 2023-09-27 19:05 Root cause confirmed, research for solutions, affected users are informed 2023-09-28 09:15 Solutions are defined, development starts

Lessons Learned#

What went well#

Alerting was fast once the message was received. Cooperation between DeFi groups (youves, 3Route, Quipuswap) on Tezos is remarkable. Affected users were immediately contacted.

What went wrong#

Even though the bug, which existed in the ligo template, was discovered during the last testing round before mainnet deployment, its impact was highly underestimated and the affected LP token contract was still deployed.

Where we got lucky#

Again we are thankful for our community of users and for the tightly knit community in Tezos DeFi where people help each other. We got also lucky that the network of contracts for youves are solid and flexible in the same time, which will allow us to recover the funds, using the DAO and the youves keyholders, which are admins of the uXTZ token contract. We are confident to be able to recover the locked tokens.

Conclusion#

Once again, we had to learn that any allegedly small issue absolutely needs to be addressed and may not be postponed to be resolved only because the impact is deemed to be irrelevant. We will adjust our processes in order to avoid any similar situations in the future. Again, be assured that all youves synthetic assets, all other flat curve contracts and all farm contracts are not affected by this bug. Only the yyXTZ LP token contract has this issue.

Final thoughts, addressing possible concerns#

We also aware of the uncommon nature of this operation. But we think if this is the only possible way to recover around $ 135k value of assets trapped, then we are obliged to do it.

We are also aware that this might raise concerns in some users, seeing that core contributors, together with the acceptance of the youves keyholders, are able to mint any arbitrary amount of synthetic assets. But be assured that keyholders only did agree to this recovery operation because it benefits the youves platform and their most engaged users which were at risk of losing their funds.

On the other hand, the sucessfully executed operation proves that the smart contract stack in use is solid and flexible and even allows for recovery operations to a limited extent. The flashswap method used in this operation can also be regarded as a proof of concept for the future trading engine, where users will be allowed to mint with a fraction of the required collateral, as long as their minted synthetic asset is sold for collateral and is added to the position and the end of an atomic operation group. More on that will be communicated soon.

Let us know if there are any questions or concerns we should address.

Thank you for your engagement, youves core contributors, Markus, Alessandro, Florin