mirror of
https://github.com/vacp2p/staking-reward-streamer.git
synced 2026-01-09 21:18:01 -05:00
cleanup: remove unused RewardStreamer contract
This was some version of the staking contract for demonstration purposes and is not actually used as we're working on `RewardStreamerMP` version, which is aware of multiplier points. Closes #84
This commit is contained in:
23
.gas-report
23
.gas-report
@@ -22,20 +22,6 @@
|
||||
| run | 2482684 | 2482684 | 2482684 | 2482684 | 3 |
|
||||
|
||||
|
||||
| src/RewardsStreamer.sol:RewardsStreamer contract | | | | | |
|
||||
|--------------------------------------------------|-----------------|--------|--------|--------|---------|
|
||||
| Deployment Cost | Deployment Size | | | | |
|
||||
| 677674 | 3080 | | | | |
|
||||
| Function Name | min | avg | median | max | # calls |
|
||||
| accountedRewards | 350 | 600 | 350 | 2350 | 8 |
|
||||
| getUserInfo | 789 | 789 | 789 | 789 | 12 |
|
||||
| rewardIndex | 349 | 599 | 349 | 2349 | 8 |
|
||||
| stake | 85202 | 100705 | 105102 | 111812 | 3 |
|
||||
| totalStaked | 350 | 350 | 350 | 350 | 8 |
|
||||
| unstake | 110056 | 110062 | 110062 | 110068 | 2 |
|
||||
| updateRewardIndex | 23372 | 45573 | 39574 | 73774 | 3 |
|
||||
|
||||
|
||||
| src/RewardsStreamerMP.sol:RewardsStreamerMP contract | | | | | |
|
||||
|------------------------------------------------------|-----------------|--------|--------|--------|---------|
|
||||
| Deployment Cost | Deployment Size | | | | |
|
||||
@@ -201,12 +187,11 @@
|
||||
| test/mocks/MockToken.sol:MockToken contract | | | | | |
|
||||
|---------------------------------------------|-----------------|-------|--------|-------|---------|
|
||||
| Deployment Cost | Deployment Size | | | | |
|
||||
| 625370 | 3260 | | | | |
|
||||
| 625454 | 3260 | | | | |
|
||||
| Function Name | min | avg | median | max | # calls |
|
||||
| approve | 46330 | 46339 | 46342 | 46342 | 262 |
|
||||
| balanceOf | 558 | 989 | 558 | 2558 | 139 |
|
||||
| mint | 51279 | 56438 | 51279 | 68379 | 275 |
|
||||
| transfer | 34384 | 42934 | 42934 | 51484 | 2 |
|
||||
| approve | 46330 | 46339 | 46342 | 46342 | 257 |
|
||||
| balanceOf | 558 | 926 | 558 | 2558 | 103 |
|
||||
| mint | 51279 | 56407 | 51279 | 68379 | 270 |
|
||||
|
||||
|
||||
| test/mocks/StackOverflowStakeManager.sol:StackOverflowStakeManager contract | | | | | |
|
||||
|
||||
@@ -33,7 +33,6 @@ RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39404)
|
||||
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39340)
|
||||
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39375)
|
||||
RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 618531)
|
||||
RewardsStreamerTest:testStake() (gas: 869181)
|
||||
StakeTest:test_StakeMultipleAccounts() (gas: 499536)
|
||||
StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 505474)
|
||||
StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 842540)
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||
|
||||
contract RewardsStreamer is ReentrancyGuard {
|
||||
error StakingManager__AmountCannotBeZero();
|
||||
error StakingManager__TransferFailed();
|
||||
error StakingManager__InsufficientBalance();
|
||||
|
||||
IERC20 public immutable STAKING_TOKEN;
|
||||
IERC20 public immutable REWARD_TOKEN;
|
||||
|
||||
uint256 public constant SCALE_FACTOR = 1e18;
|
||||
|
||||
uint256 public totalStaked;
|
||||
uint256 public rewardIndex;
|
||||
uint256 public accountedRewards;
|
||||
|
||||
struct UserInfo {
|
||||
uint256 stakedBalance;
|
||||
uint256 userRewardIndex;
|
||||
}
|
||||
|
||||
mapping(address account => UserInfo data) public users;
|
||||
|
||||
constructor(address _stakingToken, address _rewardToken) {
|
||||
STAKING_TOKEN = IERC20(_stakingToken);
|
||||
REWARD_TOKEN = IERC20(_rewardToken);
|
||||
}
|
||||
|
||||
function stake(uint256 amount) external nonReentrant {
|
||||
if (amount == 0) {
|
||||
revert StakingManager__AmountCannotBeZero();
|
||||
}
|
||||
|
||||
updateRewardIndex();
|
||||
|
||||
UserInfo storage user = users[msg.sender];
|
||||
uint256 userRewards = calculateUserRewards(msg.sender);
|
||||
if (userRewards > 0) {
|
||||
distributeRewards(msg.sender, userRewards);
|
||||
}
|
||||
|
||||
bool success = STAKING_TOKEN.transferFrom(msg.sender, address(this), amount);
|
||||
if (!success) {
|
||||
revert StakingManager__TransferFailed();
|
||||
}
|
||||
|
||||
user.stakedBalance += amount;
|
||||
totalStaked += amount;
|
||||
user.userRewardIndex = rewardIndex;
|
||||
}
|
||||
|
||||
function unstake(uint256 amount) external nonReentrant {
|
||||
UserInfo storage user = users[msg.sender];
|
||||
if (amount > user.stakedBalance) {
|
||||
revert StakingManager__InsufficientBalance();
|
||||
}
|
||||
|
||||
updateRewardIndex();
|
||||
|
||||
uint256 userRewards = calculateUserRewards(msg.sender);
|
||||
if (userRewards > 0) {
|
||||
distributeRewards(msg.sender, userRewards);
|
||||
}
|
||||
|
||||
user.stakedBalance -= amount;
|
||||
totalStaked -= amount;
|
||||
|
||||
bool success = STAKING_TOKEN.transfer(msg.sender, amount);
|
||||
if (!success) {
|
||||
revert StakingManager__TransferFailed();
|
||||
}
|
||||
|
||||
user.userRewardIndex = rewardIndex;
|
||||
}
|
||||
|
||||
function updateRewardIndex() public {
|
||||
if (totalStaked == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 rewardBalance = REWARD_TOKEN.balanceOf(address(this));
|
||||
uint256 newRewards = rewardBalance > accountedRewards ? rewardBalance - accountedRewards : 0;
|
||||
|
||||
if (newRewards > 0) {
|
||||
rewardIndex += (newRewards * SCALE_FACTOR) / totalStaked;
|
||||
accountedRewards += newRewards;
|
||||
}
|
||||
}
|
||||
|
||||
function getStakedBalance(address userAddress) public view returns (uint256) {
|
||||
return users[userAddress].stakedBalance;
|
||||
}
|
||||
|
||||
function getPendingRewards(address userAddress) public view returns (uint256) {
|
||||
return calculateUserRewards(userAddress);
|
||||
}
|
||||
|
||||
function calculateUserRewards(address userAddress) public view returns (uint256) {
|
||||
UserInfo storage user = users[userAddress];
|
||||
return (user.stakedBalance * (rewardIndex - user.userRewardIndex)) / SCALE_FACTOR;
|
||||
}
|
||||
|
||||
// send the rewards and updates accountedRewards
|
||||
function distributeRewards(address to, uint256 amount) internal {
|
||||
uint256 rewardBalance = REWARD_TOKEN.balanceOf(address(this));
|
||||
// If amount is higher than the contract's balance (for rounding error), transfer the balance.
|
||||
if (amount > rewardBalance) {
|
||||
amount = rewardBalance;
|
||||
}
|
||||
|
||||
accountedRewards -= amount;
|
||||
|
||||
bool success = REWARD_TOKEN.transfer(to, amount);
|
||||
if (!success) {
|
||||
revert StakingManager__TransferFailed();
|
||||
}
|
||||
}
|
||||
|
||||
function getUserInfo(address userAddress) public view returns (UserInfo memory) {
|
||||
return users[userAddress];
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import { Test } from "forge-std/Test.sol";
|
||||
import { RewardsStreamer } from "../src/RewardsStreamer.sol";
|
||||
import { MockToken } from "./mocks/MockToken.sol";
|
||||
|
||||
contract RewardsStreamerTest is Test {
|
||||
MockToken rewardToken;
|
||||
MockToken stakingToken;
|
||||
RewardsStreamer public streamer;
|
||||
|
||||
address admin = makeAddr("admin");
|
||||
address alice = makeAddr("alice");
|
||||
address bob = makeAddr("bob");
|
||||
address charlie = makeAddr("charlie");
|
||||
address dave = makeAddr("dave");
|
||||
|
||||
function setUp() public {
|
||||
rewardToken = new MockToken("Reward Token", "RT");
|
||||
stakingToken = new MockToken("Staking Token", "ST");
|
||||
streamer = new RewardsStreamer(address(stakingToken), address(rewardToken));
|
||||
|
||||
address[4] memory users = [alice, bob, charlie, dave];
|
||||
for (uint256 i = 0; i < users.length; i++) {
|
||||
stakingToken.mint(users[i], 10_000e18);
|
||||
vm.prank(users[i]);
|
||||
stakingToken.approve(address(streamer), 10_000e18);
|
||||
}
|
||||
|
||||
rewardToken.mint(admin, 10_000e18);
|
||||
vm.prank(admin);
|
||||
rewardToken.approve(address(streamer), 10_000e18);
|
||||
}
|
||||
|
||||
struct CheckStreamerParams {
|
||||
uint256 totalStaked;
|
||||
uint256 stakingBalance;
|
||||
uint256 rewardBalance;
|
||||
uint256 rewardIndex;
|
||||
uint256 accountedRewards;
|
||||
}
|
||||
|
||||
function checkStreamer(CheckStreamerParams memory p) public view {
|
||||
assertEq(streamer.totalStaked(), p.totalStaked);
|
||||
assertEq(stakingToken.balanceOf(address(streamer)), p.stakingBalance);
|
||||
assertEq(rewardToken.balanceOf(address(streamer)), p.rewardBalance);
|
||||
assertEq(streamer.rewardIndex(), p.rewardIndex);
|
||||
assertEq(streamer.accountedRewards(), p.accountedRewards);
|
||||
}
|
||||
|
||||
struct CheckUserParams {
|
||||
address user;
|
||||
uint256 rewardBalance;
|
||||
uint256 stakedBalance;
|
||||
uint256 rewardIndex;
|
||||
}
|
||||
|
||||
function checkUser(CheckUserParams memory p) public view {
|
||||
assertEq(rewardToken.balanceOf(p.user), p.rewardBalance);
|
||||
|
||||
RewardsStreamer.UserInfo memory userInfo = streamer.getUserInfo(p.user);
|
||||
|
||||
assertEq(userInfo.stakedBalance, p.stakedBalance);
|
||||
assertEq(userInfo.userRewardIndex, p.rewardIndex);
|
||||
}
|
||||
|
||||
function testStake() public {
|
||||
streamer.updateRewardIndex();
|
||||
|
||||
// T0
|
||||
checkStreamer(
|
||||
CheckStreamerParams({
|
||||
totalStaked: 0,
|
||||
stakingBalance: 0,
|
||||
rewardBalance: 0,
|
||||
rewardIndex: 0,
|
||||
accountedRewards: 0
|
||||
})
|
||||
);
|
||||
|
||||
// T1
|
||||
vm.prank(alice);
|
||||
streamer.stake(10e18);
|
||||
|
||||
checkStreamer(
|
||||
CheckStreamerParams({
|
||||
totalStaked: 10e18,
|
||||
stakingBalance: 10e18,
|
||||
rewardBalance: 0,
|
||||
rewardIndex: 0,
|
||||
accountedRewards: 0
|
||||
})
|
||||
);
|
||||
|
||||
// T2
|
||||
vm.prank(bob);
|
||||
streamer.stake(30e18);
|
||||
|
||||
checkStreamer(
|
||||
CheckStreamerParams({
|
||||
totalStaked: 40e18,
|
||||
stakingBalance: 40e18,
|
||||
rewardBalance: 0,
|
||||
rewardIndex: 0,
|
||||
accountedRewards: 0
|
||||
})
|
||||
);
|
||||
|
||||
// T3
|
||||
vm.prank(admin);
|
||||
rewardToken.transfer(address(streamer), 1000e18);
|
||||
streamer.updateRewardIndex();
|
||||
|
||||
checkStreamer(
|
||||
CheckStreamerParams({
|
||||
totalStaked: 40e18,
|
||||
stakingBalance: 40e18,
|
||||
rewardBalance: 1000e18,
|
||||
rewardIndex: 25e18,
|
||||
accountedRewards: 1000e18
|
||||
})
|
||||
);
|
||||
|
||||
checkUser(CheckUserParams({ user: alice, rewardBalance: 0, stakedBalance: 10e18, rewardIndex: 0 }));
|
||||
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
|
||||
|
||||
// T4
|
||||
vm.prank(alice);
|
||||
streamer.unstake(10e18);
|
||||
|
||||
checkStreamer(
|
||||
CheckStreamerParams({
|
||||
totalStaked: 30e18,
|
||||
stakingBalance: 30e18,
|
||||
rewardBalance: 750e18,
|
||||
rewardIndex: 25e18,
|
||||
accountedRewards: 750e18
|
||||
})
|
||||
);
|
||||
|
||||
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18 }));
|
||||
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
|
||||
|
||||
// T5
|
||||
vm.prank(charlie);
|
||||
streamer.stake(30e18);
|
||||
|
||||
checkStreamer(
|
||||
CheckStreamerParams({
|
||||
totalStaked: 60e18,
|
||||
stakingBalance: 60e18,
|
||||
rewardBalance: 750e18,
|
||||
rewardIndex: 25e18,
|
||||
accountedRewards: 750e18
|
||||
})
|
||||
);
|
||||
|
||||
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0e18, rewardIndex: 25e18 }));
|
||||
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
|
||||
checkUser(CheckUserParams({ user: charlie, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 25e18 }));
|
||||
|
||||
// T6
|
||||
vm.prank(admin);
|
||||
rewardToken.transfer(address(streamer), 1000e18);
|
||||
streamer.updateRewardIndex();
|
||||
|
||||
checkStreamer(
|
||||
CheckStreamerParams({
|
||||
totalStaked: 60e18,
|
||||
stakingBalance: 60e18,
|
||||
rewardBalance: 1750e18,
|
||||
rewardIndex: 41_666_666_666_666_666_666,
|
||||
accountedRewards: 1750e18
|
||||
})
|
||||
);
|
||||
|
||||
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0, rewardIndex: 25e18 }));
|
||||
checkUser(CheckUserParams({ user: bob, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 0 }));
|
||||
checkUser(CheckUserParams({ user: charlie, rewardBalance: 0, stakedBalance: 30e18, rewardIndex: 25e18 }));
|
||||
|
||||
//T7
|
||||
vm.prank(bob);
|
||||
streamer.unstake(30e18);
|
||||
|
||||
checkStreamer(
|
||||
CheckStreamerParams({
|
||||
totalStaked: 30e18,
|
||||
stakingBalance: 30e18,
|
||||
rewardBalance: 500e18 + 20, // 500e18 (with rounding error of 20 wei)
|
||||
rewardIndex: 41_666_666_666_666_666_666,
|
||||
accountedRewards: 500e18 + 20
|
||||
})
|
||||
);
|
||||
|
||||
checkUser(CheckUserParams({ user: alice, rewardBalance: 250e18, stakedBalance: 0, rewardIndex: 25e18 }));
|
||||
checkUser(
|
||||
CheckUserParams({
|
||||
user: bob,
|
||||
rewardBalance: 1_249_999_999_999_999_999_980, // 750e18 + 500e18 (with rounding error)
|
||||
stakedBalance: 0,
|
||||
rewardIndex: 41_666_666_666_666_666_666
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user