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.
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.
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).
- 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 to help stakers unstake from V2
Possible via governance: Decrease the treasuryDiversion
to route back the missed rewards to V1
stakers
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.
If you haven't already, join the Kwenta community on Discord.
To be the first to learn about new updates to Kwenta, follow us on Twitter.
To trade synthetic assets and futures, visit Kwenta.