-
Notifications
You must be signed in to change notification settings - Fork 481
/
Staking20Base.sol
179 lines (151 loc) · 6.66 KB
/
Staking20Base.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
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
import "../extension/ContractMetadata.sol";
import "../extension/Multicall.sol";
import "../extension/Ownable.sol";
import "../extension/Staking20.sol";
import "../eip/interface/IERC20.sol";
import "../eip/interface/IERC20Metadata.sol";
import { CurrencyTransferLib } from "../lib/CurrencyTransferLib.sol";
/**
*
* EXTENSION: Staking20
*
* The `Staking20Base` smart contract implements Token staking mechanism.
* Allows users to stake their ERC-20 Tokens and earn rewards in form of another ERC-20 tokens.
*
* Following features and implementation setup must be noted:
*
* - ERC-20 Tokens from only one contract can be staked.
*
* - Contract admin can choose to give out rewards by either transferring or minting the rewardToken,
* which is ideally a different ERC20 token. See {_mintRewards}.
*
* - To implement custom logic for staking, reward calculation, etc. corresponding functions can be
* overridden from the extension `Staking20`.
*
* - Ownership of the contract, with the ability to restrict certain functions to
* only be called by the contract's owner.
*
* - Multicall capability to perform multiple actions atomically.
*
*/
/// note: This contract is provided as a base contract.
// This is to support a variety of use-cases that can be build on top of this base.
//
// Additional functionality such as deposit functions, reward-minting, etc.
// must be implemented by the deployer of this contract, as needed for their use-case.
contract Staking20Base is ContractMetadata, Multicall, Ownable, Staking20 {
/// @dev ERC20 Reward Token address. See {_mintRewards} below.
address public immutable rewardToken;
/// @dev Total amount of reward tokens in the contract.
uint256 private rewardTokenBalance;
constructor(
uint80 _timeUnit,
address _defaultAdmin,
uint256 _rewardRatioNumerator,
uint256 _rewardRatioDenominator,
address _stakingToken,
address _rewardToken,
address _nativeTokenWrapper
)
Staking20(
_nativeTokenWrapper,
_stakingToken,
IERC20Metadata(_stakingToken).decimals(),
IERC20Metadata(_rewardToken).decimals()
)
{
_setupOwner(_defaultAdmin);
_setStakingCondition(_timeUnit, _rewardRatioNumerator, _rewardRatioDenominator);
require(_rewardToken != _stakingToken, "Reward Token and Staking Token can't be same.");
rewardToken = _rewardToken;
}
/// @dev Lets the contract receive ether to unwrap native tokens.
receive() external payable virtual {
require(msg.sender == nativeTokenWrapper, "caller not native token wrapper.");
}
/// @dev Admin deposits reward tokens.
function depositRewardTokens(uint256 _amount) external payable virtual nonReentrant {
_depositRewardTokens(_amount); // override this for custom logic.
}
/// @dev Admin can withdraw excess reward tokens.
function withdrawRewardTokens(uint256 _amount) external virtual nonReentrant {
_withdrawRewardTokens(_amount); // override this for custom logic.
}
/// @notice View total rewards available in the staking contract.
function getRewardTokenBalance() external view virtual override returns (uint256) {
return rewardTokenBalance;
}
/*//////////////////////////////////////////////////////////////
Minting logic
//////////////////////////////////////////////////////////////*/
/**
* @dev Mint ERC20 rewards to the staker. Override for custom logic.
*
* @param _staker Address for which to calculated rewards.
* @param _rewards Amount of tokens to be given out as reward.
*
*/
function _mintRewards(address _staker, uint256 _rewards) internal virtual override {
require(_rewards <= rewardTokenBalance, "Not enough reward tokens");
rewardTokenBalance -= _rewards;
CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
address(this),
_staker,
_rewards,
nativeTokenWrapper
);
}
/*//////////////////////////////////////////////////////////////
Other Internal functions
//////////////////////////////////////////////////////////////*/
/// @dev Admin deposits reward tokens -- override for custom logic.
function _depositRewardTokens(uint256 _amount) internal virtual {
require(msg.sender == owner(), "Not authorized");
address _rewardToken = rewardToken == CurrencyTransferLib.NATIVE_TOKEN ? nativeTokenWrapper : rewardToken;
uint256 balanceBefore = IERC20(_rewardToken).balanceOf(address(this));
CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
msg.sender,
address(this),
_amount,
nativeTokenWrapper
);
uint256 actualAmount = IERC20(_rewardToken).balanceOf(address(this)) - balanceBefore;
rewardTokenBalance += actualAmount;
}
/// @dev Admin can withdraw excess reward tokens -- override for custom logic.
function _withdrawRewardTokens(uint256 _amount) internal virtual {
require(msg.sender == owner(), "Not authorized");
// to prevent locking of direct-transferred tokens
rewardTokenBalance = _amount > rewardTokenBalance ? 0 : rewardTokenBalance - _amount;
CurrencyTransferLib.transferCurrencyWithWrapper(
rewardToken,
address(this),
msg.sender,
_amount,
nativeTokenWrapper
);
// The withdrawal shouldn't reduce staking token balance. `>=` accounts for any accidental transfers.
address _stakingToken = stakingToken == CurrencyTransferLib.NATIVE_TOKEN ? nativeTokenWrapper : stakingToken;
require(
IERC20(_stakingToken).balanceOf(address(this)) >= stakingTokenBalance,
"Staking token balance reduced."
);
}
/// @dev Returns whether staking restrictions can be set in given execution context.
function _canSetStakeConditions() internal view virtual override returns (bool) {
return msg.sender == owner();
}
/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view virtual override returns (bool) {
return msg.sender == owner();
}
/// @dev Returns whether owner can be set in the given execution context.
function _canSetOwner() internal view virtual override returns (bool) {
return msg.sender == owner();
}
}