-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Draft Abstract Staking logic * draft implementation * Modified ValueDistributor and Staking contracts, added natspec comments * Refactored ValueDistributor and Staking contracts. Fixed some logic * Modified ValueDistributor and Staking contracts * Implemented tests for AbstractStaking and ValueDistributor contracts * Refactored AbstractStaking and ValueDistributor contracts, added more tests * Refactored AbstractStaking and ValueDistributor contracts * Refactored tests and moved events to the AbstractValueDistributor contract * Added 2 tests for AbstractStaking to reach the full coverage * Updated the patch version * Refactored AbstractStaking, AbstractValueDistributor and AbstractValueDistributorMock contracts * Refactored AbstractStaking and AbstractValueDistributor contracts --------- Co-authored-by: Artem Chystiakov <[email protected]>
- Loading branch information
Showing
9 changed files
with
1,548 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
|
||
_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_); | ||
} | ||
} |
Oops, something went wrong.