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

Draft Abstract Staking logic #83

Merged
merged 13 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions contracts/mock/staking/AbstractStakingMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

import {AbstractStaking} from "../../staking/AbstractStaking.sol";

contract AbstractStakingMock is AbstractStaking, Multicall {
function __AbstractStakingMock_init(
address sharesToken_,
address rewardsToken_,
uint256 rate_,
uint256 stakingStartTime_
) external initializer {
__AbstractStaking_init(sharesToken_, rewardsToken_, rate_, stakingStartTime_);
}

function mockInit(
address sharesToken_,
address rewardsToken_,
uint256 rate_,
uint256 stakingStartTime_
) external {
__AbstractStaking_init(sharesToken_, rewardsToken_, rate_, stakingStartTime_);
}

function setStakingStartTime(uint256 stakingStartTime_) external {
_setStakingStartTime(stakingStartTime_);
}

function setRate(uint256 newRate_) external {
_setRate(newRate_);
}

function userShares(address user_) external view returns (uint256) {
return userDistribution(user_).shares;
}

function userOwedValue(address user_) external view returns (uint256) {
return userDistribution(user_).owedValue;
}
}

contract StakersFactory is Multicall {
Staker[] public stakers;

function createStaker() public {
Staker staker_ = new Staker();
stakers.push(staker_);
}

function stake(
address stakingContract_,
address staker_,
address token_,
uint256 amount_
) external {
Staker(staker_).stake(stakingContract_, token_, amount_);
}

function unstake(address stakingContract_, address staker_, uint256 amount_) external {
Staker(staker_).unstake(stakingContract_, amount_);
}
}

contract Staker {
function stake(address stakingContract_, address token_, uint256 amount_) external {
IERC20(token_).approve(stakingContract_, amount_);
AbstractStakingMock(stakingContract_).stake(amount_);
}

function unstake(address stakingContract_, uint256 amount_) external {
AbstractStakingMock(stakingContract_).unstake(amount_);
}
}
36 changes: 36 additions & 0 deletions contracts/mock/staking/AbstractValueDistributorMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";

import {AbstractValueDistributor} from "../../staking/AbstractValueDistributor.sol";
import {DECIMAL} from "../../utils/Globals.sol";

contract AbstractValueDistributorMock is AbstractValueDistributor, Multicall {
function addShares(address user_, uint256 amount_) external {
_addShares(user_, amount_);
}

function removeShares(address user_, uint256 amount_) external {
_removeShares(user_, amount_);
}

function distributeValue(address user_, uint256 amount_) external {
_distributeValue(user_, amount_);
}

function userShares(address user_) external view returns (uint256) {
return userDistribution(user_).shares;
}

function userOwedValue(address user_) external view returns (uint256) {
return userDistribution(user_).owedValue;
}

function _getValueToDistribute(
uint256 timeUpTo_,
uint256 timeLastUpdate_
) internal view virtual override returns (uint256) {
return DECIMAL * (timeUpTo_ - timeLastUpdate_);
}
}
2 changes: 1 addition & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solarity/solidity-lib",
"version": "2.6.13",
"version": "2.6.14",
"license": "MIT",
"author": "Distributed Lab",
"readme": "README.md",
Expand Down
204 changes: 204 additions & 0 deletions contracts/staking/AbstractStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

import {AbstractValueDistributor} from "./AbstractValueDistributor.sol";

