Skip to content

Commit c76706a

Browse files
mllwchrryArvolear
andauthored
Draft Abstract Staking logic (#83)
* 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 <artem.ch31@gmail.com>
1 parent 5f7b2b6 commit c76706a

File tree

9 files changed

+1548
-4
lines changed

9 files changed

+1548
-4
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";
5+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6+
7+
import {AbstractStaking} from "../../staking/AbstractStaking.sol";
8+
9+
contract AbstractStakingMock is AbstractStaking, Multicall {
10+
function __AbstractStakingMock_init(
11+
address sharesToken_,
12+
address rewardsToken_,
13+
uint256 rate_,
14+
uint256 stakingStartTime_
15+
) external initializer {
16+
__AbstractStaking_init(sharesToken_, rewardsToken_, rate_, stakingStartTime_);
17+
}
18+
19+
function mockInit(
20+
address sharesToken_,
21+
address rewardsToken_,
22+
uint256 rate_,
23+
uint256 stakingStartTime_
24+
) external {
25+
__AbstractStaking_init(sharesToken_, rewardsToken_, rate_, stakingStartTime_);
26+
}
27+
28+
function setStakingStartTime(uint256 stakingStartTime_) external {
29+
_setStakingStartTime(stakingStartTime_);
30+
}
31+
32+
function setRate(uint256 newRate_) external {
33+
_setRate(newRate_);
34+
}
35+
36+
function userShares(address user_) external view returns (uint256) {
37+
return userDistribution(user_).shares;
38+
}
39+
40+
function userOwedValue(address user_) external view returns (uint256) {
41+
return userDistribution(user_).owedValue;
42+
}
43+
}
44+
45+
contract StakersFactory is Multicall {
46+
Staker[] public stakers;
47+
48+
function createStaker() public {
49+
Staker staker_ = new Staker();
50+
stakers.push(staker_);
51+
}
52+
53+
function stake(
54+
address stakingContract_,
55+
address staker_,
56+
address token_,
57+
uint256 amount_
58+
) external {
59+
Staker(staker_).stake(stakingContract_, token_, amount_);
60+
}
61+
62+
function unstake(address stakingContract_, address staker_, uint256 amount_) external {
63+
Staker(staker_).unstake(stakingContract_, amount_);
64+
}
65+
}
66+
67+
contract Staker {
68+
function stake(address stakingContract_, address token_, uint256 amount_) external {
69+
IERC20(token_).approve(stakingContract_, amount_);
70+
AbstractStakingMock(stakingContract_).stake(amount_);
71+
}
72+
73+
function unstake(address stakingContract_, uint256 amount_) external {
74+
AbstractStakingMock(stakingContract_).unstake(amount_);
75+
}
76+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";
5+
6+
import {AbstractValueDistributor} from "../../staking/AbstractValueDistributor.sol";
7+
import {DECIMAL} from "../../utils/Globals.sol";
8+
9+
contract AbstractValueDistributorMock is AbstractValueDistributor, Multicall {
10+
function addShares(address user_, uint256 amount_) external {
11+
_addShares(user_, amount_);
12+
}
13+
14+
function removeShares(address user_, uint256 amount_) external {
15+
_removeShares(user_, amount_);
16+
}
17+
18+
function distributeValue(address user_, uint256 amount_) external {
19+
_distributeValue(user_, amount_);
20+
}
21+
22+
function userShares(address user_) external view returns (uint256) {
23+
return userDistribution(user_).shares;
24+
}
25+
26+
function userOwedValue(address user_) external view returns (uint256) {
27+
return userDistribution(user_).owedValue;
28+
}
29+
30+
function _getValueToDistribute(
31+
uint256 timeUpTo_,
32+
uint256 timeLastUpdate_
33+
) internal view virtual override returns (uint256) {
34+
return DECIMAL * (timeUpTo_ - timeLastUpdate_);
35+
}
36+
}

contracts/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@solarity/solidity-lib",
3-
"version": "2.6.13",
3+
"version": "2.6.14",
44
"license": "MIT",
55
"author": "Distributed Lab",
66
"readme": "README.md",
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6+
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
7+
8+
import {AbstractValueDistributor} from "./AbstractValueDistributor.sol";
9+
10+
/**
11+
* @notice The AbstractStaking module
12+
*
13+
* Contract module for staking tokens and earning rewards based on shares.
14+
*/
15+
abstract contract AbstractStaking is AbstractValueDistributor, Initializable {
16+
using SafeERC20 for IERC20;
17+
18+
address private _sharesToken;
19+
address private _rewardsToken;
20+
21+
/**
22+
* @dev The rate of rewards distribution per second.
23+
*
24+
* It determines the rate at which rewards are earned and distributed
25+
* to stakers based on their shares.
26+
*
27+
* Note: Ensure that the `_rate` value is set correctly to match
28+
* the decimal precision of the `_rewardsToken` to ensure accurate rewards distribution.
29+
*/
30+
uint256 private _rate;
31+
32+
uint256 private _stakingStartTime;
33+
34+
/**
35+
* @dev Throws if the staking has not started yet.
36+
*/
37+
modifier stakingStarted() {
38+
_checkStakingStarted();
39+
_;
40+
}
41+
42+
/**
43+
* @notice Initializes the contract setting the values provided as shares token, rewards token, reward rate and staking start time.
44+
*
45+
* Warning: when shares and rewards tokens are the same, users may accidentally withdraw
46+
* other users' shares as a reward if the rewards token balance is improperly handled.
47+
*
48+
* @param sharesToken_ The address of the shares token.
49+
* @param rewardsToken_ The address of the rewards token.
50+
* @param rate_ The reward rate.
51+
* @param stakingStartTime_ The staking start time
52+
*/
53+
function __AbstractStaking_init(
54+
address sharesToken_,
55+
address rewardsToken_,
56+
uint256 rate_,
57+
uint256 stakingStartTime_
58+
) internal onlyInitializing {
59+
require(sharesToken_ != address(0), "Staking: zero address cannot be the Shares Token");
60+
require(rewardsToken_ != address(0), "Staking: zero address cannot be the Rewards Token");
61+
62+
_sharesToken = sharesToken_;
63+
_rewardsToken = rewardsToken_;
64+
_setRate(rate_);
65+
_setStakingStartTime(stakingStartTime_);
66+
}
67+
68+
/**
69+
* @notice Stakes the specified amount of tokens.
70+
* @param amount_ The amount of tokens to stake.
71+
*/
72+
function stake(uint256 amount_) public stakingStarted {
73+
_addShares(msg.sender, amount_);
74+
}
75+
76+
/**
77+
* @notice Unstakes the specified amount of tokens.
78+
* @param amount_ The amount of tokens to unstake.
79+
*/
80+
function unstake(uint256 amount_) public stakingStarted {
81+
_removeShares(msg.sender, amount_);
82+
}
83+
84+
/**
85+
* @notice Claims the specified amount of rewards.
86+
* @param amount_ The amount of rewards to claim.
87+
*/
88+
function claim(uint256 amount_) public stakingStarted {
89+
_distributeValue(msg.sender, amount_);
90+
}
91+
92+
/**
93+
* @notice Withdraws all the staked tokens together with rewards.
94+
*
95+
* Note: All the rewards are claimed after the shares are removed.
96+
*
97+
* @return shares_ The amount of shares being withdrawn.
98+
* @return owedValue_ The total value of the rewards owed to the user.
99+
*/
100+
function withdraw() public stakingStarted returns (uint256 shares_, uint256 owedValue_) {
101+
shares_ = userDistribution(msg.sender).shares;
102+
owedValue_ = getOwedValue(msg.sender);
103+
104+
unstake(shares_);
105+
claim(owedValue_);
106+
}
107+
108+
/**
109+
* @notice Returns the shares token.
110+
* @return The address of the shares token contract.
111+
*/
112+
function sharesToken() public view returns (address) {
113+
return _sharesToken;
114+
}
115+
116+
/**
117+
* @notice Returns the rewards token.
118+
* @return The address of the rewards token contract.
119+
*/
120+
function rewardsToken() public view returns (address) {
121+
return _rewardsToken;
122+
}
123+
124+
/**
125+
* @notice Returns the staking start time.
126+
* @return The timestamp when staking starts.
127+
*/
128+
function stakingStartTime() public view returns (uint256) {
129+
return _stakingStartTime;
130+
}
131+
132+
/**
133+
* @notice Returns the rate of rewards distribution.
134+
* @return The rate of rewards distribution per second.
135+
*/
136+
function rate() public view returns (uint256) {
137+
return _rate;
138+
}
139+
140+
/**
141+
* @notice Sets the staking start time.
142+
* @param stakingStartTime_ The timestamp when staking will start.
143+
*/
144+
function _setStakingStartTime(uint256 stakingStartTime_) internal {
145+
_stakingStartTime = stakingStartTime_;
146+
}
147+
148+
/**
149+
* @notice Sets the rate of rewards distribution per second.
150+
* @param newRate_ The new rate of rewards distribution.
151+
*/
152+
function _setRate(uint256 newRate_) internal {
153+
_update(address(0));
154+
155+
_rate = newRate_;
156+
}
157+
158+
/**
159+
* @notice Hook function that is called after shares have been added to a user's distribution.
160+
* @param user_ The address of the user.
161+
* @param amount_ The amount of shares added.
162+
*/
163+
function _afterAddShares(address user_, uint256 amount_) internal virtual override {
164+
IERC20(_sharesToken).safeTransferFrom(user_, address(this), amount_);
165+
}
166+
167+
/**
168+
* @notice Hook function that is called after shares have been removed from a user's distribution.
169+
* @param user_ The address of the user.
170+
* @param amount_ The amount of shares removed.
171+
*/
172+
function _afterRemoveShares(address user_, uint256 amount_) internal virtual override {
173+
IERC20(_sharesToken).safeTransfer(user_, amount_);
174+
}
175+
176+
/**
177+
* @notice Hook function that is called after value has been distributed to a user.
178+
* @param user_ The address of the user.
179+
* @param amount_ The amount of value distributed.
180+
*/
181+
function _afterDistributeValue(address user_, uint256 amount_) internal virtual override {
182+
IERC20(_rewardsToken).safeTransfer(user_, amount_);
183+
}
184+
185+
/**
186+
* @dev Throws if the staking has not started yet.
187+
*/
188+
function _checkStakingStarted() internal view {
189+
require(block.timestamp >= _stakingStartTime, "Staking: staking has not started yet");
190+
}
191+
192+
/**
193+
* @notice Gets the value to be distributed for a given time period.
194+
* @param timeUpTo_ The end timestamp of the period.
195+
* @param timeLastUpdate_ The start timestamp of the period.
196+
* @return The value to be distributed for the period.
197+
*/
198+
function _getValueToDistribute(
199+
uint256 timeUpTo_,
200+
uint256 timeLastUpdate_
201+
) internal view virtual override returns (uint256) {
202+
return _rate * (timeUpTo_ - timeLastUpdate_);
203+
}
204+
}

0 commit comments

Comments
 (0)