Skip to content

Commit

Permalink
chore: introduce IStakeManager interface
Browse files Browse the repository at this point in the history
This introduces a first version of `IStakeManager`, highly inspired by
the changes done in
#39.

However, in this commit, it only adds the methods that are currently
supported by both, `StakeManager` and `RewardStreamerMP`.

Future commits will add APIs for locking and leaving.
  • Loading branch information
0x-r4bbit committed Oct 17, 2024
1 parent 97ebd08 commit 714c884
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 68 deletions.
24 changes: 12 additions & 12 deletions .gas-report
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,29 @@
| Deployment Cost | Deployment Size | | | | |
| 1010922 | 4521 | | | | |
| Function Name | min | avg | median | max | # calls |
| MAX_LOCKING_PERIOD | 273 | 273 | 273 | 273 | 22 |
| MAX_MULTIPLIER | 252 | 252 | 252 | 252 | 28 |
| MIN_LOCKING_PERIOD | 273 | 273 | 273 | 273 | 11 |
| MAX_LOCKUP_PERIOD | 273 | 273 | 273 | 273 | 22 |
| MAX_MULTIPLIER | 274 | 274 | 274 | 274 | 28 |
| MIN_LOCKUP_PERIOD | 253 | 253 | 253 | 253 | 11 |
| MP_RATE_PER_YEAR | 231 | 231 | 231 | 231 | 3 |
| SCALE_FACTOR | 229 | 229 | 229 | 229 | 39 |
| STAKING_TOKEN | 273 | 273 | 273 | 273 | 128 |
| accountedRewards | 351 | 909 | 351 | 2351 | 68 |
| STAKE_TOKEN | 250 | 250 | 250 | 250 | 128 |
| accountedRewards | 373 | 931 | 373 | 2373 | 68 |
| getAccount | 1574 | 1574 | 1574 | 1574 | 65 |
| rewardIndex | 351 | 380 | 351 | 2351 | 68 |
| totalMP | 374 | 374 | 374 | 374 | 71 |
| totalMaxMP | 351 | 351 | 351 | 351 | 71 |
| totalStaked | 330 | 330 | 330 | 330 | 71 |
| updateAccountMP | 34610 | 36848 | 37112 | 37112 | 19 |
| totalMP | 330 | 330 | 330 | 330 | 71 |
| totalMaxMP | 373 | 373 | 373 | 373 | 71 |
| totalStaked | 352 | 352 | 352 | 352 | 71 |
| updateAccountMP | 34632 | 36870 | 37134 | 37134 | 19 |
| updateGlobalState | 29986 | 55567 | 47365 | 80313 | 25 |


| src/StakeVault.sol:StakeVault contract | | | | | |
|----------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost | Deployment Size | | | | |
| 857122 | 4070 | | | | |
| 857099 | 4070 | | | | |
| Function Name | min | avg | median | max | # calls |
| stake | 194703 | 231763 | 238396 | 258880 | 46 |
| unstake | 81435 | 109751 | 98993 | 141658 | 13 |
| stake | 194660 | 231720 | 238353 | 258837 | 46 |
| unstake | 81452 | 109772 | 99015 | 141680 | 13 |


