Skip to content

Commit 3f9b050

Browse files
authored
Staking base updates (#328)
* extend IERCxxxReceiver in staking bases * remove restriction from Staking20Base: reward and staking tokens can be same now * re-add restriction: staking and reward tokens can't be same * add native token support, other functionality * docs * fix slither action * fix slither action * slither action v3
1 parent 79ac32b commit 3f9b050

File tree

7 files changed

+536
-56
lines changed

7 files changed

+536
-56
lines changed

.github/workflows/slither.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
security-events: write
1515
steps:
1616
- name: Checkout repository
17-
uses: actions/checkout@v2
17+
uses: actions/checkout@v3
1818
with:
1919
submodules: recursive
2020

contracts/base/Staking1155Base.sol

Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import "../extension/Multicall.sol";
66
import "../extension/Ownable.sol";
77
import "../extension/Staking1155.sol";
88

9+
import "../eip/ERC165.sol";
910
import "../eip/interface/IERC20.sol";
11+
import "../eip/interface/IERC1155Receiver.sol";
12+
13+
import { CurrencyTransferLib } from "../lib/CurrencyTransferLib.sol";
1014

1115
/**
1216
*
@@ -31,56 +35,146 @@ import "../eip/interface/IERC20.sol";
3135
* - Multicall capability to perform multiple actions atomically.
3236
*
3337
*/
34-
contract Staking1155Base is ContractMetadata, Multicall, Ownable, Staking1155 {
38+
39+
/// note: This contract is provided as a base contract.
40+
// This is to support a variety of use-cases that can be build on top of this base.
41+
//
42+
// Additional functionality such as deposit functions, reward-minting, etc.
43+
// must be implemented by the deployer of this contract, as needed for their use-case.
44+
45+
contract Staking1155Base is ContractMetadata, Multicall, Ownable, Staking1155, ERC165, IERC1155Receiver {
3546
/// @dev ERC20 Reward Token address. See {_mintRewards} below.
3647
address public rewardToken;
3748

49+
/// @dev The address of the native token wrapper contract.
50+
address internal immutable nativeTokenWrapper;
51+
52+
/// @dev Total amount of reward tokens in the contract.
53+
uint256 private rewardTokenBalance;
54+
3855
constructor(
3956
uint256 _defaultTimeUnit,
4057
uint256 _defaultRewardsPerUnitTime,
4158
address _stakingToken,
42-
address _rewardToken
59+
address _rewardToken,
60+
address _nativeTokenWrapper
4361
) Staking1155(_stakingToken) {
4462
_setupOwner(msg.sender);
4563
_setDefaultStakingCondition(_defaultTimeUnit, _defaultRewardsPerUnitTime);
4664

4765
rewardToken = _rewardToken;
66+
nativeTokenWrapper = _nativeTokenWrapper;
67+
}
68+
69+
/// @dev Lets the contract receive ether to unwrap native tokens.
70+
receive() external payable virtual {
71+
require(msg.sender == nativeTokenWrapper, "caller not native token wrapper.");
72+
}
73+
74+
/// @dev Admin deposits reward tokens.
75+
function depositRewardTokens(uint256 _amount) external payable nonReentrant {
76+
_depositRewardTokens(_amount); // override this for custom logic.
77+
}
78+
79+
/// @dev Admin can withdraw excess reward tokens.
80+
function withdrawRewardTokens(uint256 _amount) external nonReentrant {
81+
_withdrawRewardTokens(_amount); // override this for custom logic.
4882
}
4983

5084
/// @notice View total rewards available in the staking contract.
51-
function getRewardTokenBalance() external view virtual override returns (uint256 _rewardsAvailableInContract) {
52-
return IERC20(rewardToken).balanceOf(address(this));
85+
function getRewardTokenBalance() external view virtual override returns (uint256) {
86+
return rewardTokenBalance;
87+
}
88+
89+
/*///////////////////////////////////////////////////////////////
90+
ERC 165 / 721 logic
91+
//////////////////////////////////////////////////////////////*/
92+
93+
function onERC1155Received(
94+
address,
95+
address,
96+
uint256,
97+
uint256,
98+
bytes calldata
99+
) external returns (bytes4) {
100+
require(isStaking == 2, "Direct transfer");
101+
return this.onERC1155Received.selector;
102+
}
103+
104+
function onERC1155BatchReceived(
105+
address operator,
106+
address from,
107+
uint256[] calldata ids,
108+
uint256[] calldata values,
109+
bytes calldata data
110+
) external returns (bytes4) {}
111+
112+
function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) {
113+
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
53114
}
54115

55116
/*//////////////////////////////////////////////////////////////
56117
Minting logic
57118
//////////////////////////////////////////////////////////////*/
58119

59120
/**
60-
* @dev Mint ERC20 rewards to the staker. Must override.
121+
* @dev Mint ERC20 rewards to the staker. Override for custom logic.
61122
*
62123
* @param _staker Address for which to calculated rewards.
63124
* @param _rewards Amount of tokens to be given out as reward.
64125
*
65126
*/
66127
function _mintRewards(address _staker, uint256 _rewards) internal virtual override {
67-
// Mint or transfer reward-tokens here.
68-
// e.g.
69-
//
70-
// IERC20(rewardToken).transfer(_staker, _rewards);
71-
//
72-
// OR
73-
//
74-
// Use a mintable ERC20, such as thirdweb's `TokenERC20.sol`
75-
//
76-
// TokenERC20(rewardToken).mintTo(_staker, _rewards);
77-
// note: The staking contract should have minter role to mint tokens.
128+
require(_rewards <= rewardTokenBalance, "Not enough reward tokens");
129+
rewardTokenBalance -= _rewards;
130+
CurrencyTransferLib.transferCurrencyWithWrapper(
131+
rewardToken,
132+
address(this),
133+
_staker,
134+
_rewards,
135+
nativeTokenWrapper
136+
);
78137
}
79138

80139
/*//////////////////////////////////////////////////////////////
81140
Other Internal functions
82141
//////////////////////////////////////////////////////////////*/
83142

143+
/// @dev Admin deposits reward tokens -- override for custom logic.
144+
function _depositRewardTokens(uint256 _amount) internal virtual {
145+
require(msg.sender == owner(), "Not authorized");
146+
147+
address _rewardToken = rewardToken == CurrencyTransferLib.NATIVE_TOKEN ? nativeTokenWrapper : rewardToken;
148+
149+
uint256 balanceBefore = IERC20(_rewardToken).balanceOf(address(this));
150+
CurrencyTransferLib.transferCurrencyWithWrapper(
151+
rewardToken,
152+
msg.sender,
153+
address(this),
154+
_amount,
155+
nativeTokenWrapper
156+
);
157+
uint256 actualAmount = IERC20(_rewardToken).balanceOf(address(this)) - balanceBefore;
158+
159+
rewardTokenBalance += actualAmount;
160+
}
161+
162+
/// @dev Admin can withdraw excess reward tokens -- override for custom logic.
163+
function _withdrawRewardTokens(uint256 _amount) internal virtual {
164+
require(msg.sender == owner(), "Not authorized");
165+
166+
// to prevent locking of direct-transferred tokens
167+
rewardTokenBalance = _amount > rewardTokenBalance ? 0 : rewardTokenBalance - _amount;
168+
169+
CurrencyTransferLib.transferCurrencyWithWrapper(
170+
rewardToken,
171+
address(this),
172+
msg.sender,
173+
_amount,
174+
nativeTokenWrapper
175+
);
176+
}
177+
84178
/// @dev Returns whether staking restrictions can be set in given execution context.
85179
function _canSetStakeConditions() internal view virtual override returns (bool) {
86180
return msg.sender == owner();

contracts/base/Staking20Base.sol

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import "../extension/Staking20.sol";
99
import "../eip/interface/IERC20.sol";
1010
import "../eip/interface/IERC20Metadata.sol";
1111

12+
import { CurrencyTransferLib } from "../lib/CurrencyTransferLib.sol";
13+
1214
/**
1315
*
1416
* EXTENSION: Staking20
@@ -32,10 +34,20 @@ import "../eip/interface/IERC20Metadata.sol";
3234
* - Multicall capability to perform multiple actions atomically.
3335
*
3436
*/
37+
38+
/// note: This contract is provided as a base contract.
39+
// This is to support a variety of use-cases that can be build on top of this base.
40+
//
41+
// Additional functionality such as deposit functions, reward-minting, etc.
42+
// must be implemented by the deployer of this contract, as needed for their use-case.
43+
3544
contract Staking20Base is ContractMetadata, Multicall, Ownable, Staking20 {
3645
/// @dev ERC20 Reward Token address. See {_mintRewards} below.
3746
address public rewardToken;
3847

48+
/// @dev Total amount of reward tokens in the contract.
49+
uint256 private rewardTokenBalance;
50+
3951
constructor(
4052
uint256 _timeUnit,
4153
uint256 _rewardRatioNumerator,
@@ -58,40 +70,95 @@ contract Staking20Base is ContractMetadata, Multicall, Ownable, Staking20 {
5870
rewardToken = _rewardToken;
5971
}
6072

73+
/// @dev Lets the contract receive ether to unwrap native tokens.
74+
receive() external payable virtual {
75+
require(msg.sender == nativeTokenWrapper, "caller not native token wrapper.");
76+
}
77+
78+
/// @dev Admin deposits reward tokens.
79+
function depositRewardTokens(uint256 _amount) external payable nonReentrant {
80+
_depositRewardTokens(_amount); // override this for custom logic.
81+
}
82+
83+
/// @dev Admin can withdraw excess reward tokens.
84+
function withdrawRewardTokens(uint256 _amount) external nonReentrant {
85+
_withdrawRewardTokens(_amount); // override this for custom logic.
86+
}
87+
6188
/// @notice View total rewards available in the staking contract.
62-
function getRewardTokenBalance() external view virtual override returns (uint256 _rewardsAvailableInContract) {
63-
return IERC20(rewardToken).balanceOf(address(this));
89+
function getRewardTokenBalance() external view virtual override returns (uint256) {
90+
return rewardTokenBalance;
6491
}
6592

6693
/*//////////////////////////////////////////////////////////////
6794
Minting logic
6895
//////////////////////////////////////////////////////////////*/
6996

7097
/**
71-
* @dev Mint ERC20 rewards to the staker. Must override.
98+
* @dev Mint ERC20 rewards to the staker. Override for custom logic.
7299
*
73100
* @param _staker Address for which to calculated rewards.
74101
* @param _rewards Amount of tokens to be given out as reward.
75102
*
76103
*/
77104
function _mintRewards(address _staker, uint256 _rewards) internal virtual override {
78-
// Mint or transfer reward-tokens here.
79-
// e.g.
80-
//
81-
// IERC20(rewardToken).transfer(_staker, _rewards);
82-
//
83-
// OR
84-
//
85-
// Use a mintable ERC20, such as thirdweb's `TokenERC20.sol`
86-
//
87-
// TokenERC20(rewardToken).mintTo(_staker, _rewards);
88-
// note: The staking contract should have minter role to mint tokens.
105+
require(_rewards <= rewardTokenBalance, "Not enough reward tokens");
106+
rewardTokenBalance -= _rewards;
107+
CurrencyTransferLib.transferCurrencyWithWrapper(
108+
rewardToken,
109+
address(this),
110+
_staker,
111+
_rewards,
112+
nativeTokenWrapper
113+
);
89114
}
90115

91116
/*//////////////////////////////////////////////////////////////
92117
Other Internal functions
93118
//////////////////////////////////////////////////////////////*/
94119

120+
/// @dev Admin deposits reward tokens -- override for custom logic.
121+
function _depositRewardTokens(uint256 _amount) internal virtual {
122+
require(msg.sender == owner(), "Not authorized");
123+
124+
address _rewardToken = rewardToken == CurrencyTransferLib.NATIVE_TOKEN ? nativeTokenWrapper : rewardToken;
125+
126+
uint256 balanceBefore = IERC20(_rewardToken).balanceOf(address(this));
127+
CurrencyTransferLib.transferCurrencyWithWrapper(
128+
rewardToken,
129+
msg.sender,
130+
address(this),
131+
_amount,
132+
nativeTokenWrapper
133+
);
134+
uint256 actualAmount = IERC20(_rewardToken).balanceOf(address(this)) - balanceBefore;
135+
136+
rewardTokenBalance += actualAmount;
137+
}
138+
139+
/// @dev Admin can withdraw excess reward tokens -- override for custom logic.
140+
function _withdrawRewardTokens(uint256 _amount) internal virtual {
141+
require(msg.sender == owner(), "Not authorized");
142+
143+
// to prevent locking of direct-transferred tokens
144+
rewardTokenBalance = _amount > rewardTokenBalance ? 0 : rewardTokenBalance - _amount;
145+
146+
CurrencyTransferLib.transferCurrencyWithWrapper(
147+
rewardToken,
148+
address(this),
149+
msg.sender,
150+
_amount,
151+
nativeTokenWrapper
152+
);
153+
154+
// The withdrawal shouldn't reduce staking token balance. `>=` accounts for any accidental transfers.
155+
address _stakingToken = stakingToken == CurrencyTransferLib.NATIVE_TOKEN ? nativeTokenWrapper : stakingToken;
156+
require(
157+
IERC20(_stakingToken).balanceOf(address(this)) >= stakingTokenBalance,
158+
"Staking token balance reduced."
159+
);
160+
}
161+
95162
/// @dev Returns whether staking restrictions can be set in given execution context.
96163
function _canSetStakeConditions() internal view virtual override returns (bool) {
97164
return msg.sender == owner();

0 commit comments

Comments
 (0)