Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tokenomics handover #261

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
21 changes: 2 additions & 19 deletions contracts/RewardEscrowV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -320,25 +320,8 @@ contract RewardEscrowV2 is
returns (uint256 quantity, uint256 fee)
{
uint256 escrowAmount = _entry.escrowAmount;

// Full escrow amounts claimable if block.timestamp equal to or after entry endTime
if (block.timestamp >= _entry.endTime) {
quantity = escrowAmount;
} else {
fee = _earlyVestFee(_entry);
quantity = escrowAmount - fee;
}
}

function _earlyVestFee(VestingEntry memory _entry)
internal
view
returns (uint256 earlyVestFee)
{
uint256 timeUntilVest = _entry.endTime - block.timestamp;
// Fee starts by default at 90% (but could be any percentage) and falls linearly
earlyVestFee =
(_entry.escrowAmount * _entry.earlyVestingFee * timeUntilVest) / (100 * _entry.duration);
quantity = escrowAmount;
fee = 0;
}

/*///////////////////////////////////////////////////////////////
Expand Down
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ out = 'out'
libs = ['node_modules', 'lib']
test = 'test/foundry'
cache_path = 'forge-cache'
optimizer_runs = 1_000_000
optimizer_runs = 800_000

[fmt]
line_length = 100
Expand Down
51 changes: 51 additions & 0 deletions scripts/Upgrade.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.19;

import {Script, console} from "forge-std/Script.sol";
import {RewardEscrowV2} from "../contracts/RewardEscrowV2.sol";
import {StakingRewardsV2} from "../contracts/StakingRewardsV2.sol";
import {OptimismParameters} from "scripts/utils/parameters/OptimismParameters.sol";

/// @dev steps to upgrade RewardEscrowV2 on Optimism:
/// (1) ensure the .env file contains the following variables:
/// - DEPLOYER_PRIVATE_KEY - the private key of the deployer
/// - ETHERSCAN_API_KEY - the API key of the Optimism Etherscan account (a normal etherscan API key will not work)
/// - ARCHIVE_NODE_URL_L2 - the archive node URL of the Optimism network
/// (2) load the variables in the .env file via `source .env`
/// (3) run `forge script scripts/Upgrade.s.sol:UpgradeRewardEscrowV2 --rpc-url $ARCHIVE_NODE_URL_L2 --etherscan-api-key $ETHERSCAN_API_KEY --broadcast --verify -vvvv``
/// (4) Call upgradeTo() on the proxy with the new implementation address
contract UpgradeRewardEscrowV2 is Script, OptimismParameters {
function run() public {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

RewardEscrowV2 rewardEscrowImplementation = new RewardEscrowV2(KWENTA_TOKEN, STAKING_REWARDS_NOTIFIER);

vm.stopBroadcast();
}
}


/// @dev steps to upgrade StakingRewardsV2 on Optimism:
/// (1) ensure the .env file contains the following variables:
/// - DEPLOYER_PRIVATE_KEY - the private key of the deployer
/// - ETHERSCAN_API_KEY - the API key of the Optimism Etherscan account (a normal etherscan API key will not work)
/// - ARCHIVE_NODE_URL_L2 - the archive node URL of the Optimism network
/// (2) load the variables in the .env file via `source .env`
/// (3) run `forge script scripts/Upgrade.s.sol:UpgradeStakingRewardsV2 --rpc-url $ARCHIVE_NODE_URL_L2 --etherscan-api-key $ETHERSCAN_API_KEY --broadcast --verify -vvvv``
/// (4) Call upgradeTo() on the proxy with the new implementation address
contract UpgradeStakingRewardsV2 is Script, OptimismParameters {
function run() public {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

StakingRewardsV2 stakingRewardsV2Implementation = new StakingRewardsV2(
KWENTA_TOKEN,
USDC_TOKEN,
REWARD_ESCROW_V2,
STAKING_REWARDS_NOTIFIER
);

vm.stopBroadcast();
}
}
12 changes: 12 additions & 0 deletions scripts/utils/parameters/OptimismParameters.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.19;

contract OptimismParameters {
address public constant KWENTA_TOKEN = 0x920Cf626a271321C151D027030D5d08aF699456b;

address public constant STAKING_REWARDS_NOTIFIER = 0xb176DaD2916db0905cd2D65ed54FDC3a878aFFe4;

address public constant REWARD_ESCROW_V2 = 0xb2a20fCdc506a685122847b21E34536359E94C56;

address public constant USDC_TOKEN = 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85;
}
6 changes: 3 additions & 3 deletions test/foundry/integration/escrow.migrator.fork.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -983,9 +983,9 @@ contract StakingV2MigrationForkTests is EscrowMigratorTestHelpers {
entryIDs = rewardEscrowV2.getAccountVestingEntryIDs(user1, 0, numVestingEntries);
(uint256 newTotal, uint256 newTotalFee) = rewardEscrowV2.getVestingQuantity(entryIDs);

// check within 1% of target
assertCloseTo(newTotal, total, total / 100);
assertCloseTo(newTotalFee, totalFee, totalFee / 100);
// new entries should no longer have a fee
assertEq(newTotal, 17.246155111414632908 ether);
assertEq(newTotalFee, 0);
}

/*//////////////////////////////////////////////////////////////
Expand Down
4 changes: 2 additions & 2 deletions test/foundry/integration/stakingV2.migration.fork.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ contract StakingV2MigrationForkTests is StakingTestHelpers {
// check user got some funds
assertGt(kwenta.balanceOf(user2), 0);

// check treasury got some funds
// check treasury got nothing (vest fees are now 0)
uint256 treasuryBalanceAfter = kwenta.balanceOf(treasury);
assertGt(treasuryBalanceAfter, treasuryBalanceBefore);
assertEq(treasuryBalanceAfter, treasuryBalanceBefore);
}

