Skip to content

Commit 464d016

Browse files
Gas Optimization Bounty submission #1 (#452)
* NFT Stake optimizations (#417) * Update Struct `Staker` * uint256 amountStake => uint64 amountStaked * uint256 conditionIdOflastUpdate => uint64 conditionIdOflastUpdate * uint256 timeOfLastUpdate => uint128 timeOfLastUpdate * Other associated changes to facilitate use of updated struct * Remove `stakersArray` and reorder varibles `stakersArray` does not appear to serve any purpose other than to allow for easy off-chain retreival of all staked users. Moving the declaration of indexArray below the non-dynamic variables allows for more efficient variable packing. This saves ~2K gas on the staking function. * Remove ownership and approval check from `_stake` The removed checks are included in all modern ERC721 implementations. If a project removes them from their ERC721 contract, they should override the `_stake` function and include them there. However, in most instances, it is redundant to include them in `_stake`. * linting * run yarn gas * update gas report to ensure accuracy * Re-add stakers array * yarn gas --------- Co-authored-by: WhiteOakKong <92236155+WhiteOakKong@users.noreply.github.com>
1 parent a3713db commit 464d016

File tree

7 files changed

+71
-85
lines changed

7 files changed

+71
-85
lines changed

contracts/extension/Staking721.sol

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ abstract contract Staking721 is ReentrancyGuard, IStaking721 {
1717
///@dev Address of ERC721 NFT contract -- staked tokens belong to this contract.
1818
address public stakingToken;
1919

20+
/// @dev Flag to check direct transfers of staking tokens.
21+
uint8 internal isStaking = 1;
22+
23+
///@dev Next staking condition Id. Tracks number of conditon updates so far.
24+
uint64 private nextConditionId;
25+
2026
///@dev List of token-ids ever staked.
2127
uint256[] public indexedTokens;
2228

2329
/// @dev List of accounts that have staked their NFTs.
2430
address[] public stakersArray;
2531

26-
/// @dev Flag to check direct transfers of staking tokens.
27-
uint8 internal isStaking = 1;
28-
29-
///@dev Next staking condition Id. Tracks number of conditon updates so far.
30-
uint256 private nextConditionId;
31-
3232
///@dev Mapping from token-id to whether it is indexed or not.
3333
mapping(uint256 => bool) public isIndexed;
3434

@@ -175,7 +175,7 @@ abstract contract Staking721 is ReentrancyGuard, IStaking721 {
175175

176176
/// @dev Staking logic. Override to add custom logic.
177177
function _stake(uint256[] calldata _tokenIds) internal virtual {
178-
uint256 len = _tokenIds.length;
178+
uint64 len = uint64(_tokenIds.length);
179179
require(len != 0, "Staking 0 tokens");
180180

181181
address _stakingToken = stakingToken;
@@ -184,17 +184,10 @@ abstract contract Staking721 is ReentrancyGuard, IStaking721 {
184184
_updateUnclaimedRewardsForStaker(_stakeMsgSender());
185185
} else {
186186
stakersArray.push(_stakeMsgSender());
187-
stakers[_stakeMsgSender()].timeOfLastUpdate = block.timestamp;
187+
stakers[_stakeMsgSender()].timeOfLastUpdate = uint128(block.timestamp);
188188
stakers[_stakeMsgSender()].conditionIdOflastUpdate = nextConditionId - 1;
189189
}
190190
for (uint256 i = 0; i < len; ++i) {
191-
require(
192-
IERC721(_stakingToken).ownerOf(_tokenIds[i]) == _stakeMsgSender() &&
193-
(IERC721(_stakingToken).getApproved(_tokenIds[i]) == address(this) ||
194-
IERC721(_stakingToken).isApprovedForAll(_stakeMsgSender(), address(this))),
195-
"Not owned or approved"
196-
);
197-
198191
isStaking = 2;
199192
IERC721(_stakingToken).safeTransferFrom(_stakeMsgSender(), address(this), _tokenIds[i]);
200193
isStaking = 1;
@@ -214,7 +207,7 @@ abstract contract Staking721 is ReentrancyGuard, IStaking721 {
214207
/// @dev Withdraw logic. Override to add custom logic.
215208
function _withdraw(uint256[] calldata _tokenIds) internal virtual {
216209
uint256 _amountStaked = stakers[_stakeMsgSender()].amountStaked;
217-
uint256 len = _tokenIds.length;
210+
uint64 len = uint64(_tokenIds.length);
218211
require(len != 0, "Withdrawing 0 tokens");
219212
require(_amountStaked >= len, "Withdrawing more than staked");
220213

@@ -249,7 +242,7 @@ abstract contract Staking721 is ReentrancyGuard, IStaking721 {
249242

250243
require(rewards != 0, "No rewards");
251244

252-
stakers[_stakeMsgSender()].timeOfLastUpdate = block.timestamp;
245+
stakers[_stakeMsgSender()].timeOfLastUpdate = uint128(block.timestamp);
253246
stakers[_stakeMsgSender()].unclaimedRewards = 0;
254247
stakers[_stakeMsgSender()].conditionIdOflastUpdate = nextConditionId - 1;
255248

@@ -271,7 +264,7 @@ abstract contract Staking721 is ReentrancyGuard, IStaking721 {
271264
function _updateUnclaimedRewardsForStaker(address _staker) internal virtual {
272265
uint256 rewards = _calculateRewards(_staker);
273266
stakers[_staker].unclaimedRewards += rewards;
274-
stakers[_staker].timeOfLastUpdate = block.timestamp;
267+
stakers[_staker].timeOfLastUpdate = uint128(block.timestamp);
275268
stakers[_staker].conditionIdOflastUpdate = nextConditionId - 1;
276269
}
277270

contracts/extension/Staking721Upgradeable.sol

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking7
1717
///@dev Address of ERC721 NFT contract -- staked tokens belong to this contract.
1818
address public stakingToken;
1919

20+
/// @dev Flag to check direct transfers of staking tokens.
21+
uint8 internal isStaking = 1;
22+
23+
///@dev Next staking condition Id. Tracks number of conditon updates so far.
24+
uint64 private nextConditionId;
25+
2026
///@dev List of token-ids ever staked.
2127
uint256[] public indexedTokens;
2228

2329
/// @dev List of accounts that have staked their NFTs.
2430
address[] public stakersArray;
2531

26-
/// @dev Flag to check direct transfers of staking tokens.
27-
uint8 internal isStaking = 1;
28-
29-
///@dev Next staking condition Id. Tracks number of conditon updates so far.
30-
uint256 private nextConditionId;
31-
3232
///@dev Mapping from token-id to whether it is indexed or not.
3333
mapping(uint256 => bool) public isIndexed;
3434

@@ -177,7 +177,7 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking7
177177

178178
/// @dev Staking logic. Override to add custom logic.
179179
function _stake(uint256[] calldata _tokenIds) internal virtual {
180-
uint256 len = _tokenIds.length;
180+
uint64 len = uint64(_tokenIds.length);
181181
require(len != 0, "Staking 0 tokens");
182182

183183
address _stakingToken = stakingToken;
@@ -186,17 +186,10 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking7
186186
_updateUnclaimedRewardsForStaker(_stakeMsgSender());
187187
} else {
188188
stakersArray.push(_stakeMsgSender());
189-
stakers[_stakeMsgSender()].timeOfLastUpdate = block.timestamp;
189+
stakers[_stakeMsgSender()].timeOfLastUpdate = uint128(block.timestamp);
190190
stakers[_stakeMsgSender()].conditionIdOflastUpdate = nextConditionId - 1;
191191
}
192192
for (uint256 i = 0; i < len; ++i) {
193-
require(
194-
IERC721(_stakingToken).ownerOf(_tokenIds[i]) == _stakeMsgSender() &&
195-
(IERC721(_stakingToken).getApproved(_tokenIds[i]) == address(this) ||
196-
IERC721(_stakingToken).isApprovedForAll(_stakeMsgSender(), address(this))),
197-
"Not owned or approved"
198-
);
199-
200193
isStaking = 2;
201194
IERC721(_stakingToken).safeTransferFrom(_stakeMsgSender(), address(this), _tokenIds[i]);
202195
isStaking = 1;
@@ -216,7 +209,7 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking7
216209
/// @dev Withdraw logic. Override to add custom logic.
217210
function _withdraw(uint256[] calldata _tokenIds) internal virtual {
218211
uint256 _amountStaked = stakers[_stakeMsgSender()].amountStaked;
219-
uint256 len = _tokenIds.length;
212+
uint64 len = uint64(_tokenIds.length);
220213
require(len != 0, "Withdrawing 0 tokens");
221214
require(_amountStaked >= len, "Withdrawing more than staked");
222215

@@ -251,7 +244,7 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking7
251244

252245
require(rewards != 0, "No rewards");
253246

254-
stakers[_stakeMsgSender()].timeOfLastUpdate = block.timestamp;
247+
stakers[_stakeMsgSender()].timeOfLastUpdate = uint128(block.timestamp);
255248
stakers[_stakeMsgSender()].unclaimedRewards = 0;
256249
stakers[_stakeMsgSender()].conditionIdOflastUpdate = nextConditionId - 1;
257250

@@ -273,7 +266,7 @@ abstract contract Staking721Upgradeable is ReentrancyGuardUpgradeable, IStaking7
273266
function _updateUnclaimedRewardsForStaker(address _staker) internal virtual {
274267
uint256 rewards = _calculateRewards(_staker);
275268
stakers[_staker].unclaimedRewards += rewards;
276-
stakers[_staker].timeOfLastUpdate = block.timestamp;
269+
stakers[_staker].timeOfLastUpdate = uint128(block.timestamp);
277270
stakers[_staker].conditionIdOflastUpdate = nextConditionId - 1;
278271
}
279272

contracts/extension/interface/IStaking721.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ interface IStaking721 {
3131
* @param conditionIdOflastUpdate Condition-Id when rewards were last updated for user.
3232
*/
3333
struct Staker {
34-
uint256 amountStaked;
35-
uint256 timeOfLastUpdate;
34+
uint64 amountStaked;
35+
uint64 conditionIdOflastUpdate;
36+
uint128 timeOfLastUpdate;
3637
uint256 unclaimedRewards;
37-
uint256 conditionIdOflastUpdate;
3838
}
3939

4040
/**

gasreport.txt

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,98 @@
11
Compiling 70 files with 0.8.12
2-
Solc 0.8.12 finished in 136.28s
2+
Solc 0.8.12 finished in 133.05s
33
Compiler run successful
44

55
Running 3 tests for src/test/benchmark/EditionStakeBenchmark.t.sol:EditionStakeBenchmarkTest
66
[PASS] test_benchmark_editionStake_claimRewards() (gas: 69252)
77
[PASS] test_benchmark_editionStake_stake() (gas: 214287)
88
[PASS] test_benchmark_editionStake_withdraw() (gas: 49469)
9-
Test result: ok. 3 passed; 0 failed; finished in 399.92ms
9+
Test result: ok. 3 passed; 0 failed; finished in 375.95ms
1010

1111
Running 2 tests for src/test/benchmark/MultiwrapBenchmark.t.sol:MultiwrapBenchmarkTest
1212
[PASS] test_benchmark_multiwrap_unwrap() (gas: 92404)
1313
[PASS] test_benchmark_multiwrap_wrap() (gas: 475314)
14-
Test result: ok. 2 passed; 0 failed; finished in 399.85ms
14+
Test result: ok. 2 passed; 0 failed; finished in 376.62ms
1515

16-
Running 5 tests for src/test/benchmark/SignatureDropBenchmark.t.sol:SignatureDropBenchmarkTest
17-
[PASS] test_bechmark_signatureDrop_claim_five_tokens() (gas: 141456)
18-
[PASS] test_bechmark_signatureDrop_setClaimConditions() (gas: 73774)
19-
[PASS] test_benchmark_signatureDrop_lazyMint() (gas: 124639)
20-
[PASS] test_benchmark_signatureDrop_lazyMint_for_delayed_reveal() (gas: 226228)
21-
[PASS] test_benchmark_signatureDrop_reveal() (gas: 9183)
22-
Test result: ok. 5 passed; 0 failed; finished in 402.05ms
16+
Running 3 tests for src/test/benchmark/PackBenchmark.t.sol:PackBenchmarkTest
17+
[PASS] test_benchmark_pack_addPackContents() (gas: 219449)
18+
[PASS] test_benchmark_pack_createPack() (gas: 1425232)
19+
[PASS] test_benchmark_pack_openPack() (gas: 170278)
20+
Test result: ok. 3 passed; 0 failed; finished in 378.83ms
2321

2422
Running 4 tests for src/test/benchmark/TokenERC1155Benchmark.t.sol:TokenERC1155BenchmarkTest
2523
[PASS] test_benchmark_tokenERC1155_burn() (gas: 5744)
2624
[PASS] test_benchmark_tokenERC1155_mintTo() (gas: 121028)
2725
[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 263757)
2826
[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 292255)
29-
Test result: ok. 4 passed; 0 failed; finished in 411.39ms
27+
Test result: ok. 4 passed; 0 failed; finished in 377.39ms
28+
29+
Running 5 tests for src/test/benchmark/SignatureDropBenchmark.t.sol:SignatureDropBenchmarkTest
30+
[PASS] test_bechmark_signatureDrop_claim_five_tokens() (gas: 141456)
31+
[PASS] test_bechmark_signatureDrop_setClaimConditions() (gas: 73774)
32+
[PASS] test_benchmark_signatureDrop_lazyMint() (gas: 124639)
33+
[PASS] test_benchmark_signatureDrop_lazyMint_for_delayed_reveal() (gas: 226228)
34+
[PASS] test_benchmark_signatureDrop_reveal() (gas: 9183)
35+
Test result: ok. 5 passed; 0 failed; finished in 378.21ms
3036

3137
Running 1 test for src/test/benchmark/AirdropERC20Benchmark.t.sol:AirdropERC20BenchmarkTest
3238
[PASS] test_benchmark_airdropERC20_airdrop() (gas: 32173710)
33-
Test result: ok. 1 passed; 0 failed; finished in 416.85ms
39+
Test result: ok. 1 passed; 0 failed; finished in 394.74ms
3440

3541
Running 1 test for src/test/benchmark/AirdropERC1155Benchmark.t.sol:AirdropERC1155BenchmarkTest
3642
[PASS] test_benchmark_airdropERC1155_airdrop() (gas: 38084693)
37-
Test result: ok. 1 passed; 0 failed; finished in 432.26ms
43+
Test result: ok. 1 passed; 0 failed; finished in 398.96ms
3844

3945
Running 1 test for src/test/benchmark/AirdropERC721Benchmark.t.sol:AirdropERC721BenchmarkTest
4046
[PASS] test_benchmark_airdropERC721_airdrop() (gas: 43898701)
41-
Test result: ok. 1 passed; 0 failed; finished in 465.71ms
47+
Test result: ok. 1 passed; 0 failed; finished in 416.83ms
48+
49+
Running 3 tests for src/test/benchmark/NFTStakeBenchmark.t.sol:NFTStakeBenchmarkTest
50+
[PASS] test_benchmark_nftStake_claimRewards() (gas: 68826)
51+
[PASS] test_benchmark_nftStake_stake_five_tokens() (gas: 549421)
52+
[PASS] test_benchmark_nftStake_withdraw() (gas: 39648)
53+
Test result: ok. 3 passed; 0 failed; finished in 156.09ms
54+
55+
Running 4 tests for src/test/benchmark/TokenERC721Benchmark.t.sol:TokenERC721BenchmarkTest
56+
[PASS] test_benchmark_tokenERC721_burn() (gas: 10411)
57+
[PASS] test_benchmark_tokenERC721_mintTo() (gas: 149977)
58+
[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_ERC20() (gas: 259382)
59+
[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_native_token() (gas: 283724)
60+
Test result: ok. 4 passed; 0 failed; finished in 160.13ms
4261

4362
Running 3 tests for src/test/benchmark/TokenStakeBenchmark.t.sol:TokenStakeBenchmarkTest
4463
[PASS] test_benchmark_tokenStake_claimRewards() (gas: 75694)
4564
[PASS] test_benchmark_tokenStake_stake() (gas: 181796)
4665
[PASS] test_benchmark_tokenStake_withdraw() (gas: 55541)
47-
Test result: ok. 3 passed; 0 failed; finished in 182.91ms
66+
Test result: ok. 3 passed; 0 failed; finished in 166.98ms
4867

4968
Running 3 tests for src/test/benchmark/TokenERC20Benchmark.t.sol:TokenERC20BenchmarkTest
5069
[PASS] test_benchmark_tokenERC20_mintTo() (gas: 118533)
5170
[PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_ERC20() (gas: 181738)
5271
[PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_native_token() (gas: 206150)
53-
Test result: ok. 3 passed; 0 failed; finished in 192.16ms
54-
55-
Running 3 tests for src/test/benchmark/NFTStakeBenchmark.t.sol:NFTStakeBenchmarkTest
56-
[PASS] test_benchmark_nftStake_claimRewards() (gas: 68479)
57-
[PASS] test_benchmark_nftStake_stake_five_tokens() (gas: 617449)
58-
[PASS] test_benchmark_nftStake_withdraw() (gas: 39121)
59-
Test result: ok. 3 passed; 0 failed; finished in 210.22ms
60-
61-
Running 4 tests for src/test/benchmark/TokenERC721Benchmark.t.sol:TokenERC721BenchmarkTest
62-
[PASS] test_benchmark_tokenERC721_burn() (gas: 10411)
63-
[PASS] test_benchmark_tokenERC721_mintTo() (gas: 149977)
64-
[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_ERC20() (gas: 259382)
65-
[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_native_token() (gas: 283724)
66-
Test result: ok. 4 passed; 0 failed; finished in 187.04ms
72+
Test result: ok. 3 passed; 0 failed; finished in 160.61ms
6773

68-
Running 3 tests for src/test/benchmark/PackBenchmark.t.sol:PackBenchmarkTest
69-
[PASS] test_benchmark_pack_addPackContents() (gas: 219449)
70-
[PASS] test_benchmark_pack_createPack() (gas: 1425232)
71-
[PASS] test_benchmark_pack_openPack() (gas: 170278)
72-
Test result: ok. 3 passed; 0 failed; finished in 173.34ms
74+
Running 3 tests for src/test/benchmark/PackVRFDirectBenchmark.t.sol:PackVRFDirectBenchmarkTest
75+
[PASS] test_benchmark_packvrf_createPack() (gas: 1391866)
76+
[PASS] test_benchmark_packvrf_openPack() (gas: 119981)
77+
[PASS] test_benchmark_packvrf_openPackAndClaimRewards() (gas: 3621)
78+
Test result: ok. 3 passed; 0 failed; finished in 153.03ms
7379

7480
Running 2 tests for src/test/benchmark/DropERC20Benchmark.t.sol:DropERC20BenchmarkTest
7581
[PASS] test_bechmark_dropERC20_claim() (gas: 230441)
7682
[PASS] test_bechmark_dropERC20_setClaimConditions_five_conditions() (gas: 501045)
77-
Test result: ok. 2 passed; 0 failed; finished in 765.73ms
83+
Test result: ok. 2 passed; 0 failed; finished in 706.23ms
7884

7985
Running 3 tests for src/test/benchmark/DropERC1155Benchmark.t.sol:DropERC1155BenchmarkTest
8086
[PASS] test_bechmark_dropERC1155_claim() (gas: 186668)
8187
[PASS] test_bechmark_dropERC1155_setClaimConditions_five_conditions() (gas: 492331)
8288
[PASS] test_benchmark_dropERC1155_lazyMint() (gas: 123873)
83-
Test result: ok. 3 passed; 0 failed; finished in 784.83ms
84-
85-
Running 3 tests for src/test/benchmark/PackVRFDirectBenchmark.t.sol:PackVRFDirectBenchmarkTest
86-
[PASS] test_benchmark_packvrf_createPack() (gas: 1391866)
87-
[PASS] test_benchmark_packvrf_openPack() (gas: 119981)
88-
[PASS] test_benchmark_packvrf_openPackAndClaimRewards() (gas: 3621)
89-
Test result: ok. 3 passed; 0 failed; finished in 127.97ms
89+
Test result: ok. 3 passed; 0 failed; finished in 713.16ms
9090

9191
Running 5 tests for src/test/benchmark/DropERC721Benchmark.t.sol:DropERC721BenchmarkTest
9292
[PASS] test_bechmark_dropERC721_claim_five_tokens() (gas: 211967)
9393
[PASS] test_bechmark_dropERC721_setClaimConditions_five_conditions() (gas: 500859)
9494
[PASS] test_benchmark_dropERC721_lazyMint() (gas: 124544)
9595
[PASS] test_benchmark_dropERC721_lazyMint_for_delayed_reveal() (gas: 226124)
9696
[PASS] test_benchmark_dropERC721_reveal() (gas: 8800)
97-
Test result: ok. 5 passed; 0 failed; finished in 875.25ms
97+
Test result: ok. 5 passed; 0 failed; finished in 359.38ms
9898

src/test/sdk/extension/StakingExtension.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ contract StakingExtensionTest is DSTest, Test {
198198
_tokenIds[0] = 6;
199199

200200
vm.prank(stakerOne);
201-
vm.expectRevert("Not owned or approved");
201+
vm.expectRevert("ERC721: transfer from incorrect owner");
202202
ext.stake(_tokenIds);
203203
}
204204

src/test/staking/NFTStake.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ contract NFTStakeTest is BaseTest {
151151
_tokenIds[0] = 6;
152152

153153
vm.prank(stakerOne);
154-
vm.expectRevert("Not owned or approved");
154+
vm.expectRevert("ERC721: transfer from incorrect owner");
155155
stakeContract.stake(_tokenIds);
156156
}
157157

src/test/staking/NFTStake_EthReward.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ contract NFTStakeEthRewardTest is BaseTest {
160160
_tokenIds[0] = 6;
161161

162162
vm.prank(stakerOne);
163-
vm.expectRevert("Not owned or approved");
163+
vm.expectRevert("ERC721: transfer from incorrect owner");
164164
stakeContract.stake(_tokenIds);
165165
}
166166

0 commit comments

Comments
 (0)