/**
* @notice The AbstractStaking module
*
* Contract module for staking tokens and earning rewards based on shares.
*/
abstract contract AbstractStaking is AbstractValueDistributor, Initializable {
using SafeERC20 for IERC20;

address private _sharesToken;
address private _rewardsToken;

/**
* @dev The rate of rewards distribution per second.
*
* It determines the rate at which rewards are earned and distributed
* to stakers based on their shares.
*
* Note: Ensure that the `_rate` value is set correctly to match
* the decimal precision of the `_rewardsToken` to ensure accurate rewards distribution.
*/
uint256 private _rate;

uint256 private _stakingStartTime;

/**
* @dev Throws if the staking has not started yet.
*/
modifier stakingStarted() {
_checkStakingStarted();
_;
}

/**
* @notice Initializes the contract setting the values provided as shares token, rewards token, reward rate and staking start time.
*
* Warning: when shares and rewards tokens are the same, users may accidentally withdraw
* other users' shares as a reward if the rewards token balance is improperly handled.
*
* @param sharesToken_ The address of the shares token.
* @param rewardsToken_ The address of the rewards token.
* @param rate_ The reward rate.
* @param stakingStartTime_ The staking start time
*/
function __AbstractStaking_init(
address sharesToken_,
address rewardsToken_,
uint256 rate_,
uint256 stakingStartTime_
) internal onlyInitializing {
require(sharesToken_ != address(0), "Staking: zero address cannot be the Shares Token");
require(rewardsToken_ != address(0), "Staking: zero address cannot be the Rewards Token");
mllwchrry marked this conversation as resolved.
Show resolved Hide resolved

_sharesToken = sharesToken_;
_rewardsToken = rewardsToken_;
_setRate(rate_);
_setStakingStartTime(stakingStartTime_);
}

/**
* @notice Stakes the specified amount of tokens.
* @param amount_ The amount of tokens to stake.
*/
function stake(uint256 amount_) public stakingStarted {
_addShares(msg.sender, amount_);
}

/**
* @notice Unstakes the specified amount of tokens.
* @param amount_ The amount of tokens to unstake.
*/
function unstake(uint256 amount_) public stakingStarted {
_removeShares(msg.sender, amount_);
}

/**
* @notice Claims the specified amount of rewards.
* @param amount_ The amount of rewards to claim.
*/
function claim(uint256 amount_) public stakingStarted {
_distributeValue(msg.sender, amount_);
}

/**
* @notice Withdraws all the staked tokens together with rewards.
*
* Note: All the rewards are claimed after the shares are removed.
*
* @return shares_ The amount of shares being withdrawn.
* @return owedValue_ The total value of the rewards owed to the user.
*/
function withdraw() public stakingStarted returns (uint256 shares_, uint256 owedValue_) {
shares_ = userDistribution(msg.sender).shares;
owedValue_ = getOwedValue(msg.sender);

unstake(shares_);
claim(owedValue_);
}

/**
* @notice Returns the shares token.
* @return The address of the shares token contract.
*/
function sharesToken() public view returns (address) {
return _sharesToken;
}

/**
* @notice Returns the rewards token.
* @return The address of the rewards token contract.
*/
function rewardsToken() public view returns (address) {
return _rewardsToken;
}

/**
* @notice Returns the staking start time.
* @return The timestamp when staking starts.
*/
function stakingStartTime() public view returns (uint256) {
return _stakingStartTime;
}

/**
* @notice Returns the rate of rewards distribution.
* @return The rate of rewards distribution per second.
*/
function rate() public view returns (uint256) {
return _rate;
}

/**
* @notice Sets the staking start time.
* @param stakingStartTime_ The timestamp when staking will start.
*/
function _setStakingStartTime(uint256 stakingStartTime_) internal {
_stakingStartTime = stakingStartTime_;
}

/**
* @notice Sets the rate of rewards distribution per second.
* @param newRate_ The new rate of rewards distribution.
*/
function _setRate(uint256 newRate_) internal {
_update(address(0));

_rate = newRate_;
}

/**
* @notice Hook function that is called after shares have been added to a user's distribution.
* @param user_ The address of the user.
* @param amount_ The amount of shares added.
*/
function _afterAddShares(address user_, uint256 amount_) internal virtual override {
IERC20(_sharesToken).safeTransferFrom(user_, address(this), amount_);
}

/**
* @notice Hook function that is called after shares have been removed from a user's distribution.
* @param user_ The address of the user.
* @param amount_ The amount of shares removed.
*/
function _afterRemoveShares(address user_, uint256 amount_) internal virtual override {
IERC20(_sharesToken).safeTransfer(user_, amount_);
}

/**
* @notice Hook function that is called after value has been distributed to a user.
* @param user_ The address of the user.
* @param amount_ The amount of value distributed.
*/
function _afterDistributeValue(address user_, uint256 amount_) internal virtual override {
IERC20(_rewardsToken).safeTransfer(user_, amount_);
}

/**
* @dev Throws if the staking has not started yet.
*/
function _checkStakingStarted() internal view {
require(block.timestamp >= _stakingStartTime, "Staking: staking has not started yet");
}

/**
* @notice Gets the value to be distributed for a given time period.
* @param timeUpTo_ The end timestamp of the period.
* @param timeLastUpdate_ The start timestamp of the period.
* @return The value to be distributed for the period.
*/
function _getValueToDistribute(
uint256 timeUpTo_,
uint256 timeLastUpdate_
) internal view virtual override returns (uint256) {
return _rate * (timeUpTo_ - timeLastUpdate_);
}
}
Loading