function test_Cannot_Vest_If_Treasury_Transfer_Fails() public {
Expand Down
63 changes: 20 additions & 43 deletions test/foundry/unit/RewardEscrowV2/RewardEscrowV2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {
vm.warp(block.timestamp + 26 weeks);

(uint256 claimable,) = rewardEscrowV2.getVestingEntryClaimable(1);
assertEq(claimable, 11 ether / 2);
assertEq(claimable, 10 ether);
}

function test_Correct_Amount_Claimable_After_6_Months_Fuzz(uint32 _amount) public {
Expand All @@ -223,13 +223,8 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {
vm.warp(block.timestamp + 26 weeks);

(uint256 claimable, uint256 fee) = rewardEscrowV2.getVestingEntryClaimable(1);

uint256 maxFee = amount * 90 / 100;
uint256 earlyVestFee = maxFee * 26 weeks / 52 weeks;
uint256 expectedClaimable = amount - earlyVestFee;

assertEq(claimable, expectedClaimable);
assertEq(fee, earlyVestFee);
assertEq(claimable, amount);
assertEq(fee, 0);
}

function test_Correct_Amount_Claimable_After_1_Year() public {
Expand Down Expand Up @@ -472,10 +467,10 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {

(uint256 total, uint256 totalFee) = rewardEscrowV2.getVestingQuantity(entries);

// 55% should be claimable
assertEq(total, 220 ether);
// 45% should be the fee
assertEq(totalFee, 180 ether);
// 100% should be claimable (fee is now 0)
assertEq(total, 400 ether);
// 0 should be the fee
assertEq(totalFee, 0);
}

/*//////////////////////////////////////////////////////////////
Expand All @@ -500,8 +495,8 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {

// check initial values
(uint256 claimable, uint256 fee) = rewardEscrowV2.getVestingEntryClaimable(1);
assertEq(claimable, 550 ether);
assertEq(fee, 450 ether);
assertEq(claimable, 1000 ether);
assertEq(fee, 0);
assertEq(rewardEscrowV2.totalEscrowedBalance(), 1000 ether);
assertEq(rewardEscrowV2.totalEscrowedAccountBalance(address(this)), 1000 ether);
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 0);
Expand All @@ -514,15 +509,15 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {
uint256 treasuryBalanceAfter = kwenta.balanceOf(treasury);
uint256 treasuryReceived = treasuryBalanceAfter - treasuryBalanceBefore;

// 22.5% should go to the treasury
assertEq(treasuryReceived, 225 ether);
// 0% should go to the treasury
assertEq(treasuryReceived, 0);

// 22.5% should go to RewardsNotifier
assertEq(kwenta.balanceOf(address(rewardsNotifier)), 225 ether);
// 0% should go to RewardsNotifier
assertEq(kwenta.balanceOf(address(rewardsNotifier)), 0);

// 55% should go to the staker
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 550 ether);
assertEq(kwenta.balanceOf(address(this)), 550 ether);
// 100% should go to the staker
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 1000 ether);
assertEq(kwenta.balanceOf(address(this)), 1000 ether);
assertEq(rewardEscrowV2.totalEscrowedAccountBalance(address(this)), 0);

// Nothing should be left in reward escrow
Expand All @@ -544,24 +539,6 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {
assertEq(earlyVestingFee, 90);
}

function test_vest_Should_Properly_Emit_Event_With_Distributor() public {
appendRewardEscrowEntryV2(address(this), 1000 ether);
vm.warp(block.timestamp + 26 weeks);

// check initial values
(uint256 claimable, uint256 fee) = rewardEscrowV2.getVestingEntryClaimable(1);
assertEq(claimable, 550 ether);
assertEq(fee, 450 ether);
assertEq(rewardEscrowV2.totalEscrowedBalance(), 1000 ether);
assertEq(rewardEscrowV2.totalEscrowedAccountBalance(address(this)), 1000 ether);
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 0);

vm.expectEmit(true, true, true, true);
emit EarlyVestFeeSent(225 ether, 225 ether);
entryIDs.push(1);
rewardEscrowV2.vest(entryIDs);
}

function test_Should_Revert_If_Kwenta_Transfer_Fails() public {
appendRewardEscrowEntryV2(address(this), 1000 ether);

Expand Down Expand Up @@ -779,7 +756,7 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {
function test_vest_Should_Emit_Correct_Event_For_Three_Entries() public {
create3EntriesWithDifferentDurations(address(this));
vm.expectEmit(true, true, true, true);
emit Vested(address(this), 775 ether);
emit Vested(address(this), 1000 ether);
vestAllEntries(address(this));
}

Expand All @@ -794,7 +771,7 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {
create3EntriesWithDifferentDurations(address(this));
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 0);
vestAllEntries(address(this));
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 775 ether);
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 1000 ether);
}

function test_vest_Three_Entries_Should_Update_totalEscrowedBalance() public {
Expand All @@ -814,14 +791,14 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup {
rewardEscrowV2.vest(entryIDs);

// Check only 2 entries were vested
assertEq(kwenta.balanceOf(address(this)), 775 ether);
assertEq(kwenta.balanceOf(address(this)), 1000 ether);
assertEq(kwenta.balanceOf(address(rewardEscrowV2)), 0 ether);

// Attempt to vest again
rewardEscrowV2.vest(entryIDs);

// Check only 2 entries were vested
assertEq(kwenta.balanceOf(address(this)), 775 ether);
assertEq(kwenta.balanceOf(address(this)), 1000 ether);
assertEq(kwenta.balanceOf(address(rewardEscrowV2)), 0 ether);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ contract RewardEscrowV2VestingChangesTests is DefaultStakingV2Setup {

// check vested balance
uint256 balanceAfter = kwenta.balanceOf(user1);
uint256 amountVestedAfterFee = escrowAmount - (escrowAmount * earlyVestingFee / 100);
uint256 amountVestedAfterFee = escrowAmount;
assertEq(balanceAfter, balanceBefore + amountVestedAfterFee);
}

Expand Down Expand Up @@ -131,7 +131,7 @@ contract RewardEscrowV2VestingChangesTests is DefaultStakingV2Setup {

// check vested balance
uint256 balanceAfter = kwenta.balanceOf(user1);
uint256 amountVestedAfterFee = escrowAmount - (escrowAmount * earlyVestingFee / 100);
uint256 amountVestedAfterFee = escrowAmount;
assertEq(balanceAfter, balanceBefore + amountVestedAfterFee);
}

Expand All @@ -154,11 +154,11 @@ contract RewardEscrowV2VestingChangesTests is DefaultStakingV2Setup {

// check vested balance
uint256 balanceAfter = kwenta.balanceOf(user1);
assertEq(balanceAfter, userBalanceBefore);
assertEq(balanceAfter, userBalanceBefore + escrowAmount);
uint256 treasuryBalanceAfter = kwenta.balanceOf(treasury);
assertEq(treasuryBalanceAfter, treasuryBalanceBefore + escrowAmount * 50 / 100);
assertEq(treasuryBalanceAfter, treasuryBalanceBefore);
uint256 earlyVestFeeDistributorBalanceAfter = kwenta.balanceOf(address(rewardsNotifier));
assertEq(earlyVestFeeDistributorBalanceAfter, escrowAmount * 50 / 100);
assertEq(earlyVestFeeDistributorBalanceAfter, 0);
}

/*//////////////////////////////////////////////////////////////
Expand All @@ -185,7 +185,7 @@ contract RewardEscrowV2VestingChangesTests is DefaultStakingV2Setup {

// check vested balance
uint256 balanceAfter = kwenta.balanceOf(user1);
uint256 amountVestedAfterFee = escrowAmount - (escrowAmount * earlyVestingFee / 100);
uint256 amountVestedAfterFee = escrowAmount;
assertEq(balanceAfter, balanceBefore + amountVestedAfterFee);
}

Expand Down Expand Up @@ -222,7 +222,7 @@ contract RewardEscrowV2VestingChangesTests is DefaultStakingV2Setup {

// check vested balance
uint256 balanceAfter = kwenta.balanceOf(user1);
uint256 amountVestedAfterFee = escrowAmount - (escrowAmount * earlyVestingFee / 100);
uint256 amountVestedAfterFee = escrowAmount;
assertEq(balanceAfter, balanceBefore + amountVestedAfterFee);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ contract StakingRewardsNotifierTest is DefaultStakingV2Setup {

// check initial values
(uint256 claimable, uint256 fee) = rewardEscrowV2.getVestingEntryClaimable(1);
assertEq(claimable, 550 ether);
assertEq(fee, 450 ether);
assertEq(claimable, 1000 ether);
assertEq(fee, 0);
assertEq(rewardEscrowV2.totalEscrowedBalance(), 1000 ether);
assertEq(rewardEscrowV2.totalEscrowedAccountBalance(address(this)), 1000 ether);
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 0);
Expand All @@ -148,15 +148,15 @@ contract StakingRewardsNotifierTest is DefaultStakingV2Setup {
uint256 treasuryBalanceAfter = kwenta.balanceOf(treasury);
uint256 treasuryReceived = treasuryBalanceAfter - treasuryBalanceBefore;

// 22.5% should go to the treasury
assertEq(treasuryReceived, 225 ether);
// 0% should go to the treasury
assertEq(treasuryReceived, 0);

// 22.5% should go to RewardsNotifier
assertEq(kwenta.balanceOf(address(rewardsNotifier)), 225 ether);
// 0% should go to RewardsNotifier
assertEq(kwenta.balanceOf(address(rewardsNotifier)), 0);

// 55% should go to the staker
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 550 ether);
assertEq(kwenta.balanceOf(address(this)), 550 ether);
// 100% should go to the staker
assertEq(rewardEscrowV2.totalVestedAccountBalance(address(this)), 1000 ether);
assertEq(kwenta.balanceOf(address(this)), 1000 ether);
assertEq(rewardEscrowV2.totalEscrowedAccountBalance(address(this)), 0);

// Nothing should be left in reward escrow
Expand All @@ -170,6 +170,6 @@ contract StakingRewardsNotifierTest is DefaultStakingV2Setup {
supplySchedule.mint();
uint256 balanceAfter = kwenta.balanceOf(address(stakingRewardsV2));
assertGt(balanceAfter, balanceBefore);
assertEq(balanceAfter - balanceBefore, 225 ether + mintAmount);
assertEq(balanceAfter - balanceBefore, mintAmount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup {
rewards = rewardEscrowV2.escrowedBalanceOf(user1);
rewardsUsdc = usdc.balanceOf(user1);
assertEq(rewards, expectedRewards);
assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 10);
assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 15);
}
}

Expand Down
Loading