| src/XPNFTToken.sol:XPNFTToken contract | | | | | |
Expand Down
64 changes: 32 additions & 32 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
IntegrationTest:testStakeFoo() (gas: 1459038)
IntegrationTest:testStakeFoo() (gas: 1459151)
NFTMetadataGeneratorSVGTest:testGenerateMetadata() (gas: 92874)
NFTMetadataGeneratorSVGTest:testSetImageStrings() (gas: 60081)
NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35818)
NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 109345)
NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 50653)
NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 35993)
RewardsStreamerTest:testStake() (gas: 869874)
StakeTest:test_StakeMultipleAccounts() (gas: 484237)
StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 629617)
StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 796183)
StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 490118)
StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 511520)
StakeTest:test_StakeOneAccount() (gas: 279766)
StakeTest:test_StakeOneAccountAndRewards() (gas: 425142)
StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 485820)
StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 480886)
StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 293512)
StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 293543)
StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 293654)
UnstakeTest:test_StakeMultipleAccounts() (gas: 484259)
UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 629594)
UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 796205)
UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 490095)
UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 511542)
UnstakeTest:test_StakeOneAccount() (gas: 279789)
UnstakeTest:test_StakeOneAccountAndRewards() (gas: 425164)
UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 485842)
UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 480866)
UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 293557)
UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 293543)
UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 293632)
UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 495149)
UnstakeTest:test_UnstakeMultipleAccounts() (gas: 672560)
UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 991506)
UnstakeTest:test_UnstakeOneAccount() (gas: 468535)
UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 483413)
UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 574857)
UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 504995)
StakeTest:test_StakeMultipleAccounts() (gas: 484173)
StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 629575)
StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 796295)
StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 490078)
StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 511480)
StakeTest:test_StakeOneAccount() (gas: 279745)
StakeTest:test_StakeOneAccountAndRewards() (gas: 425143)
StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 485909)
StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 480997)
StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 293513)
StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 293524)
StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 293635)
UnstakeTest:test_StakeMultipleAccounts() (gas: 484195)
UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 629552)
UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 796317)
UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 490055)
UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 511502)
UnstakeTest:test_StakeOneAccount() (gas: 279768)
UnstakeTest:test_StakeOneAccountAndRewards() (gas: 425165)
UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 485931)
UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 480977)
UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 293558)
UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 293524)
UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 293613)
UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 495152)
UnstakeTest:test_UnstakeMultipleAccounts() (gas: 672552)
UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 991588)
UnstakeTest:test_UnstakeOneAccount() (gas: 468592)
UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 483480)
UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 574902)
UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 505088)
XPNFTTokenTest:testApproveNotAllowed() (gas: 10507)
XPNFTTokenTest:testGetApproved() (gas: 10531)
XPNFTTokenTest:testIsApprovedForAll() (gas: 10705)
Expand Down
2 changes: 1 addition & 1 deletion certora/confs/RewardsStreamerMP.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"certora/helpers/ERC20A.sol"
],
"link" : [
"RewardsStreamerMP:STAKING_TOKEN=ERC20A",
"RewardsStreamerMP:STAKE_TOKEN=ERC20A",
"RewardsStreamerMP:REWARD_TOKEN=ERC20A"
],
"msg": "Verifying RewardsStreamerMP.sol",
Expand Down
15 changes: 8 additions & 7 deletions src/RewardsStreamerMP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ pragma solidity ^0.8.26;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IStakeManager } from "./interfaces/IStakeManager.sol";

