/
GovernanceStaking.sol
192 lines (168 loc) · 5.44 KB
/
GovernanceStaking.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "../utils/DAOUpgradeableContract.sol";
import "../utils/NameService.sol";
import "../Interfaces.sol";
import "../DAOStackInterfaces.sol";
import "./MultiBaseGovernanceShareField.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
/**
* @title Staking contract that allows citizens to stake G$ to get GDAO rewards
*/
contract GovernanceStaking is
ERC20Upgradeable,
MultiBaseGovernanceShareField,
DAOUpgradeableContract,
ReentrancyGuardUpgradeable
{
uint256 public constant FUSE_MONTHLY_BLOCKS = 12 * 60 * 24 * 30;
// Token address
ERC20 token;
/**
* @dev Emitted when `staker` earns an `amount` of GOOD tokens
*/
event ReputationEarned(address indexed staker, uint256 amount);
/**
* @dev Emitted when `staker` stakes an `amount` of GoodDollars
*/
event Staked(address indexed staker, uint256 amount);
/**
* @dev Emitted when `staker` withdraws an `amount` of staked GoodDollars
*/
event StakeWithdraw(address indexed staker, uint256 amount);
/**
* @dev Constructor
* @param _ns The address of the INameService contract
*/
constructor(INameService _ns) initializer {
setDAO(_ns);
token = ERC20(nameService.getAddress("GOODDOLLAR"));
__ERC20_init("G$ Staking For GOOD", "sG$");
rewardsPerBlock[address(this)] = (2 ether * 1e6) / FUSE_MONTHLY_BLOCKS; // (2M monthly GDAO as specified in specs, divided by blocks in month )
}
/**
* @dev this contract runs on fuse
*/
function getChainBlocksPerMonth() public pure override returns (uint256) {
return 518400; //12 * 60 * 24 * 30
}
/**
* @dev Allows a staker to deposit Tokens. Notice that `approve` is
* needed to be executed before the execution of this method.
* Can be executed only when the contract is not paused.
* @param _amount The amount of GD to stake
*/
function stake(uint256 _amount) external {
require(_amount > 0, "You need to stake a positive token amount");
require(
token.transferFrom(_msgSender(), address(this), _amount),
"transferFrom failed, make sure you approved token transfer"
);
_increaseProductivity(
address(this),
_msgSender(),
_amount,
0,
block.number
);
_mint(_msgSender(), _amount); // mint Staking token for staker
_mintRewards(_msgSender());
emit Staked(_msgSender(), _amount);
}
/**
* @dev Withdraws the sender staked Token.
*/
function withdrawStake(uint256 _amount) external nonReentrant {
(uint256 userProductivity, ) = getProductivity(address(this), _msgSender());
if (_amount == 0) _amount = userProductivity;
require(_amount > 0, "Should withdraw positive amount");
require(userProductivity >= _amount, "Not enough token staked");
uint256 tokenWithdraw = _amount;
_burn(_msgSender(), _amount); // burn their staking tokens
_decreaseProductivity(
address(this),
_msgSender(),
_amount,
0,
block.number
);
_mintRewards(_msgSender());
require(
token.transfer(_msgSender(), tokenWithdraw),
"withdraw transfer failed"
);
emit StakeWithdraw(_msgSender(), _amount);
}
/**
* @dev Staker can withdraw their rewards without withdraw their stake
*/
function withdrawRewards() public nonReentrant returns (uint256) {
return _mintRewards(_msgSender());
}
/**
* @dev Mint rewards of the staker
* @param user Receipent address of the rewards
* @return Returns amount of the minted rewards
* emits 'ReputationEarned' event for staker earned GOOD amount
*/
function _mintRewards(address user) internal returns (uint256) {
uint256 amount = _issueEarnedRewards(address(this), user, 0, block.number);
if (amount > 0) {
ERC20(nameService.getAddress("REPUTATION")).mint(user, amount);
emit ReputationEarned(_msgSender(), amount);
}
return amount;
}
/**
* @dev Returns the number of decimals used to get its user representation.
*/
function decimals() public view virtual override returns (uint8) {
return 2;
}
/**
* @dev override _transfer to handle rewards calculations when transfer the stake
*/
function _transfer(
address from,
address to,
uint256 value
) internal override {
_decreaseProductivity(address(this), from, value, 0, block.number);
_increaseProductivity(address(this), to, value, 0, block.number);
_mintRewards(from);
_mintRewards(to);
super._transfer(from, to, value);
}
/**
* @dev Calculate rewards per block from monthly amount of rewards and set it
* @param _monthlyAmount total rewards which will distribute monthly
*/
function setMonthlyRewards(uint256 _monthlyAmount) public {
_onlyAvatar();
_setMonthlyRewards(address(this), _monthlyAmount);
}
/// @dev helper function for multibase
function getRewardsPerBlock() public view returns (uint256) {
return rewardsPerBlock[address(this)];
}
/// @dev helper function for multibase
function getProductivity(address _user)
public
view
returns (uint256, uint256)
{
return getProductivity(address(this), _user);
}
/// @dev helper function for multibase
function getUserPendingReward(address _user) public view returns (uint256) {
return getUserPendingReward(address(this), 0, block.number, _user);
}
/// @dev helper function for multibase
function users(address _user) public view returns (UserInfo memory) {
return contractToUsers[address(this)][_user];
}
function totalRewardsPerShare() public view returns (uint256) {
return totalRewardsPerShare(address(this));
}
}