# StakingV2 Migration Pause - Post Mortem


By [Synthetix Exchange](https://paragraph.com/@kwenta) · 2023-07-18

---

At 2 AM UTC Friday, July 14th, 2023, a potential vulnerability was discovered by Kwenta smart contract engineers in the set of `StakingV2` contracts that put newly minted inflationary rewards at risk. This was later validated by a proof of concept (PoC) test case. Relevant parties were alerted and gathered. Swift action was taken by the Kwenta protocolDAO (pDAO) to pause the `StakingV2` contracts to secure staker funds.

Analysis continued into the weekend, with auditors engaged, to fully understand the issue and prepare a potential rollback of the migration.

> **A decision to reverse the migration was made on Monday.**

Thankfully, the launch of `StakingV2` had only occurred a few hours prior, and the vulnerability was discovered internally before funds were at risk. As of this time, all funds are safe, [and a reverse migration is taking place](https://mirror.xyz/kwenta.eth/xFomD0VE0H7o2sQ9zGeLyYPwCmtp8tLMXNTGtWy2UOQ).

### Issue At a high level

The vulnerability stems from the fact that `StakingV1`, when stake and unstake actions occur, does not alert the `StakingV2` contract of these state changes.

The Staking contract works when an action (like `stake/unstake`) is performed, and the amount of reward tokens owed to you in the time (based on your stake) that has passed is recorded. Because `StakingV1` actions do not alert `StakingV2` properly, it's possible to lose all or extract more rewards than is deserved.

The migration process was halted because the bug could be encountered during the migration flow from `StakingV1` to `StakingV2`. If a `V1` staker begins accruing `V2` rewards and then attempts to unstake from `StakingV1` in order to migrate to `StakingV2`, all `V2` accrued rewards (or rewards pertaining to the now unstaked "liquid" stake) are lost because the earned amount is not properly recorded on the `V2` contract.

[

🐞 · Kwenta/token@dfcc4ff
-------------------------

This is the main repository for the Kwenta token and respective system contracts (staking, escrow, distribution, etc..). - 🐞 · Kwenta/token@dfcc4ff

https://github.com

![](https://storage.googleapis.com/papyrus_images/3930a75aae24d0d4aa68a9f32b5c62726519bd9fc85a9afec45254c4c0fe432f.png)

](https://github.com/Kwenta/token/commit/dfcc4ff82fce5090b57e28b1f74a88b4ca252243)

    function test_Actual() public {
            fundAccountAndStakeV1(user1, 10 ether);
    
            vm.warp(block.timestamp + 1 weeks);
    
            vm.prank(user1);
            stakingRewardsV1.unstake(10 ether);
    
            uint reward = stakingRewardsV2.rewards(user1);
            assert(reward > 0);
    }
    

Eventually, this issue revealed deeper issues within the `StakingV2` contracts. For example, it is possible to manipulate the `V1` stake function to generate more rewards than are deserved.

By increasing your `V1` stake (potentially with a flashloan) before claiming rewards, your earned rewards can be manipulated because your previous staked amount isn’t properly accounted for on the V2 contract.

Therefore, when you go to claim your rewards, it will be based on your new staked balance \* the time since the original stake event.

    function test_Flash_Attack() public {
            uint256 totalNewRewards = 100_000 ether;
            fundAccountAndStakeV2(user1, 100 ether);
            fundAccountAndStakeV2(user2, 100 ether);
            addNewRewardsToStakingRewardsV2(totalNewRewards);
            vm.warp(block.timestamp + 4 weeks);
    
            // flash attack
            uint256 fundsBorrwedViaFlashLoan = 100_000 ether;
            fundAccountAndStakeV1(user3, fundsBorrwedViaFlashLoan);
            vm.prank(user3);
            stakingRewardsV2.getReward();
    
            uint256 escrowedBalance = rewardEscrowV2.escrowedBalanceOf(user3);
            // users claimed at least 99.8% of all the rewards
            assertCloseTo(escrowedBalance, totalNewRewards, 200 ether);
            // then user would pay back the flash loan
        }
    

Kwenta has multiple security processes to prevent issues like these; however, there is always a non-zero chance of an incident. All new code undergoes thorough testing utilizing Foundry's testing suite. Kwenta Engineering makes use of unit, integration, fork tests, and extended testing tools like fuzz and invariant tests.

We enforce 80+% code coverage. The code is then reviewed internally by the Core Contributors (CCs) and then external auditors, such as our partners Macro and Omniscia, for vulnerabilities and potential edge cases. Lastly, post-launch, we set up monitoring tools (Tenderly alerts for invariants) and bug bounty programs to limit any potential vulnerabilities that may crop up in production.

In this case, the logic behind the Staking contracts during development was a misunderstanding. It was not enough to pull `StakingV1` balances into the `StakingV2` contract **only** when `V2` balances were changed. Unfortunately, there were a few internal process failures that could have mitigated this issue.

`StakingV2` is a large, intricate system with many new technical features and expected legacy support. Most of these changes were delivered as a monolithic pull request on Github, making it hard for the internal team to scrutinize every feature and modification. Secondly, even though testing was comprehensive, the specific test cases where this could have been detected were not included.

Lastly, during audits, auditors did not thoroughly review this interaction between `StakingV1` and `StakingV2` because it was presumed that `StakingV1` functionality was out of the scope of this upgrade. These process failures forwarded this bug into production. Outcomes Kwenta CCs have decided the best course of action is to reverse the migration process until `StakingV2` issues have been resolved and the code re-audited. This is the best decision for the security of the protocol. The current plan is to remove all `StakingV1`\-related code from the `StakingV2` contracts. A migration contract will also help stakers port their `V1` escrow to `V2` escrow (therefore, it can be staked).

### In order to enable reverse migration, the following need to happen:

\- Upgrade the `StakingRewardsV2` contracts to unpause the unstake and `unstakeEscrow` functions

\- Provide a recovery function to collect the newly minted, not yet distributed rewards.

\- Upgrade the `RewardEscrowV2` function to fix the `earlyVestFee` at 0, so users can vest any rewards collected in the `V2` contracts without penalty

\- Collect the newly minted supply from the `StakingRewardsV2` contract

\- Point the SupplySchedule back to `StakingRewardsV1`

\- Launched a [simple UI](https://kwenta.eth.limo/dashboard/staking) to help stakers unstake from `V2` [Possible via governance:](https://discord.gg/kwentaio) Decrease the `treasuryDiversion` to route back the missed rewards to `V1` stakers

### For future migrations, an alternative approach will be used

A custom migration contract will be built that allows migration from V1 to V2 for escrow entries. Then the `Staking V2` contracts will no longer check `V1` balances; instead, all rewards will go to `V2` stakers, but everyone will be able to migrate.

**Join the Kwenta Community**
-----------------------------

If you haven't already, join the Kwenta community on [Discord](https://discord.gg/kwentaio).

To be the first to learn about new updates to Kwenta, follow us on [Twitter](https://twitter.com/kwenta_io).

To trade synthetic assets and futures, visit [Kwenta](https://kwenta.eth.limo/).

---

*Originally published on [Synthetix Exchange](https://paragraph.com/@kwenta/stakingv2-migration-pause-post-mortem)*