// Rewards Streamer with Multiplier Points
contract RewardsStreamerMP is ReentrancyGuard {
contract RewardsStreamerMP is ReentrancyGuard, IStakeManager {
error StakingManager__AmountCannotBeZero();
error StakingManager__TransferFailed();
error StakingManager__InsufficientBalance();
error StakingManager__InvalidLockingPeriod();
error StakingManager__CannotRestakeWithLockedFunds();
error StakingManager__TokensAreLocked();

IERC20 public immutable STAKING_TOKEN;
IERC20 public immutable STAKE_TOKEN;
IERC20 public immutable REWARD_TOKEN;

uint256 public constant SCALE_FACTOR = 1e18;
uint256 public constant MP_RATE_PER_YEAR = 1e18;

uint256 public constant MIN_LOCKING_PERIOD = 90 days;
uint256 public constant MAX_LOCKING_PERIOD = 4 * 365 days;
uint256 public constant MIN_LOCKUP_PERIOD = 90 days;
uint256 public constant MAX_LOCKUP_PERIOD = 4 * 365 days;
uint256 public constant MAX_MULTIPLIER = 4;

uint256 public totalStaked;
Expand All @@ -42,7 +43,7 @@ contract RewardsStreamerMP is ReentrancyGuard {
mapping(address account => Account data) public accounts;

constructor(address _stakingToken, address _rewardToken) {
STAKING_TOKEN = IERC20(_stakingToken);
STAKE_TOKEN = IERC20(_stakingToken);
REWARD_TOKEN = IERC20(_rewardToken);
lastMPUpdatedTime = block.timestamp;
}
Expand All @@ -52,7 +53,7 @@ contract RewardsStreamerMP is ReentrancyGuard {
revert StakingManager__AmountCannotBeZero();
}

if (lockPeriod != 0 && (lockPeriod < MIN_LOCKING_PERIOD || lockPeriod > MAX_LOCKING_PERIOD)) {
if (lockPeriod != 0 && (lockPeriod < MIN_LOCKUP_PERIOD || lockPeriod > MAX_LOCKUP_PERIOD)) {
revert StakingManager__InvalidLockingPeriod();
}

Expand All @@ -77,7 +78,7 @@ contract RewardsStreamerMP is ReentrancyGuard {
uint256 bonusMP = 0;

if (lockPeriod != 0) {
uint256 lockMultiplier = (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR) / MAX_LOCKING_PERIOD;
uint256 lockMultiplier = (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR) / MAX_LOCKUP_PERIOD;
bonusMP = amount * lockMultiplier / SCALE_FACTOR;
account.lockUntil = block.timestamp + lockPeriod;
} else {
Expand Down
8 changes: 4 additions & 4 deletions src/StakeVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.26;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { RewardsStreamerMP } from "./RewardsStreamerMP.sol";
import { IStakeManager } from "./interfaces/IStakeManager.sol";

/**
* @title StakeVault
Expand All @@ -23,7 +23,7 @@ contract StakeVault is Ownable {
//if is needed that STAKE_TOKEN to be a variable, RewardStreamerMP should be changed to check codehash and
//StakeVault(msg.sender).STAKE_TOKEN()
IERC20 public immutable STAKE_TOKEN;
RewardsStreamerMP private stakeManager;
IStakeManager private stakeManager;

/**
* @dev Emitted when tokens are staked.
Expand All @@ -46,8 +46,8 @@ contract StakeVault is Ownable {
* @param _owner The address of the owner.
* @param _stakeManager The address of the RewardStreamerMP contract.
*/
constructor(address _owner, RewardsStreamerMP _stakeManager) Ownable(_owner) {
STAKE_TOKEN = _stakeManager.STAKING_TOKEN();
constructor(address _owner, IStakeManager _stakeManager) Ownable(_owner) {
STAKE_TOKEN = _stakeManager.STAKE_TOKEN();
stakeManager = _stakeManager;
}

Expand Down
26 changes: 26 additions & 0 deletions src/interfaces/IStakeManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IStakeManager {
error StakeManager__FundsLocked();
error StakeManager__InvalidLockTime();
error StakeManager__InsufficientFunds();
error StakeManager__StakeIsTooLow();

function stake(uint256 _amount, uint256 _seconds) external;
function unstake(uint256 _amount) external;

function totalStaked() external view returns (uint256);
function totalMP() external view returns (uint256);
function totalMaxMP() external view returns (uint256);
function getStakedBalance(address _vault) external view returns (uint256 _balance);

function STAKE_TOKEN() external view returns (IERC20);
function REWARD_TOKEN() external view returns (IERC20);
function MIN_LOCKUP_PERIOD() external view returns (uint256);
function MAX_LOCKUP_PERIOD() external view returns (uint256);
function MP_RATE_PER_YEAR() external view returns (uint256);
function MAX_MULTIPLIER() external view returns (uint256);
}
24 changes: 12 additions & 12 deletions test/RewardsStreamerMP.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ contract RewardsStreamerMPTest is Test {

function _calculateBonusMP(uint256 amount, uint256 lockupTime) public view returns (uint256) {
return amount
* (lockupTime * streamer.MAX_MULTIPLIER() * streamer.SCALE_FACTOR() / streamer.MAX_LOCKING_PERIOD())
* (lockupTime * streamer.MAX_MULTIPLIER() * streamer.SCALE_FACTOR() / streamer.MAX_LOCKUP_PERIOD())
/ streamer.SCALE_FACTOR();
}

Expand Down Expand Up @@ -535,15 +535,15 @@ contract StakeTest is RewardsStreamerMPTest {

function test_StakeOneAccountWithMinLockUp() public {
uint256 stakeAmount = 10e18;
uint256 lockUpPeriod = streamer.MIN_LOCKING_PERIOD();
uint256 lockUpPeriod = streamer.MIN_LOCKUP_PERIOD();
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockUpPeriod);

_stake(alice, stakeAmount, lockUpPeriod);

checkStreamer(
CheckStreamerParams({
totalStaked: stakeAmount,
// 10e18 + (amount * (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR / MAX_LOCKING_PERIOD) / SCALE_FACTOR)
// 10e18 + (amount * (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR / MAX_LOCKUP_PERIOD) / SCALE_FACTOR)
totalMP: stakeAmount + expectedBonusMP,
totalMaxMP: 52_465_753_424_657_534_240,
stakingBalance: stakeAmount,
Expand All @@ -556,15 +556,15 @@ contract StakeTest is RewardsStreamerMPTest {

function test_StakeOneAccountWithMaxLockUp() public {
uint256 stakeAmount = 10e18;
uint256 lockUpPeriod = streamer.MAX_LOCKING_PERIOD();
uint256 lockUpPeriod = streamer.MAX_LOCKUP_PERIOD();
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockUpPeriod);

_stake(alice, stakeAmount, lockUpPeriod);

checkStreamer(
CheckStreamerParams({
totalStaked: stakeAmount,
// 10 + (amount * (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR / MAX_LOCKING_PERIOD) / SCALE_FACTOR)
// 10 + (amount * (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR / MAX_LOCKUP_PERIOD) / SCALE_FACTOR)
totalMP: stakeAmount + expectedBonusMP,
totalMaxMP: 90e18,
stakingBalance: stakeAmount,
Expand All @@ -577,15 +577,15 @@ contract StakeTest is RewardsStreamerMPTest {

function test_StakeOneAccountWithRandomLockUp() public {
uint256 stakeAmount = 10e18;
uint256 lockUpPeriod = streamer.MIN_LOCKING_PERIOD() + 13 days;
uint256 lockUpPeriod = streamer.MIN_LOCKUP_PERIOD() + 13 days;
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockUpPeriod);

_stake(alice, stakeAmount, lockUpPeriod);

checkStreamer(
CheckStreamerParams({
totalStaked: stakeAmount,
// 10 + (amount * (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR / MAX_LOCKING_PERIOD) / SCALE_FACTOR)
// 10 + (amount * (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR / MAX_LOCKUP_PERIOD) / SCALE_FACTOR)
totalMP: stakeAmount + expectedBonusMP,
totalMaxMP: 52_821_917_808_219_178_080,
stakingBalance: stakeAmount,
Expand Down Expand Up @@ -860,7 +860,7 @@ contract StakeTest is RewardsStreamerMPTest {

function test_StakeMultipleAccountsWithMinLockUp() public {
uint256 aliceStakeAmount = 10e18;
uint256 aliceLockUpPeriod = streamer.MIN_LOCKING_PERIOD();
uint256 aliceLockUpPeriod = streamer.MIN_LOCKUP_PERIOD();
uint256 aliceExpectedBonusMP = _calculateBonusMP(aliceStakeAmount, aliceLockUpPeriod);

uint256 bobStakeAmount = 30e18;
Expand Down Expand Up @@ -891,11 +891,11 @@ contract StakeTest is RewardsStreamerMPTest {

function test_StakeMultipleAccountsWithRandomLockUp() public {
uint256 aliceStakeAmount = 10e18;
uint256 aliceLockUpPeriod = streamer.MAX_LOCKING_PERIOD() - 21 days;
uint256 aliceLockUpPeriod = streamer.MAX_LOCKUP_PERIOD() - 21 days;
uint256 aliceExpectedBonusMP = _calculateBonusMP(aliceStakeAmount, aliceLockUpPeriod);

uint256 bobStakeAmount = 30e18;
uint256 bobLockUpPeriod = streamer.MIN_LOCKING_PERIOD() + 43 days;
uint256 bobLockUpPeriod = streamer.MIN_LOCKUP_PERIOD() + 43 days;
uint256 bobExpectedBonusMP = _calculateBonusMP(bobStakeAmount, bobLockUpPeriod);

// alice stakes with lockup period
Expand Down Expand Up @@ -1157,7 +1157,7 @@ contract UnstakeTest is StakeTest {
test_StakeOneAccountWithMinLockUp();

uint256 stakeAmount = 10e18;
uint256 lockUpPeriod = streamer.MIN_LOCKING_PERIOD();
uint256 lockUpPeriod = streamer.MIN_LOCKUP_PERIOD();
// 10e18 is what's used in `test_StakeOneAccountWithMinLockUp`
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockUpPeriod);

Expand Down Expand Up @@ -1230,7 +1230,7 @@ contract UnstakeTest is StakeTest {
function test_UnstakeBonusMPAndAccuredMP() public {
// setup variables
uint256 amountStaked = 10e18;
uint256 secondsLocked = streamer.MIN_LOCKING_PERIOD();
uint256 secondsLocked = streamer.MIN_LOCKUP_PERIOD();
uint256 reducedStake = 5e18;
uint256 increasedTime = 365 days;

Expand Down

0 comments on commit 714c884

Please sign in to comment.