Skip to content

Commit

Permalink
adjusting for bonus rewards in the preview function
Browse files Browse the repository at this point in the history
  • Loading branch information
aalavandhan committed Nov 28, 2024
1 parent 53056d1 commit 60d7716
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 67 deletions.
75 changes: 45 additions & 30 deletions contracts/TokenGeyser.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ contract TokenGeyser is
// Time-bonus params
//
uint256 public constant BONUS_DECIMALS = 2;
uint256 public constant BONUS_HUNDRED_PERC = 10 ** BONUS_DECIMALS;
uint256 public startBonus;
uint256 public bonusPeriodSec;

Expand Down Expand Up @@ -250,6 +251,7 @@ contract TokenGeyser is
uint256 stakingShareSecondsToBurn = 0;
uint256 sharesLeftToBurn = stakingSharesToBurn;
uint256 rewardAmount = 0;
uint256 totalUnlocked_ = totalUnlocked();
while (sharesLeftToBurn > 0) {
Stake storage lastStake = accountStakes[accountStakes.length - 1];
uint256 stakeTimeSec = block.timestamp.sub(lastStake.timestampSec);
Expand All @@ -260,7 +262,9 @@ contract TokenGeyser is
rewardAmount = computeNewReward(
rewardAmount,
newStakingShareSecondsToBurn,
stakeTimeSec
totalStakingShareSeconds,
stakeTimeSec,
totalUnlocked_
);
stakingShareSecondsToBurn = stakingShareSecondsToBurn.add(
newStakingShareSecondsToBurn
Expand All @@ -273,7 +277,9 @@ contract TokenGeyser is
rewardAmount = computeNewReward(
rewardAmount,
newStakingShareSecondsToBurn,
stakeTimeSec
totalStakingShareSeconds,
stakeTimeSec,
totalUnlocked_
);
stakingShareSecondsToBurn = stakingShareSecondsToBurn.add(
newStakingShareSecondsToBurn
Expand Down Expand Up @@ -320,29 +326,31 @@ contract TokenGeyser is
* unstake op. Any bonuses are already applied.
* @param stakingShareSeconds The stakingShare-seconds that are being burned for new
* distribution tokens.
* @param totalStakingShareSeconds_ The total stakingShare-seconds.
* @param stakeTimeSec Length of time for which the tokens were staked. Needed to calculate
* the time-bonus.
* @param totalUnlocked_ The reward tokens currently unlocked.
* @return Updated amount of distribution tokens to award, with any bonus included on the
* newly added tokens.
*/
function computeNewReward(
uint256 currentRewardTokens,
uint256 stakingShareSeconds,
uint256 stakeTimeSec
uint256 totalStakingShareSeconds_,
uint256 stakeTimeSec,
uint256 totalUnlocked_
) public view returns (uint256) {
uint256 newRewardTokens = totalUnlocked().mul(stakingShareSeconds).div(
totalStakingShareSeconds
);
uint256 newRewardTokens = (totalStakingShareSeconds_ > 0)
? totalUnlocked_.mul(stakingShareSeconds).div(totalStakingShareSeconds_)
: 0;

if (stakeTimeSec >= bonusPeriodSec) {
return currentRewardTokens.add(newRewardTokens);
}

uint256 oneHundredPct = 10 ** BONUS_DECIMALS;
uint256 bonusedReward = startBonus
.add(oneHundredPct.sub(startBonus).mul(stakeTimeSec).div(bonusPeriodSec))
.mul(newRewardTokens)
.div(oneHundredPct);
uint256 bonusedReward = (
BONUS_HUNDRED_PERC.sub(startBonus).mul(stakeTimeSec).div(bonusPeriodSec)
).add(startBonus).mul(newRewardTokens).div(BONUS_HUNDRED_PERC);
return currentRewardTokens.add(bonusedReward);
}

Expand Down Expand Up @@ -412,9 +420,9 @@ contract TokenGeyser is
* @return durationSec The amount of time in seconds when all the reward tokens unlock.
*/
function unlockDuration() public view returns (uint256 durationSec) {
durationSec = type(uint256).max;
durationSec = 0;
for (uint256 s = 0; s < unlockSchedules.length; s++) {
durationSec = Math.min(
durationSec = Math.max(
(block.timestamp < unlockSchedules[s].endAtSec)
? unlockSchedules[s].endAtSec - block.timestamp
: 0,
Expand Down Expand Up @@ -467,12 +475,13 @@ contract TokenGeyser is
uint256 userStake = totalStakedBy(addr).add(additionalStake);
uint256 totalStaked_ = totalStaked().add(additionalStake);

// Compute user's stake and rewards
uint256 userRewards = 0;
// Compute user's final stake share and rewards
uint256 rewardAmount = 0;
{
uint256 additionalStakingShareSeconds = durationSec.mul(
computeStakingShares(additionalStake)
);

uint256 newStakingShareSeconds = block
.timestamp
.sub(lastAccountingTimestampSec)
Expand All @@ -481,28 +490,34 @@ contract TokenGeyser is
uint256 totalStakingShareSeconds_ = totalStakingShareSeconds
.add(newStakingShareSeconds)
.add(additionalStakingShareSeconds);
uint256 newUserStakingShareSeconds = block
.timestamp
.sub(userTotals[addr].lastAccountingTimestampSec)
.add(durationSec)
.mul(userTotals[addr].stakingShares);
uint256 userStakingShareSeconds = userTotals[addr]
.stakingShareSeconds
.add(newUserStakingShareSeconds)
.add(additionalStakingShareSeconds);
userRewards = (totalStakingShareSeconds_ > 0)
? totalUnlocked_.mul(userStakingShareSeconds).div(
totalStakingShareSeconds_
)
: 0;

Stake[] memory accountStakes = userStakes[addr];
for (uint256 s = 0; s < accountStakes.length; s++) {
Stake memory stake_ = accountStakes[s];
uint256 stakeDurationSec = endTimestampSec.sub(stake_.timestampSec);
rewardAmount = computeNewReward(
rewardAmount,
stake_.stakingShares.mul(stakeDurationSec),
totalStakingShareSeconds_,
durationSec,
totalUnlocked_
);
}
rewardAmount = computeNewReward(
rewardAmount,
additionalStakingShareSeconds,
totalStakingShareSeconds_,
durationSec,
totalUnlocked_
);
}

return (
totalLocked_,
totalUnlocked_,
userStake,
totalStaked_,
userRewards,
rewardAmount,
endTimestampSec
);
}
Expand Down
5 changes: 3 additions & 2 deletions test/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ function checkSharesAprox(x, y) {

function checkAprox(x, y, delta_) {
const delta = BigInt(delta_);
const upper = y + delta;
const lower = y - delta;
const upper = BigInt(y) + delta;
const lower = BigInt(y) - delta;
expect(x).to.gte(lower).to.lte(upper);
}

Expand Down Expand Up @@ -89,6 +89,7 @@ async function deployGeyser(owner, params) {
}

module.exports = {
checkAprox,
checkAmplAprox,
checkSharesAprox,
invokeRebase,
Expand Down
50 changes: 32 additions & 18 deletions test/token_unlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
TimeHelpers,
$AMPL,
invokeRebase,
checkAprox,
checkAmplAprox,
checkSharesAprox,
deployGeyser,
Expand Down Expand Up @@ -480,11 +481,14 @@ describe("LockedPool", function () {
await ampl.approve(dist.target, $AMPL(300));
await dist.stake($AMPL(100));
await dist.lockTokens($AMPL(100), ONE_YEAR);
checkAprox(await dist.unlockDuration(), ONE_YEAR, 86400);
await TimeHelpers.increaseTime(ONE_YEAR / 2);
await dist.lockTokens($AMPL(100), ONE_YEAR);
checkAprox(await dist.unlockDuration(), ONE_YEAR, 86400);
await ampl.connect(anotherAccount).approve(dist.target, $AMPL(200));
await dist.connect(anotherAccount).stake($AMPL(200));
await TimeHelpers.increaseTime(ONE_YEAR / 10);
checkAprox(await dist.unlockDuration(), (ONE_YEAR * 9) / 10, 86400);
});

describe("when user history does exist", async function () {
Expand All @@ -496,7 +500,7 @@ describe("LockedPool", function () {
checkAmplAprox(r[1], 70);
expect(r[2]).to.eq($AMPL(100));
expect(r[3]).to.eq($AMPL(300));
checkAmplAprox(r[4], 52.5);
checkAmplAprox(r[4], 26.25);
const timeElapsed = t - _t;
expect(r[5] - _r[5])
.to.gte(timeElapsed - 1)
Expand All @@ -515,7 +519,7 @@ describe("LockedPool", function () {
checkAmplAprox(r[1], 70);
expect(r[2]).to.eq($AMPL(200));
expect(r[3]).to.eq($AMPL(400));
checkAmplAprox(r[4], 52.5);
checkAmplAprox(r[4], 26.25);
const timeElapsed = t - _t;
expect(r[5] - _r[5])
.to.gte(timeElapsed - 1)
Expand All @@ -530,14 +534,14 @@ describe("LockedPool", function () {

describe("after 3 months, without additional stake", function () {
it("should return the system state", async function () {
const r = await dist.previewRewards(ONE_YEAR/4, await owner.getAddress(), 0);
const r = await dist.previewRewards(ONE_YEAR / 4, await owner.getAddress(), 0);
const t = await TimeHelpers.currentTime();
checkAmplAprox(r[0], 80);
checkAmplAprox(r[1], 120);
expect(r[2]).to.eq($AMPL(100));
expect(r[3]).to.eq($AMPL(300));
checkAmplAprox(r[4], 65.8);
const timeElapsed = t - _t + ONE_YEAR/4;
const timeElapsed = t - _t + ONE_YEAR / 4;
expect(r[5] - _r[5])
.to.gte(timeElapsed - 1)
.to.lte(timeElapsed + 1);
Expand All @@ -551,33 +555,39 @@ describe("LockedPool", function () {

describe("after 3 months, with additional stake", function () {
it("should return the system state", async function () {
const r = await dist.previewRewards(ONE_YEAR/4, await owner.getAddress(), $AMPL(100));
const r = await dist.previewRewards(
ONE_YEAR / 4,
await owner.getAddress(),
$AMPL(100),
);
const t = await TimeHelpers.currentTime();
checkAmplAprox(r[0], 80);
checkAmplAprox(r[1], 120);
expect(r[2]).to.eq($AMPL(200));
expect(r[3]).to.eq($AMPL(400));
checkAmplAprox(r[4], 73.3333);
const timeElapsed = t - _t + ONE_YEAR/4;
const timeElapsed = t - _t + ONE_YEAR / 4;
expect(r[5] - _r[5])
.to.gte(timeElapsed - 1)
.to.lte(timeElapsed + 1);
await ampl.approve(dist.target, $AMPL(100));
await dist.stake($AMPL(100));
await TimeHelpers.increaseTime(ONE_YEAR / 4);
checkAprox(await dist.unlockDuration(), 0.65 * ONE_YEAR, 86400);
await expect(await dist.unstake($AMPL(200)))
.to.emit(dist, "TokensClaimed")
.withArgs(await owner.getAddress(), "73333353473");
await TimeHelpers.increaseTime(ONE_YEAR * 10);
await expect(await dist.connect(anotherAccount).unstake($AMPL(200)))
.to.emit(dist, "TokensClaimed")
.withArgs(await anotherAccount.getAddress(), "126666646527");
expect(await dist.unlockDuration()).to.eq(0);
});
});
});

describe("when user history does not exist", async function () {
describe("current state, with no additional stake", function(){
describe("current state, with no additional stake", function () {
it("should return the system state", async function () {
const r = await dist.previewRewards(0, ethers.ZeroAddress, 0);
const t = await TimeHelpers.currentTime();
Expand All @@ -591,9 +601,9 @@ describe("LockedPool", function () {
.to.gte(timeElapsed - 1)
.to.lte(timeElapsed + 1);
});
})
});

describe("current state, with additional stake", function(){
describe("current state, with additional stake", function () {
it("should return the system state", async function () {
const r = await dist.previewRewards(0, ethers.ZeroAddress, $AMPL(100));
const t = await TimeHelpers.currentTime();
Expand All @@ -607,39 +617,43 @@ describe("LockedPool", function () {
.to.gte(timeElapsed - 1)
.to.lte(timeElapsed + 1);
});
})
});

describe("after 3 months, without additional stake", function(){
describe("after 3 months, without additional stake", function () {
it("should return the system state", async function () {
const r = await dist.previewRewards(ONE_YEAR/4, ethers.ZeroAddress, 0);
const r = await dist.previewRewards(ONE_YEAR / 4, ethers.ZeroAddress, 0);
const t = await TimeHelpers.currentTime();
checkAmplAprox(r[0], 79.99);
checkAmplAprox(r[1], 120);
expect(r[2]).to.eq(0n);
expect(r[3]).to.eq($AMPL(300));
checkAmplAprox(r[4], 0);
const timeElapsed = t - _t + ONE_YEAR/4;
const timeElapsed = t - _t + ONE_YEAR / 4;
expect(r[5] - _r[5])
.to.gte(timeElapsed - 1)
.to.lte(timeElapsed + 1);
});
})
});

describe("after 3 months, with additional stake", function(){
describe("after 3 months, with additional stake", function () {
it("should return the system state", async function () {
const r = await dist.previewRewards(ONE_YEAR/4, ethers.ZeroAddress, $AMPL(100));
const r = await dist.previewRewards(
ONE_YEAR / 4,
ethers.ZeroAddress,
$AMPL(100),
);
const t = await TimeHelpers.currentTime();
checkAmplAprox(r[0], 79.99);
checkAmplAprox(r[1], 120);
expect(r[2]).to.eq($AMPL(100));
expect(r[3]).to.eq($AMPL(400));
checkAmplAprox(r[4], 16.666);
const timeElapsed = t - _t + ONE_YEAR/4;
const timeElapsed = t - _t + ONE_YEAR / 4;
expect(r[5] - _r[5])
.to.gte(timeElapsed - 1)
.to.lte(timeElapsed + 1);
});
})
});
});
});
});
Loading

0 comments on commit 60d7716

Please sign in to comment.