Skip to content

Commit af41ffb

Browse files
authored
Staking - ERC1155 (#282)
* staking for ERC1155 * tests for ERC1155 staking prebuilt * more tests * view functions * code comments, docs
1 parent 95e1be8 commit af41ffb

22 files changed

+4697
-68
lines changed

contracts/extension/Staking1155.sol

Lines changed: 412 additions & 0 deletions
Large diffs are not rendered by default.

contracts/extension/Staking1155Upgradeable.sol

Lines changed: 414 additions & 0 deletions
Large diffs are not rendered by default.

contracts/extension/Staking721.sol

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,25 @@ import "./interface/IStaking.sol";
1212

1313
abstract contract Staking721 is ReentrancyGuard, IStaking {
1414
/*///////////////////////////////////////////////////////////////
15-
State variables
15+
State variables / Mappings
1616
//////////////////////////////////////////////////////////////*/
1717

18+
///@dev Address of ERC721 NFT contract -- staked tokens belong to this contract.
19+
address public nftCollection;
20+
1821
/// @dev Unit of time specified in number of seconds. Can be set as 1 seconds, 1 days, 1 hours, etc.
1922
uint256 public timeUnit;
2023

2124
///@dev Rewards accumulated per unit of time.
2225
uint256 public rewardsPerUnitTime;
2326

24-
///@dev Address of ERC721 NFT contract -- staked tokens belong to this contract.
25-
address public nftCollection;
27+
///@dev List of token-ids ever staked.
28+
uint256[] public indexedTokens;
29+
30+
///@dev Mapping from token-id to whether it is indexed or not.
31+
mapping(uint256 => bool) public isIndexed;
2632

27-
///@dev Mapping from staker address to Staker struct. See {struct IStaking.Staker}.
33+
///@dev Mapping from staker address to Staker struct. See {struct IStaking721.Staker}.
2834
mapping(address => Staker) public stakers;
2935

3036
/// @dev Mapping from staked token-id to staker address.
@@ -121,10 +127,35 @@ abstract contract Staking721 is ReentrancyGuard, IStaking {
121127
/**
122128
* @notice View amount staked and total rewards for a user.
123129
*
124-
* @param _staker Address for which to calculated rewards.
130+
* @param _staker Address for which to calculated rewards.
131+
* @return _tokensStaked List of token-ids staked by staker.
132+
* @return _rewards Available reward amount.
125133
*/
126-
function getStakeInfo(address _staker) public view virtual returns (uint256 _tokensStaked, uint256 _rewards) {
127-
_tokensStaked = stakers[_staker].amountStaked;
134+
function getStakeInfo(address _staker)
135+
public
136+
view
137+
virtual
138+
returns (uint256[] memory _tokensStaked, uint256 _rewards)
139+
{
140+
uint256[] memory _indexedTokens = indexedTokens;
141+
bool[] memory _isStakerToken = new bool[](_indexedTokens.length);
142+
uint256 indexedTokenCount = _indexedTokens.length;
143+
uint256 stakerTokenCount = 0;
144+
145+
for (uint256 i = 0; i < indexedTokenCount; i++) {
146+
_isStakerToken[i] = stakerAddress[_indexedTokens[i]] == _staker;
147+
if (_isStakerToken[i]) stakerTokenCount += 1;
148+
}
149+
150+
_tokensStaked = new uint256[](stakerTokenCount);
151+
uint256 count = 0;
152+
for (uint256 i = 0; i < indexedTokenCount; i++) {
153+
if (_isStakerToken[i]) {
154+
_tokensStaked[count] = _indexedTokens[i];
155+
count += 1;
156+
}
157+
}
158+
128159
_rewards = _availableRewards(_staker);
129160
}
130161

@@ -137,16 +168,28 @@ abstract contract Staking721 is ReentrancyGuard, IStaking {
137168
uint256 len = _tokenIds.length;
138169
require(len != 0, "Staking 0 tokens");
139170

171+
address _nftCollection = nftCollection;
172+
140173
if (stakers[msg.sender].amountStaked > 0) {
141174
_updateUnclaimedRewardsForStaker(msg.sender);
142175
} else {
143176
stakersArray.push(msg.sender);
144177
stakers[msg.sender].timeOfLastUpdate = block.timestamp;
145178
}
146179
for (uint256 i = 0; i < len; ++i) {
147-
require(IERC721(nftCollection).ownerOf(_tokenIds[i]) == msg.sender, "Not owner");
148-
IERC721(nftCollection).transferFrom(msg.sender, address(this), _tokenIds[i]);
180+
require(
181+
IERC721(_nftCollection).ownerOf(_tokenIds[i]) == msg.sender &&
182+
(IERC721(_nftCollection).getApproved(_tokenIds[i]) == address(this) ||
183+
IERC721(_nftCollection).isApprovedForAll(msg.sender, address(this))),
184+
"Not owned or approved"
185+
);
186+
IERC721(_nftCollection).transferFrom(msg.sender, address(this), _tokenIds[i]);
149187
stakerAddress[_tokenIds[i]] = msg.sender;
188+
189+
if (!isIndexed[_tokenIds[i]]) {
190+
isIndexed[_tokenIds[i]] = true;
191+
indexedTokens.push(_tokenIds[i]);
192+
}
150193
}
151194
stakers[msg.sender].amountStaked += len;
152195

@@ -160,6 +203,8 @@ abstract contract Staking721 is ReentrancyGuard, IStaking {
160203
require(len != 0, "Withdrawing 0 tokens");
161204
require(_amountStaked >= len, "Withdrawing more than staked");
162205

206+
address _nftCollection = nftCollection;
207+
163208
_updateUnclaimedRewardsForStaker(msg.sender);
164209

165210
if (_amountStaked == len) {
@@ -175,7 +220,7 @@ abstract contract Staking721 is ReentrancyGuard, IStaking {
175220
for (uint256 i = 0; i < len; ++i) {
176221
require(stakerAddress[_tokenIds[i]] == msg.sender, "Not staker");
177222
stakerAddress[_tokenIds[i]] = address(0);
178-
IERC721(nftCollection).transferFrom(address(this), msg.sender, _tokenIds[i]);
223+
IERC721(_nftCollection).transferFrom(address(this), msg.sender, _tokenIds[i]);
179224
}
180225

181226
emit TokensWithdrawn(msg.sender, _tokenIds);
@@ -242,7 +287,7 @@ abstract contract Staking721 is ReentrancyGuard, IStaking {
242287
}
243288

244289
/**
245-
* @dev Mint ERC20 rewards to the staker. Must override.
290+
* @dev Mint/Transfer ERC20 rewards to the staker. Must override.
246291
*
247292
* @param _staker Address for which to calculated rewards.
248293
* @param _rewards Amount of tokens to be given out as reward.
@@ -252,7 +297,7 @@ abstract contract Staking721 is ReentrancyGuard, IStaking {
252297
* ```
253298
* function _mintRewards(address _staker, uint256 _rewards) internal override {
254299
*
255-
* IERC20(rewardTokenAddress)._mint(_staker, _rewards);
300+
* TokenERC20(rewardTokenAddress).mintTo(_staker, _rewards);
256301
*
257302
* }
258303
* ```

contracts/extension/Staking721Upgradeable.sol

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,25 @@ import "./interface/IStaking.sol";
1212

1313
abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking {
1414
/*///////////////////////////////////////////////////////////////
15-
State variables
15+
State variables / Mappings
1616
//////////////////////////////////////////////////////////////*/
1717

18+
///@dev Address of ERC721 NFT contract -- staked tokens belong to this contract.
19+
address public nftCollection;
20+
1821
/// @dev Unit of time specified in number of seconds. Can be set as 1 seconds, 1 days, 1 hours, etc.
1922
uint256 public timeUnit;
2023

2124
///@dev Rewards accumulated per unit of time.
2225
uint256 public rewardsPerUnitTime;
2326

24-
///@dev Address of ERC721 NFT contract -- staked tokens belong to this contract.
25-
address public nftCollection;
27+
///@dev List of token-ids ever staked.
28+
uint256[] public indexedTokens;
29+
30+
///@dev Mapping from token-id to whether it is indexed or not.
31+
mapping(uint256 => bool) public isIndexed;
2632

27-
///@dev Mapping from staker address to Staker struct. See {struct IStaking.Staker}.
33+
///@dev Mapping from staker address to Staker struct. See {struct IStaking721.Staker}.
2834
mapping(address => Staker) public stakers;
2935

3036
/// @dev Mapping from staked token-id to staker address.
@@ -123,10 +129,35 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking
123129
/**
124130
* @notice View amount staked and total rewards for a user.
125131
*
126-
* @param _staker Address for which to calculated rewards.
132+
* @param _staker Address for which to calculated rewards.
133+
* @return _tokensStaked List of token-ids staked by staker.
134+
* @return _rewards Available reward amount.
127135
*/
128-
function getStakeInfo(address _staker) public view virtual returns (uint256 _tokensStaked, uint256 _rewards) {
129-
_tokensStaked = stakers[_staker].amountStaked;
136+
function getStakeInfo(address _staker)
137+
public
138+
view
139+
virtual
140+
returns (uint256[] memory _tokensStaked, uint256 _rewards)
141+
{
142+
uint256[] memory _indexedTokens = indexedTokens;
143+
bool[] memory _isStakerToken = new bool[](_indexedTokens.length);
144+
uint256 indexedTokenCount = _indexedTokens.length;
145+
uint256 stakerTokenCount = 0;
146+
147+
for (uint256 i = 0; i < indexedTokenCount; i++) {
148+
_isStakerToken[i] = stakerAddress[_indexedTokens[i]] == _staker;
149+
if (_isStakerToken[i]) stakerTokenCount += 1;
150+
}
151+
152+
_tokensStaked = new uint256[](stakerTokenCount);
153+
uint256 count = 0;
154+
for (uint256 i = 0; i < indexedTokenCount; i++) {
155+
if (_isStakerToken[i]) {
156+
_tokensStaked[count] = _indexedTokens[i];
157+
count += 1;
158+
}
159+
}
160+
130161
_rewards = _availableRewards(_staker);
131162
}
132163

@@ -139,16 +170,28 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking
139170
uint256 len = _tokenIds.length;
140171
require(len != 0, "Staking 0 tokens");
141172

173+
address _nftCollection = nftCollection;
174+
142175
if (stakers[msg.sender].amountStaked > 0) {
143176
_updateUnclaimedRewardsForStaker(msg.sender);
144177
} else {
145178
stakersArray.push(msg.sender);
146179
stakers[msg.sender].timeOfLastUpdate = block.timestamp;
147180
}
148181
for (uint256 i = 0; i < len; ++i) {
149-
require(IERC721(nftCollection).ownerOf(_tokenIds[i]) == msg.sender, "Not owner");
150-
IERC721(nftCollection).transferFrom(msg.sender, address(this), _tokenIds[i]);
182+
require(
183+
IERC721(_nftCollection).ownerOf(_tokenIds[i]) == msg.sender &&
184+
(IERC721(_nftCollection).getApproved(_tokenIds[i]) == address(this) ||
185+
IERC721(_nftCollection).isApprovedForAll(msg.sender, address(this))),
186+
"Not owned or approved"
187+
);
188+
IERC721(_nftCollection).transferFrom(msg.sender, address(this), _tokenIds[i]);
151189
stakerAddress[_tokenIds[i]] = msg.sender;
190+
191+
if (!isIndexed[_tokenIds[i]]) {
192+
isIndexed[_tokenIds[i]] = true;
193+
indexedTokens.push(_tokenIds[i]);
194+
}
152195
}
153196
stakers[msg.sender].amountStaked += len;
154197

@@ -162,6 +205,8 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking
162205
require(len != 0, "Withdrawing 0 tokens");
163206
require(_amountStaked >= len, "Withdrawing more than staked");
164207

208+
address _nftCollection = nftCollection;
209+
165210
_updateUnclaimedRewardsForStaker(msg.sender);
166211

167212
if (_amountStaked == len) {
@@ -177,7 +222,7 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking
177222
for (uint256 i = 0; i < len; ++i) {
178223
require(stakerAddress[_tokenIds[i]] == msg.sender, "Not staker");
179224
stakerAddress[_tokenIds[i]] = address(0);
180-
IERC721(nftCollection).transferFrom(address(this), msg.sender, _tokenIds[i]);
225+
IERC721(_nftCollection).transferFrom(address(this), msg.sender, _tokenIds[i]);
181226
}
182227

183228
emit TokensWithdrawn(msg.sender, _tokenIds);
@@ -244,7 +289,7 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking
244289
}
245290

246291
/**
247-
* @dev Mint ERC20 rewards to the staker. Must override.
292+
* @dev Mint/Transfer ERC20 rewards to the staker. Must override.
248293
*
249294
* @param _staker Address for which to calculated rewards.
250295
* @param _rewards Amount of tokens to be given out as reward.
@@ -254,7 +299,7 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking
254299
* ```
255300
* function _mintRewards(address _staker, uint256 _rewards) internal override {
256301
*
257-
* IERC20(rewardTokenAddress)._mint(_staker, _rewards);
302+
* TokenERC20(rewardTokenAddress).mintTo(_staker, _rewards);
258303
*
259304
* }
260305
* ```

contracts/extension/interface/IStaking.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,5 @@ interface IStaking {
5656
*
5757
* @param staker Address for which to calculated rewards.
5858
*/
59-
function getStakeInfo(address staker) external view returns (uint256 _tokensStaked, uint256 _rewards);
59+
function getStakeInfo(address staker) external view returns (uint256[] memory _tokensStaked, uint256 _rewards);
6060
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
interface IStaking1155 {
5+
/// @dev Emitted when tokens are staked.
6+
event TokensStaked(address indexed staker, uint256 indexed tokenId, uint256 amount);
7+
8+
/// @dev Emitted when a set of staked token-ids are withdrawn.
9+
event TokensWithdrawn(address indexed staker, uint256 indexed tokenId, uint256 amount);
10+
11+
/// @dev Emitted when a staker claims staking rewards.
12+
event RewardsClaimed(address indexed staker, uint256 rewardAmount);
13+
14+
/// @dev Emitted when contract admin updates timeUnit.
15+
event UpdatedTimeUnit(uint256 indexed _tokenId, uint256 oldTimeUnit, uint256 newTimeUnit);
16+
17+
/// @dev Emitted when contract admin updates rewardsPerUnitTime.
18+
event UpdatedRewardsPerUnitTime(
19+
uint256 indexed _tokenId,
20+
uint256 oldRewardsPerUnitTime,
21+
uint256 newRewardsPerUnitTime
22+
);
23+
24+
/// @dev Emitted when contract admin updates timeUnit.
25+
event UpdatedDefaultTimeUnit(uint256 oldTimeUnit, uint256 newTimeUnit);
26+
27+
/// @dev Emitted when contract admin updates rewardsPerUnitTime.
28+
event UpdatedDefaultRewardsPerUnitTime(uint256 oldRewardsPerUnitTime, uint256 newRewardsPerUnitTime);
29+
30+
/**
31+
* @notice Staker Info.
32+
*
33+
* @param amountStaked Total number of tokens staked by the staker.
34+
*
35+
* @param timeOfLastUpdate Last reward-update timestamp.
36+
*
37+
* @param unclaimedRewards Rewards accumulated but not claimed by user yet.
38+
*/
39+
struct Staker {
40+
uint256 amountStaked;
41+
uint256 timeOfLastUpdate;
42+
uint256 unclaimedRewards;
43+
}
44+
45+
/**
46+
* @notice Stake ERC721 Tokens.
47+
*
48+
* @param tokenId ERC1155 token-id to stake.
49+
* @param amount Amount to stake.
50+
*/
51+
function stake(uint256 tokenId, uint256 amount) external;
52+
53+
/**
54+
* @notice Withdraw staked tokens.
55+
*
56+
* @param tokenId ERC1155 token-id to withdraw.
57+
* @param amount Amount to withdraw.
58+
*/
59+
function withdraw(uint256 tokenId, uint256 amount) external;
60+
61+
/**
62+
* @notice Claim accumulated rewards.
63+
*
64+
* @param tokenId Staked token Id.
65+
*/
66+
function claimRewards(uint256 tokenId) external;
67+
68+
/**
69+
* @notice View amount staked and total rewards for a user.
70+
*
71+
* @param tokenId Staked token Id.
72+
* @param staker Address for which to calculated rewards.
73+
*/
74+
function getStakeInfoForToken(uint256 tokenId, address staker)
75+
external
76+
view
77+
returns (uint256 _tokensStaked, uint256 _rewards);
78+
79+
/**
80+
* @notice View amount staked and total rewards for a user.
81+
*
82+
* @param staker Address for which to calculated rewards.
83+
*/
84+
function getStakeInfo(address staker)
85+
external
86+
view
87+
returns (
88+
uint256[] memory _tokensStaked,
89+
uint256[] memory _tokenAmounts,
90+
uint256 _totalRewards
91+
);
92+
}

0 commit comments

Comments
 (0)