TempleGold

TempleDAO
Foundry
25,000 USDC
View results
Submission Details
Severity: high
Invalid

TempleGoldStaking::migrateWithdraw does not update user stake in new staking contract

Summary

The migrateWithdraw function is designed to facilitate the migration of funds to a new staking contract. However, the function fails to update the staker's balance on the new contract after moving the funds, leading to potential discrepancies and loss of staked balances for users.

https://github.com/TempleDAO/temple/blob/templegold/protocol/contracts/templegold/TempleGoldStaking.sol#L168

Vulnerability Details

The function correctly transfers the staked funds to the new staking contract but omits the crucial step of updating the staker's balance on the new contract. As a result, the new staking contract does not reflect the migrated staked amount for the user, causing inconsistencies and potential loss of staked funds.

function migrateWithdraw(address staker, uint256 index) external override onlyMigrator returns (uint256) {
if (staker == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
StakeInfo storage _stakeInfo = _stakeInfos[staker][index];
uint256 stakerBalance = _stakeInfo.amount;
@> _withdrawFor(_stakeInfo, staker, msg.sender, index, _stakeInfo.amount, true, staker);
return stakerBalance;
}
// This function does not update user stake
function _withdrawFor(
StakeInfo storage stakeInfo,
address staker,
address toAddress,
uint256 stakeIndex,
uint256 amount,
bool claimRewards,
address rewardsToAddress
) internal updateReward(staker, stakeIndex) {
if (amount == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
uint256 _stakeAmount = stakeInfo.amount;
if (_stakeAmount < amount)
{ revert CommonEventsAndErrors.InsufficientBalance(address(stakingToken), amount, _stakeAmount); }
unchecked {
stakeInfo.amount = _stakeAmount - amount;
}
_balances[staker] -= amount;
totalSupply -= amount;
_moveDelegates(delegates[staker], address(0), amount);
stakingToken.safeTransfer(toAddress, amount);
emit Withdrawn(staker, toAddress, stakeIndex, amount);
if (claimRewards) {
// can call internal because user reward already updated
_getReward(staker, rewardsToAddress, stakeIndex);
}
}

Proof of Concept

  1. Executor calls setMigrator on the old staking contract, passes the new staking contract as the migrator.

  2. Bob stakes 100 Temple Token

  3. After a few days, Someone calls distributeRewards

  4. After a few days, bob checks staking balance and it is looking good.

  5. Protocol decides to migrate to a new staking contract

  6. Protocol calls migrateWithdraw for bob stake

  7. The funds successfully moved to the new staking contract but bob stakes were not updated in the new staking contract.

Proof of Code

vm.startPrank(executor);
staking.setMigrator(address(mockStaking));
uint256 _rewardDuration = 16 weeks;
uint32 _vestingPeriod = uint32(_rewardDuration);
_setVestingFactor(templeGold);
_setVestingPeriod(_vestingPeriod);
_setRewardDuration(_rewardDuration);
// bob stakes
vm.startPrank(bob);
deal(address(templeToken), bob, 1000 ether, true);
_approve(address(templeToken), address(staking), type(uint).max);
staking.stake(100 ether);
skip(2 days);
// distibute Reward
staking.distributeRewards();
skip(2 days);
uint256 bobStakingBalance = staking.balanceOf(bob);
// new staking contract
vm.startPrank(address(mockStaking)); // this simulates calling the migrateWithdraw from the new staking contract
staking.migrateWithdraw(bob, 1);
uint256 bobNewStakingBalance = mockStaking.balanceOf(bob);
assert(bobStakingBalance != bobNewStakingBalance);
vm.startPrank(bob);
vm.expectRevert();
mockStaking.withdraw(bobStakingBalance, 1, false);

Impact

Users will experience a loss of staked funds, leading to a potential financial loss.

Tools Used

Manual Review

Recommendations

Modify the migrateWithdraw or other neccesary function to include the step of updating the staker's balance on the new staking contract. Ensure that the staker's balance is accurately reflected in the new contract after migration.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.