Skip to content

Commit

Permalink
fix: ensure thawing period when unstaking always rounds up
Browse files Browse the repository at this point in the history
  • Loading branch information
pcarranzav committed Jan 26, 2024
1 parent 726bfb2 commit 25d0752
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 4 deletions.
10 changes: 7 additions & 3 deletions packages/contracts/contracts/staking/libs/MathUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@ library MathUtils {
/**
* @dev Calculates the weighted average of two values pondering each of these
* values based on configured weights. The contribution of each value N is
* weightN/(weightA + weightB).
* weightN/(weightA + weightB). The calculation rounds up to ensure the result
* is always greater than the smallest of the two values.
* @param valueA The amount for value A
* @param weightA The weight to use for value A
* @param valueB The amount for value B
* @param weightB The weight to use for value B
*/
function weightedAverage(
function weightedAverageRoundingUp(
uint256 valueA,
uint256 weightA,
uint256 valueB,
uint256 weightB
) internal pure returns (uint256) {
return valueA.mul(weightA).add(valueB.mul(weightB)).div(weightA.add(weightB));
return
valueA.mul(weightA).add(valueB.mul(weightB)).add(weightA.add(weightB).sub(1)).div(
weightA.add(weightB)
);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/contracts/staking/libs/Stakes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ library Stakes {
// Take into account period averaging for multiple unstake requests
uint256 lockingPeriod = _period;
if (stake.tokensLocked > 0) {
lockingPeriod = MathUtils.weightedAverage(
lockingPeriod = MathUtils.weightedAverageRoundingUp(
MathUtils.diffOrZero(stake.tokensLockedUntil, block.number), // Remaining thawing period
stake.tokensLocked, // Weighted by remaining unstaked tokens
_period, // Thawing period
Expand Down
34 changes: 34 additions & 0 deletions packages/contracts/test/unit/staking/staking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,40 @@ describe('Staking:Stakes', () => {
expect(afterIndexerStake.tokensLockedUntil).eq(expectedLockedUntil)
})

it('should always increase the thawing period on subsequent unstakes', async function () {
const tokensToUnstake = toGRT('10')
const tokensToUnstakeSecondTime = toGRT('0.000001')
const thawingPeriod = toBN(await staking.thawingPeriod())

// Unstake (1)
const tx1 = await staking.connect(indexer).unstake(tokensToUnstake)
const receipt1 = await tx1.wait()
const event1: Event = receipt1.events.pop()
const tokensLockedUntil1 = event1.args['until']

// Move forward before the tokens are unlocked for withdrawal
await helpers.mineUpTo(tokensLockedUntil1.sub(5))

// Calculate locking time for tokens taking into account the previous unstake request
const currentBlock = await helpers.latestBlock()

// Ensure at least 1 block is added (i.e. the weighted average rounds up)
const expectedLockedUntil = tokensLockedUntil1.add(1)

// Unstake (2)
const tx2 = await staking.connect(indexer).unstake(tokensToUnstakeSecondTime)
const receipt2 = await tx2.wait()

// Verify events
const event2: Event = receipt2.events.pop()
expect(event2.args['until']).eq(expectedLockedUntil)

// Verify state
const afterIndexerStake = await staking.stakes(indexer.address)
expect(afterIndexerStake.tokensLocked).eq(tokensToUnstake.add(tokensToUnstakeSecondTime)) // we unstaked two times
expect(afterIndexerStake.tokensLockedUntil).eq(expectedLockedUntil)
})

it('should unstake and withdraw if some tokens are unthawed', async function () {
const tokensToUnstake = toGRT('10')
const thawingPeriod = toBN(await staking.thawingPeriod())
Expand Down

0 comments on commit 25d0752

Please sign in to comment.