-
Notifications
You must be signed in to change notification settings - Fork 18
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
👷 KSX - Remove escrow on inflationary rewards and remove staking cooldown #252
base: main
Are you sure you want to change the base?
Changes from 22 commits
62462f1
6006911
1052a6d
5142e90
1735867
43300af
371f847
b4b69a7
d33ee53
9dee0eb
fb03e81
6336698
7ff1a0a
15eea33
9cb9509
427134b
827e85c
e4d6166
48a2b16
0d173e7
0f1db85
c9e48f6
bf210e9
c6d26b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ import {IStakingRewardsIntegrator} from "./interfaces/IStakingRewardsIntegrator. | |
/// @title KWENTA Escrow Migrator | ||
/// Used to migrate escrow entries from RewardEscrowV1 to RewardEscrowV2 | ||
/// @author tommyrharper ([email protected]) | ||
/// @author Flocqst ([email protected]) | ||
contract EscrowMigrator is | ||
IEscrowMigrator, | ||
Ownable2StepUpgradeable, | ||
|
@@ -410,7 +411,6 @@ contract EscrowMigrator is | |
_payForMigration(_account); | ||
|
||
uint256 migratedEscrow; | ||
uint256 cooldown = stakingRewardsV2.cooldownPeriod(); | ||
mapping(uint256 => VestingEntry) storage userEntries = registeredVestingSchedules[_account]; | ||
|
||
for (uint256 i = 0; i < _entryIDs.length; i++) { | ||
|
@@ -432,15 +432,6 @@ contract EscrowMigrator is | |
registeredEntry.migrated = true; | ||
migratedEscrow += originalEscrowAmount; | ||
|
||
/// @dev it essential for security that the duration is not less than the cooldown period, | ||
/// otherwise the user could do a governance attack by bypassing the unstaking cooldown lock | ||
/// by migrating their escrow then staking, voting, and vesting immediately | ||
if (duration < cooldown) { | ||
uint256 timeCreated = endTime - duration; | ||
duration = cooldown; | ||
endTime = timeCreated + cooldown; | ||
} | ||
|
||
IRewardEscrowV2.VestingEntry memory entry = IRewardEscrowV2.VestingEntry({ | ||
escrowAmount: originalEscrowAmount, | ||
duration: duration, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ import {IEscrowMigrator} from "./interfaces/IEscrowMigrator.sol"; | |
/// @title KWENTA Reward Escrow V2 | ||
/// @author Originally inspired by SYNTHETIX RewardEscrow | ||
/// @author Kwenta's RewardEscrow V1 by JaredBorders ([email protected]), JChiaramonte7 ([email protected]) | ||
/// @author RewardEscrowV2 by tommyrharper ([email protected]) | ||
/// @author RewardEscrowV2 by tommyrharper ([email protected]), Flocqst ([email protected]) | ||
/// @notice Updated version of Synthetix's RewardEscrow with new features specific to Kwenta | ||
contract RewardEscrowV2 is | ||
IRewardEscrowV2, | ||
|
@@ -379,7 +379,7 @@ contract RewardEscrowV2 is | |
unchecked { | ||
amountToUnstake = totalWithFee - unstakedEscrow; | ||
} | ||
stakingRewards.unstakeEscrowSkipCooldown(msg.sender, amountToUnstake); | ||
stakingRewards.unstakeEscrowAdmin(msg.sender, amountToUnstake); | ||
jcmonte marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// update balances | ||
|
@@ -432,8 +432,7 @@ contract RewardEscrowV2 is | |
if (_earlyVestingFee > MAXIMUM_EARLY_VESTING_FEE) revert EarlyVestingFeeTooHigh(); | ||
if (_earlyVestingFee < MINIMUM_EARLY_VESTING_FEE) revert EarlyVestingFeeTooLow(); | ||
if (_deposit == 0) revert ZeroAmount(); | ||
uint256 minimumDuration = stakingRewards.cooldownPeriod(); | ||
if (_duration < minimumDuration || _duration > MAX_DURATION) revert InvalidDuration(); | ||
if (_duration == 0 || _duration > MAX_DURATION) revert InvalidDuration(); | ||
|
||
/// @dev this will revert if the kwenta token transfer fails | ||
kwenta.transferFrom(msg.sender, address(this), _deposit); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ import {IRewardEscrowV2} from "./interfaces/IRewardEscrowV2.sol"; | |
/// @title KWENTA Staking Rewards V2 | ||
/// @author Originally inspired by SYNTHETIX StakingRewards | ||
/// @author Kwenta's StakingRewards V1 by JaredBorders ([email protected]), JChiaramonte7 ([email protected]) | ||
/// @author StakingRewardsV2 (this) by tommyrharper ([email protected]) | ||
/// @author StakingRewardsV2 (this) by tommyrharper ([email protected]), Flocqst ([email protected]) | ||
/// @notice Updated version of Synthetix's StakingRewards with new features specific to Kwenta | ||
contract StakingRewardsV2 is | ||
IStakingRewardsV2, | ||
|
@@ -27,12 +27,6 @@ contract StakingRewardsV2 is | |
CONSTANTS/IMMUTABLES | ||
///////////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice minimum time length of the unstaking cooldown period | ||
uint256 public constant MIN_COOLDOWN_PERIOD = 1 weeks; | ||
|
||
/// @notice maximum time length of the unstaking cooldown period | ||
uint256 public constant MAX_COOLDOWN_PERIOD = 52 weeks; | ||
|
||
/// @notice Contract for KWENTA ERC20 token - used for BOTH staking and rewards | ||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable | ||
IKwenta public immutable kwenta; | ||
|
@@ -81,9 +75,6 @@ contract StakingRewardsV2 is | |
/// @notice summation of rewardRate divided by total staked tokens | ||
uint256 public rewardPerTokenStored; | ||
|
||
/// @inheritdoc IStakingRewardsV2 | ||
uint256 public cooldownPeriod; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are we redeploying staking v2 or upgrading? because im pretty sure if we upgrade this removal will brick everything |
||
|
||
/// @notice represents the rewardPerToken | ||
/// value the last time the staker calculated earned() rewards | ||
mapping(address => uint256) public userRewardPerTokenPaid; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it wont let me comment a couple lines below for some reason but |
||
|
@@ -136,17 +127,6 @@ contract StakingRewardsV2 is | |
if (msg.sender != address(rewardsNotifier)) revert OnlyRewardsNotifier(); | ||
} | ||
|
||
/// @notice only allow execution after the unstaking cooldown period has elapsed | ||
modifier afterCooldown(address _account) { | ||
_afterCooldown(_account); | ||
_; | ||
} | ||
|
||
function _afterCooldown(address _account) internal view { | ||
uint256 canUnstakeAt = userLastStakeTime[_account] + cooldownPeriod; | ||
if (canUnstakeAt > block.timestamp) revert MustWaitForUnlock(canUnstakeAt); | ||
} | ||
|
||
/*/////////////////////////////////////////////////////////////// | ||
CONSTRUCTOR / INITIALIZER | ||
///////////////////////////////////////////////////////////////*/ | ||
|
@@ -191,7 +171,6 @@ contract StakingRewardsV2 is | |
|
||
// define values | ||
rewardsDuration = 1 weeks; | ||
cooldownPeriod = 2 weeks; | ||
} | ||
|
||
/*/////////////////////////////////////////////////////////////// | ||
|
@@ -239,28 +218,30 @@ contract StakingRewardsV2 is | |
///////////////////////////////////////////////////////////////*/ | ||
|
||
/// @inheritdoc IStakingRewardsV2 | ||
function stake(uint256 _amount) external whenNotPaused updateReward(msg.sender) { | ||
function stake(uint256 _amount) external { | ||
_stake(msg.sender, _amount); | ||
|
||
// transfer token to this contract from the caller | ||
kwenta.transferFrom(msg.sender, address(this), _amount); | ||
} | ||
|
||
function _stake(address _account, uint256 _amount) | ||
internal | ||
whenNotPaused | ||
updateReward(_account) | ||
{ | ||
if (_amount == 0) revert AmountZero(); | ||
|
||
// update state | ||
userLastStakeTime[msg.sender] = block.timestamp; | ||
_addTotalSupplyCheckpoint(totalSupply() + _amount); | ||
_addBalancesCheckpoint(msg.sender, balanceOf(msg.sender) + _amount); | ||
|
||
// emit staking event and index msg.sender | ||
emit Staked(msg.sender, _amount); | ||
_addBalancesCheckpoint(_account, balanceOf(_account) + _amount); | ||
|
||
// transfer token to this contract from the caller | ||
kwenta.transferFrom(msg.sender, address(this), _amount); | ||
// emit staking event and index _account | ||
emit Staked(_account, _amount); | ||
} | ||
|
||
/// @inheritdoc IStakingRewardsV2 | ||
function unstake(uint256 _amount) | ||
public | ||
whenNotPaused | ||
updateReward(msg.sender) | ||
afterCooldown(msg.sender) | ||
{ | ||
function unstake(uint256 _amount) public whenNotPaused updateReward(msg.sender) { | ||
if (_amount == 0) revert AmountZero(); | ||
uint256 nonEscrowedBalance = nonEscrowedBalanceOf(msg.sender); | ||
if (_amount > nonEscrowedBalance) revert InsufficientBalance(nonEscrowedBalance); | ||
|
@@ -291,7 +272,6 @@ contract StakingRewardsV2 is | |
if (_amount > unstakedEscrow) revert InsufficientUnstakedEscrow(unstakedEscrow); | ||
|
||
// update state | ||
userLastStakeTime[_account] = block.timestamp; | ||
_addBalancesCheckpoint(_account, balanceOf(_account) + _amount); | ||
_addEscrowedBalancesCheckpoint(_account, escrowedBalanceOf(_account) + _amount); | ||
|
||
|
@@ -304,15 +284,12 @@ contract StakingRewardsV2 is | |
} | ||
|
||
/// @inheritdoc IStakingRewardsV2 | ||
function unstakeEscrow(uint256 _amount) external afterCooldown(msg.sender) { | ||
function unstakeEscrow(uint256 _amount) external { | ||
_unstakeEscrow(msg.sender, _amount); | ||
} | ||
|
||
/// @inheritdoc IStakingRewardsV2 | ||
function unstakeEscrowSkipCooldown(address _account, uint256 _amount) | ||
external | ||
onlyRewardEscrow | ||
{ | ||
function unstakeEscrowAdmin(address _account, uint256 _amount) external onlyRewardEscrow { | ||
_unstakeEscrow(_account, _amount); | ||
} | ||
|
||
|
@@ -361,31 +338,44 @@ contract StakingRewardsV2 is | |
whenNotPaused | ||
updateReward(_account) | ||
{ | ||
uint256 reward = rewards[_account]; | ||
if (reward > 0) { | ||
_processReward(_account, _to, true); | ||
} | ||
|
||
/// @notice Process KWENTA and USDC rewards | ||
/// @dev transferKwenta is set to false when compounding KWENTA rewards | ||
/// @param _account The address of the account to process rewards for | ||
/// @param _to The address to transfer rewards to | ||
/// @param transferKwenta Boolean flag to determine if Kwenta should be transferred | ||
/// @return kwentaReward The amount of Kwenta reward processed | ||
function _processReward(address _account, address _to, bool transferKwenta) | ||
internal | ||
updateReward(_account) | ||
returns (uint256 kwentaReward) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't seem you're using kwentaReward outside of _processReward, but for consistency sake you should either return both kwenta and USDC reward as a tuple or return neither. |
||
{ | ||
// Process Kwenta reward | ||
kwentaReward = rewards[_account]; | ||
if (kwentaReward > 0) { | ||
// update state (first) | ||
rewards[_account] = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if transferKwenta is set to false meaning they want to compound their rewards then doesnt this line ruin that? their rewards are set to 0 but they dont get transferred their kwenta, so next time this is called again (im assuming after they waited for it to compound in this contract), then their rewards will be 0 or close to 0 and therefore they lost their kwenta entirely? if im understanding this correctly then this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh upon further look, kwentaReward is extrapolating this value? and it hopefully gets relogged when calling _stake in _compound? |
||
|
||
// emit reward claimed event and index account | ||
emit RewardPaid(_account, reward); | ||
emit RewardPaid(_account, kwentaReward); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the other event is called RewardPaidUSDC this should be called RewardPaidKwenta to match |
||
|
||
// transfer token from this contract to the rewardEscrow | ||
// and create a vesting entry at the _to address | ||
kwenta.transfer(address(rewardEscrow), reward); | ||
rewardEscrow.appendVestingEntry(_to, reward); | ||
if (transferKwenta) { | ||
kwenta.transfer(_to, kwentaReward); | ||
} | ||
} | ||
|
||
uint256 rewardUSDC = rewardsUSDC[_account] / PRECISION; | ||
if (rewardUSDC > 0) { | ||
// Process USDC reward | ||
uint256 usdcReward = rewardsUSDC[_account] / PRECISION; | ||
if (usdcReward > 0) { | ||
// update state (first) | ||
rewardsUSDC[_account] = 0; | ||
|
||
// emit reward claimed event and index account | ||
emit RewardPaidUSDC(_account, rewardUSDC); | ||
emit RewardPaidUSDC(_account, usdcReward); | ||
|
||
// transfer token from this contract to the account | ||
// as newly issued rewards from inflation are now issued as non-escrowed | ||
usdc.transfer(_to, rewardUSDC); | ||
usdc.transfer(_to, usdcReward); | ||
} | ||
} | ||
|
||
|
@@ -397,8 +387,8 @@ contract StakingRewardsV2 is | |
/// @dev internal helper to compound for a given account | ||
/// @param _account the account to compound for | ||
function _compound(address _account) internal { | ||
_getReward(_account); | ||
_stakeEscrow(_account, unstakedEscrowedBalanceOf(_account)); | ||
uint256 reward = _processReward(_account, _account, false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will reward be 0 here? |
||
_stake(_account, reward); | ||
} | ||
|
||
/*/////////////////////////////////////////////////////////////// | ||
|
@@ -688,17 +678,6 @@ contract StakingRewardsV2 is | |
emit RewardsDurationUpdated(rewardsDuration); | ||
} | ||
|
||
/// @inheritdoc IStakingRewardsV2 | ||
function setCooldownPeriod(uint256 _cooldownPeriod) external onlyOwner { | ||
if (_cooldownPeriod < MIN_COOLDOWN_PERIOD) revert CooldownPeriodTooLow(MIN_COOLDOWN_PERIOD); | ||
if (_cooldownPeriod > MAX_COOLDOWN_PERIOD) { | ||
revert CooldownPeriodTooHigh(MAX_COOLDOWN_PERIOD); | ||
} | ||
|
||
cooldownPeriod = _cooldownPeriod; | ||
emit CooldownPeriodUpdated(cooldownPeriod); | ||
} | ||
|
||
/*/////////////////////////////////////////////////////////////// | ||
PAUSABLE | ||
///////////////////////////////////////////////////////////////*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I would break each author up on separate lines