diff --git a/.gas-report b/.gas-report index a43cca3..46597ab 100644 --- a/.gas-report +++ b/.gas-report @@ -1,144 +1,177 @@ +| lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy contract | | | | | | +|-------------------------------------------------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 259350 | 1159 | | | | | +| Function Name | min | avg | median | max | # calls | +| MAX_LOCKUP_PERIOD | 644 | 1426 | 644 | 5144 | 23 | +| MAX_MULTIPLIER | 667 | 1567 | 667 | 5167 | 30 | +| MIN_LOCKUP_PERIOD | 646 | 3918 | 5146 | 5146 | 11 | +| MP_RATE_PER_YEAR | 602 | 602 | 602 | 602 | 3 | +| SCALE_FACTOR | 600 | 600 | 600 | 600 | 41 | +| STAKING_TOKEN | 7253 | 7253 | 7253 | 7253 | 182 | +| accountedRewards | 722 | 1289 | 722 | 2722 | 74 | +| emergencyModeEnabled | 7270 | 7270 | 7270 | 7270 | 7 | +| enableEmergencyMode | 28422 | 45323 | 50607 | 50607 | 8 | +| getAccount | 2020 | 2020 | 2020 | 2020 | 71 | +| getStakedBalance | 7464 | 7464 | 7464 | 7464 | 1 | +| isTrustedCodehash | 938 | 1454 | 938 | 2938 | 182 | +| rewardIndex | 766 | 793 | 766 | 2766 | 74 | +| setTrustedCodehash | 52794 | 52794 | 52794 | 52794 | 47 | +| totalMP | 723 | 723 | 723 | 723 | 77 | +| totalMaxMP | 722 | 722 | 722 | 722 | 77 | +| totalStaked | 723 | 723 | 723 | 723 | 78 | +| updateAccountMP | 41679 | 43917 | 44181 | 44181 | 19 | +| updateGlobalState | 36999 | 67243 | 56463 | 89414 | 28 | +| upgradeToAndCall | 29734 | 33540 | 33540 | 37346 | 2 | + + | src/RewardsStreamer.sol:RewardsStreamer contract | | | | | | |--------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 690902 | 3154 | | | | | +| 677674 | 3080 | | | | | | Function Name | min | avg | median | max | # calls | -| accountedRewards | 351 | 601 | 351 | 2351 | 8 | -| getUserInfo | 793 | 793 | 793 | 793 | 12 | -| rewardIndex | 350 | 600 | 350 | 2350 | 8 | -| stake | 85235 | 100736 | 105135 | 111838 | 3 | -| totalStaked | 351 | 351 | 351 | 351 | 8 | -| unstake | 110100 | 110106 | 110106 | 110112 | 2 | -| updateRewardIndex | 23374 | 45581 | 39585 | 73785 | 3 | +| accountedRewards | 350 | 600 | 350 | 2350 | 8 | +| getUserInfo | 789 | 789 | 789 | 789 | 12 | +| rewardIndex | 349 | 599 | 349 | 2349 | 8 | +| stake | 85202 | 100705 | 105102 | 111812 | 3 | +| totalStaked | 350 | 350 | 350 | 350 | 8 | +| unstake | 110056 | 110062 | 110062 | 110068 | 2 | +| updateRewardIndex | 23372 | 45573 | 39574 | 73774 | 3 | -| src/RewardsStreamerMP.sol:RewardsStreamerMP contract | | | | | | -|------------------------------------------------------|-----------------|-------|--------|-------|---------| -| Deployment Cost | Deployment Size | | | | | -| 1425736 | 6528 | | | | | -| Function Name | min | avg | median | max | # calls | -| MAX_LOCKUP_PERIOD | 228 | 228 | 228 | 228 | 23 | -| MAX_MULTIPLIER | 274 | 274 | 274 | 274 | 30 | -| MIN_LOCKUP_PERIOD | 275 | 275 | 275 | 275 | 11 | -| MP_RATE_PER_YEAR | 231 | 231 | 231 | 231 | 3 | -| SCALE_FACTOR | 295 | 295 | 295 | 295 | 41 | -| STAKING_TOKEN | 273 | 273 | 273 | 273 | 174 | -| accountedRewards | 351 | 906 | 351 | 2351 | 72 | -| emergencyModeEnabled | 2377 | 2377 | 2377 | 2377 | 7 | -| enableEmergencyMode | 23504 | 40411 | 45696 | 45696 | 8 | -| getAccount | 1596 | 1596 | 1596 | 1596 | 71 | -| getStakedBalance | 2579 | 2579 | 2579 | 2579 | 1 | -| isTrustedCodehash | 496 | 1013 | 496 | 2496 | 174 | -| rewardIndex | 373 | 400 | 373 | 2373 | 72 | -| setTrustedCodehash | 47926 | 47926 | 47926 | 47926 | 45 | -| totalMP | 330 | 330 | 330 | 330 | 75 | -| totalMaxMP | 351 | 351 | 351 | 351 | 75 | -| totalStaked | 330 | 330 | 330 | 330 | 76 | -| updateAccountMP | 36758 | 38996 | 39260 | 39260 | 19 | -| updateGlobalState | 32134 | 60366 | 49513 | 82460 | 28 | +| src/RewardsStreamerMP.sol:RewardsStreamerMP contract | | | | | | +|------------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 2084418 | 9588 | | | | | +| Function Name | min | avg | median | max | # calls | +| MAX_LOCKUP_PERIOD | 272 | 272 | 272 | 272 | 23 | +| MAX_MULTIPLIER | 295 | 295 | 295 | 295 | 30 | +| MIN_LOCKUP_PERIOD | 274 | 274 | 274 | 274 | 11 | +| MP_RATE_PER_YEAR | 230 | 230 | 230 | 230 | 3 | +| SCALE_FACTOR | 228 | 228 | 228 | 228 | 41 | +| STAKING_TOKEN | 2381 | 2381 | 2381 | 2381 | 182 | +| accountedRewards | 350 | 917 | 350 | 2350 | 74 | +| emergencyModeEnabled | 2398 | 2398 | 2398 | 2398 | 7 | +| enableEmergencyMode | 2482 | 19389 | 24674 | 24674 | 8 | +| getAccount | 1621 | 1621 | 1621 | 1621 | 71 | +| getStakedBalance | 2589 | 2589 | 2589 | 2589 | 1 | +| initialize | 137795 | 137795 | 137795 | 137795 | 47 | +| isTrustedCodehash | 563 | 1079 | 563 | 2563 | 182 | +| lock | 9839 | 33584 | 14168 | 76747 | 3 | +| proxiableUUID | 297 | 297 | 297 | 297 | 1 | +| rewardIndex | 394 | 421 | 394 | 2394 | 74 | +| setTrustedCodehash | 26203 | 26203 | 26203 | 26203 | 47 | +| stake | 134612 | 171004 | 176214 | 196693 | 57 | +| totalMP | 351 | 351 | 351 | 351 | 77 | +| totalMaxMP | 350 | 350 | 350 | 350 | 77 | +| totalStaked | 351 | 351 | 351 | 351 | 78 | +| unstake | 63925 | 84721 | 63925 | 120391 | 13 | +| updateAccountMP | 15375 | 17613 | 17877 | 17877 | 19 | +| updateGlobalState | 11066 | 41310 | 30530 | 63481 | 28 | +| upgradeToAndCall | 3146 | 6955 | 6955 | 10765 | 2 | | src/StakeVault.sol:StakeVault contract | | | | | | |----------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 1095864 | 5202 | | | | | +| 1086958 | 5110 | | | | | | Function Name | min | avg | median | max | # calls | -| STAKING_TOKEN | 217 | 217 | 217 | 217 | 1 | -| emergencyExit | 31410 | 43924 | 43160 | 60260 | 7 | -| lock | 38362 | 60516 | 42741 | 100445 | 3 | -| stake | 199213 | 237055 | 242906 | 263435 | 56 | -| unstake | 84988 | 113999 | 103434 | 146128 | 13 | -| withdraw | 37318 | 37318 | 37318 | 37318 | 1 | +| STAKING_TOKEN | 216 | 216 | 216 | 216 | 1 | +| emergencyExit | 36298 | 48802 | 48036 | 65136 | 7 | +| lock | 43256 | 66100 | 47633 | 107411 | 3 | +| stake | 206077 | 242480 | 247679 | 268206 | 57 | +| unstake | 90514 | 120704 | 110342 | 153215 | 13 | +| withdraw | 42194 | 42194 | 42194 | 42194 | 1 | | src/XPNFTToken.sol:XPNFTToken contract | | | | | | |-------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 737164 | 3407 | | | | | +| 721357 | 3311 | | | | | | Function Name | min | avg | median | max | # calls | -| approve | 393 | 393 | 393 | 393 | 1 | -| getApproved | 296 | 296 | 296 | 296 | 1 | -| isApprovedForAll | 510 | 510 | 510 | 510 | 1 | -| metadataGenerator | 325 | 325 | 325 | 325 | 1 | -| safeTransferFrom(address,address,uint256) | 485 | 485 | 485 | 485 | 1 | -| safeTransferFrom(address,address,uint256,bytes) | 677 | 677 | 677 | 677 | 1 | -| setApprovalForAll | 475 | 475 | 475 | 475 | 1 | -| setMetadataGenerator | 23996 | 26489 | 26489 | 28983 | 2 | -| tokenURI | 76129 | 76129 | 76129 | 76129 | 1 | -| transferFrom | 530 | 530 | 530 | 530 | 1 | +| approve | 391 | 391 | 391 | 391 | 1 | +| getApproved | 293 | 293 | 293 | 293 | 1 | +| isApprovedForAll | 507 | 507 | 507 | 507 | 1 | +| metadataGenerator | 324 | 324 | 324 | 324 | 1 | +| safeTransferFrom(address,address,uint256) | 482 | 482 | 482 | 482 | 1 | +| safeTransferFrom(address,address,uint256,bytes) | 673 | 673 | 673 | 673 | 1 | +| setApprovalForAll | 473 | 473 | 473 | 473 | 1 | +| setMetadataGenerator | 23993 | 26486 | 26486 | 28980 | 2 | +| tokenURI | 71322 | 71322 | 71322 | 71322 | 1 | +| transferFrom | 527 | 527 | 527 | 527 | 1 | | src/XPToken.sol:XPToken contract | | | | | | |----------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 1061807 | 4968 | | | | | +| 1041788 | 4854 | | | | | | Function Name | min | avg | median | max | # calls | -| acceptOwnership | 28284 | 28284 | 28284 | 28284 | 1 | -| addXPProvider | 23968 | 57630 | 51091 | 68191 | 44 | -| allowance | 533 | 533 | 533 | 533 | 2 | -| approve | 413 | 413 | 413 | 413 | 2 | -| balanceOf | 3618 | 11284 | 9618 | 20618 | 6 | -| getXPProviders | 1032 | 3285 | 3285 | 5538 | 4 | -| mint | 24246 | 75896 | 91049 | 91061 | 14 | -| mintAllowance | 5626 | 5662 | 5662 | 5699 | 2 | -| owner | 374 | 1040 | 374 | 2374 | 3 | -| removeXPProvider | 23688 | 28097 | 25803 | 34800 | 6 | -| totalSupply | 2963 | 4963 | 2963 | 10963 | 8 | -| transfer | 411 | 411 | 411 | 411 | 2 | -| transferFrom | 499 | 499 | 499 | 499 | 2 | -| transferOwnership | 47732 | 47732 | 47732 | 47732 | 1 | +| acceptOwnership | 28282 | 28282 | 28282 | 28282 | 1 | +| addXPProvider | 23966 | 57627 | 51088 | 68188 | 44 | +| allowance | 530 | 530 | 530 | 530 | 2 | +| approve | 410 | 410 | 410 | 410 | 2 | +| balanceOf | 3601 | 11267 | 9601 | 20601 | 6 | +| getXPProviders | 1028 | 3281 | 3281 | 5534 | 4 | +| mint | 24244 | 75876 | 91025 | 91037 | 14 | +| mintAllowance | 5604 | 5641 | 5641 | 5678 | 2 | +| owner | 373 | 1039 | 373 | 2373 | 3 | +| removeXPProvider | 23686 | 28093 | 25801 | 34793 | 6 | +| totalSupply | 2952 | 4952 | 2952 | 10952 | 8 | +| transfer | 408 | 408 | 408 | 408 | 2 | +| transferFrom | 495 | 495 | 495 | 495 | 2 | +| transferOwnership | 47730 | 47730 | 47730 | 47730 | 1 | | src/nft-metadata-generators/NFTMetadataGeneratorSVG.sol:NFTMetadataGeneratorSVG contract | | | | | | |------------------------------------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 1196494 | 5995 | | | | | +| 1146955 | 5713 | | | | | | Function Name | min | avg | median | max | # calls | -| generate | 54832 | 54832 | 54832 | 54832 | 1 | -| imagePrefix | 1183 | 2183 | 2183 | 3183 | 2 | -| imageSuffix | 1227 | 2227 | 2227 | 3227 | 2 | -| setImageStrings | 25255 | 30614 | 30614 | 35974 | 2 | +| generate | 51746 | 51746 | 51746 | 51746 | 1 | +| imagePrefix | 1074 | 2074 | 2074 | 3074 | 2 | +| imageSuffix | 1118 | 2118 | 2118 | 3118 | 2 | +| setImageStrings | 25248 | 30606 | 30606 | 35965 | 2 | | src/nft-metadata-generators/NFTMetadataGeneratorURL.sol:NFTMetadataGeneratorURL contract | | | | | | |------------------------------------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 1165211 | 5808 | | | | | +| 1120404 | 5550 | | | | | | Function Name | min | avg | median | max | # calls | -| generate | 64528 | 64528 | 64528 | 64528 | 1 | -| setURLStrings | 25423 | 30782 | 30782 | 36142 | 2 | -| urlPrefix | 1184 | 1184 | 1184 | 1184 | 1 | -| urlSuffix | 1250 | 1250 | 1250 | 1250 | 1 | +| generate | 61198 | 61198 | 61198 | 61198 | 1 | +| setURLStrings | 25416 | 30774 | 30774 | 36133 | 2 | +| urlPrefix | 1075 | 1075 | 1075 | 1075 | 1 | +| urlSuffix | 1141 | 1141 | 1141 | 1141 | 1 | | test/mocks/MockMetadataGenerator.sol:MockMetadataGenerator contract | | | | | | |---------------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 940871 | 4711 | | | | | +| 896351 | 4458 | | | | | | Function Name | min | avg | median | max | # calls | -| generate | 60984 | 60984 | 60984 | 60984 | 1 | +| generate | 57807 | 57807 | 57807 | 57807 | 1 | | test/mocks/MockToken.sol:MockToken contract | | | | | | |---------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 639406 | 3369 | | | | | +| 625454 | 3260 | | | | | | Function Name | min | avg | median | max | # calls | -| approve | 46334 | 46343 | 46346 | 46346 | 222 | -| balanceOf | 561 | 1382 | 561 | 2561 | 336 | -| mint | 51284 | 58898 | 51284 | 68384 | 238 | -| transfer | 34390 | 48859 | 51490 | 51490 | 13 | +| approve | 46330 | 46339 | 46342 | 46342 | 232 | +| balanceOf | 558 | 1380 | 558 | 2558 | 338 | +| mint | 51279 | 58862 | 51279 | 68379 | 248 | +| transfer | 34384 | 48853 | 51484 | 51484 | 13 | | test/mocks/XPProviderMock.sol:XPProviderMock contract | | | | | | |-------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 152145 | 489 | | | | | +| 149299 | 472 | | | | | | Function Name | min | avg | median | max | # calls | -| getTotalXPShares | 302 | 968 | 302 | 2302 | 72 | -| getUserXPShare | 504 | 1837 | 2504 | 2504 | 12 | -| setTotalXPShares | 43632 | 43632 | 43632 | 43632 | 20 | -| setUserXPShare | 44128 | 44128 | 44128 | 44128 | 4 | +| getTotalXPShares | 301 | 967 | 301 | 2301 | 72 | +| getUserXPShare | 501 | 1834 | 2501 | 2501 | 12 | +| setTotalXPShares | 43630 | 43630 | 43630 | 43630 | 20 | +| setUserXPShare | 44125 | 44125 | 44125 | 44125 | 4 | diff --git a/.gas-snapshot b/.gas-snapshot index 6cd0596..c18236b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,84 +1,86 @@ -EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 79829) -EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 283234) -EmergencyExitTest:test_EmergencyExitBasic() (gas: 379874) -EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 788714) -EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 517455) -EmergencyExitTest:test_EmergencyExitWithLock() (gas: 370304) -EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 507361) -EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 34607) -IntegrationTest:testStakeFoo() (gas: 1490300) -LockTest:test_LockFailsWithInvalidPeriod() (gas: 290178) -LockTest:test_LockFailsWithNoStake() (gas: 53357) -LockTest:test_LockWithoutPriorLock() (gas: 383272) -NFTMetadataGeneratorSVGTest:testGenerateMetadata() (gas: 92874) -NFTMetadataGeneratorSVGTest:testSetImageStrings() (gas: 60081) -NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35818) -NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 109345) -NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 50653) -NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 35993) -RewardsStreamerTest:testStake() (gas: 869874) -StakeTest:test_StakeMultipleAccounts() (gas: 493279) -StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 640763) -StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 818252) -StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 499381) -StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 520783) -StakeTest:test_StakeOneAccount() (gas: 284277) -StakeTest:test_StakeOneAccountAndRewards() (gas: 431756) -StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 498901) -StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 494078) -StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 298175) -StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 298187) -StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 298298) -StakingTokenTest:testStakeToken() (gas: 10426) -UnstakeTest:test_StakeMultipleAccounts() (gas: 493323) -UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 640807) -UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 818251) -UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 499380) -UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 520827) -UnstakeTest:test_StakeOneAccount() (gas: 284300) -UnstakeTest:test_StakeOneAccountAndRewards() (gas: 431800) -UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 498945) -UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 494080) -UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 298132) -UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 298187) -UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 298298) -UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 508511) -UnstakeTest:test_UnstakeMultipleAccounts() (gas: 688755) -UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 1014239) -UnstakeTest:test_UnstakeOneAccount() (gas: 480152) -UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 496638) -UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 585964) -UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 518574) -WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 295608) -XPNFTTokenTest:testApproveNotAllowed() (gas: 10507) -XPNFTTokenTest:testGetApproved() (gas: 10531) -XPNFTTokenTest:testIsApprovedForAll() (gas: 10705) -XPNFTTokenTest:testSafeTransferNotAllowed() (gas: 10688) -XPNFTTokenTest:testSafeTransferWithDataNotAllowed() (gas: 10906) -XPNFTTokenTest:testSetApprovalForAllNotAllowed() (gas: 8474) -XPNFTTokenTest:testSetMetadataGenerator() (gas: 1014075) -XPNFTTokenTest:testSetMetadataGeneratorRevert() (gas: 1010606) -XPNFTTokenTest:testTokenURI() (gas: 111913) -XPNFTTokenTest:testTransferNotAllowed() (gas: 10723) -XPTokenMintAllowanceTest:testAddXPProviderOnlyOwner() (gas: 285721) -XPTokenMintAllowanceTest:testBalanceOf() (gas: 294561) -XPTokenMintAllowanceTest:testBalanceOfWithNoSystemTotalXP() (gas: 43428) -XPTokenMintAllowanceTest:testMintAllowance_Available() (gas: 205169) -XPTokenMintAllowanceTest:testMintAllowance_NotAvailable() (gas: 205107) -XPTokenMintAllowanceTest:testMintOnlyOwner() (gas: 241956) -XPTokenMintAllowanceTest:testMint_Ok() (gas: 264233) -XPTokenMintAllowanceTest:testMint_RevertWithAllowanceExceeded() (gas: 246656) -XPTokenMintAllowanceTest:testRemoveXPProviderIndexOutOfBounds() (gas: 36295) -XPTokenMintAllowanceTest:testRemoveXPProviderOnlyOwner() (gas: 72176) -XPTokenMintAllowanceTest:testTotalSupply() (gas: 202454) -XPTokenMintAllowanceTest:testTransfersNotAllowed() (gas: 20658) -XPTokenOwnershipTest:testInitialOwner() (gas: 12649) -XPTokenOwnershipTest:testOwnershipTransfer() (gas: 87271) -XPTokenTest:testAddXPProviderOnlyOwner() (gas: 285753) -XPTokenTest:testBalanceOf() (gas: 294565) -XPTokenTest:testBalanceOfWithNoSystemTotalXP() (gas: 43405) -XPTokenTest:testMintOnlyOwner() (gas: 241932) -XPTokenTest:testRemoveXPProviderIndexOutOfBounds() (gas: 36277) -XPTokenTest:testRemoveXPProviderOnlyOwner() (gas: 72141) -XPTokenTest:testTotalSupply() (gas: 202403) -XPTokenTest:testTransfersNotAllowed() (gas: 20702) \ No newline at end of file +EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 89646) +EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 292881) +EmergencyExitTest:test_EmergencyExitBasic() (gas: 395173) +EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 823179) +EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 538257) +EmergencyExitTest:test_EmergencyExitWithLock() (gas: 384549) +EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 529680) +EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39344) +IntegrationTest:testStakeFoo() (gas: 1559037) +LockTest:test_LockFailsWithInvalidPeriod() (gas: 299830) +LockTest:test_LockFailsWithNoStake() (gas: 58241) +LockTest:test_LockWithoutPriorLock() (gas: 396542) +NFTMetadataGeneratorSVGTest:testGenerateMetadata() (gas: 85934) +NFTMetadataGeneratorSVGTest:testSetImageStrings() (gas: 58332) +NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35804) +NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 102512) +NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 49555) +NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 35979) +RewardsStreamerTest:testStake() (gas: 869181) +StakeTest:test_StakeMultipleAccounts() (gas: 506030) +StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 661839) +StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 872090) +StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 520069) +StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 541881) +StakeTest:test_StakeOneAccount() (gas: 290297) +StakeTest:test_StakeOneAccountAndRewards() (gas: 446101) +StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 536165) +StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 532401) +StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 310643) +StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 310610) +StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 310721) +StakingTokenTest:testStakeToken() (gas: 10422) +UnstakeTest:test_StakeMultipleAccounts() (gas: 506074) +UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 661883) +UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 872089) +UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 520068) +UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 541925) +UnstakeTest:test_StakeOneAccount() (gas: 290320) +UnstakeTest:test_StakeOneAccountAndRewards() (gas: 446145) +UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 536209) +UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 532403) +UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 310600) +UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 310610) +UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 310721) +UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 541859) +UnstakeTest:test_UnstakeMultipleAccounts() (gas: 714931) +UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 1057383) +UnstakeTest:test_UnstakeOneAccount() (gas: 499845) +UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 524183) +UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 608646) +UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 555708) +UpgradeTest:test_RevertWhenNotOwner() (gas: 2159991) +UpgradeTest:test_UpgradeStakeManager() (gas: 2445373) +WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 305631) +XPNFTTokenTest:testApproveNotAllowed() (gas: 10500) +XPNFTTokenTest:testGetApproved() (gas: 10523) +XPNFTTokenTest:testIsApprovedForAll() (gas: 10698) +XPNFTTokenTest:testSafeTransferNotAllowed() (gas: 10680) +XPNFTTokenTest:testSafeTransferWithDataNotAllowed() (gas: 10897) +XPNFTTokenTest:testSetApprovalForAllNotAllowed() (gas: 8467) +XPNFTTokenTest:testSetMetadataGenerator() (gas: 969770) +XPNFTTokenTest:testSetMetadataGeneratorRevert() (gas: 966301) +XPNFTTokenTest:testTokenURI() (gas: 103894) +XPNFTTokenTest:testTransferNotAllowed() (gas: 10715) +XPTokenMintAllowanceTest:testAddXPProviderOnlyOwner() (gas: 282868) +XPTokenMintAllowanceTest:testBalanceOf() (gas: 294494) +XPTokenMintAllowanceTest:testBalanceOfWithNoSystemTotalXP() (gas: 43385) +XPTokenMintAllowanceTest:testMintAllowance_Available() (gas: 205108) +XPTokenMintAllowanceTest:testMintAllowance_NotAvailable() (gas: 205044) +XPTokenMintAllowanceTest:testMintOnlyOwner() (gas: 241885) +XPTokenMintAllowanceTest:testMint_Ok() (gas: 264142) +XPTokenMintAllowanceTest:testMint_RevertWithAllowanceExceeded() (gas: 246592) +XPTokenMintAllowanceTest:testRemoveXPProviderIndexOutOfBounds() (gas: 36286) +XPTokenMintAllowanceTest:testRemoveXPProviderOnlyOwner() (gas: 72143) +XPTokenMintAllowanceTest:testTotalSupply() (gas: 202403) +XPTokenMintAllowanceTest:testTransfersNotAllowed() (gas: 20631) +XPTokenOwnershipTest:testInitialOwner() (gas: 12645) +XPTokenOwnershipTest:testOwnershipTransfer() (gas: 87252) +XPTokenTest:testAddXPProviderOnlyOwner() (gas: 282900) +XPTokenTest:testBalanceOf() (gas: 294498) +XPTokenTest:testBalanceOfWithNoSystemTotalXP() (gas: 43362) +XPTokenTest:testMintOnlyOwner() (gas: 241861) +XPTokenTest:testRemoveXPProviderIndexOutOfBounds() (gas: 36268) +XPTokenTest:testRemoveXPProviderOnlyOwner() (gas: 72108) +XPTokenTest:testTotalSupply() (gas: 202352) +XPTokenTest:testTransfersNotAllowed() (gas: 20675) \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73aff50..5171246 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,7 +162,8 @@ jobs: run: "pnpm install" - name: Verify rules - run: "pnpm verify" + run: | + pnpm ${{matrix.rule}} env: CERTORAKEY: ${{ secrets.CERTORAKEY }} diff --git a/.gitmodules b/.gitmodules index 690924b..9296efd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/certora/confs/EmergencyMode.conf b/certora/confs/EmergencyMode.conf index 2e8171b..8b48396 100644 --- a/certora/confs/EmergencyMode.conf +++ b/certora/confs/EmergencyMode.conf @@ -15,7 +15,8 @@ "loop_iter": "3", "packages": [ "forge-std=lib/forge-std/src", - "@openzeppelin=lib/openzeppelin-contracts" + "@openzeppelin=lib/openzeppelin-contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts" ] } diff --git a/certora/confs/RewardsStreamerMP.conf b/certora/confs/RewardsStreamerMP.conf index 15a06b5..afbef15 100644 --- a/certora/confs/RewardsStreamerMP.conf +++ b/certora/confs/RewardsStreamerMP.conf @@ -14,6 +14,7 @@ "loop_iter": "3", "packages": [ "forge-std=lib/forge-std/src", - "@openzeppelin=lib/openzeppelin-contracts" + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts" ] } diff --git a/certora/specs/EmergencyMode.spec b/certora/specs/EmergencyMode.spec index 1de2d50..4254ddb 100644 --- a/certora/specs/EmergencyMode.spec +++ b/certora/specs/EmergencyMode.spec @@ -38,6 +38,17 @@ definition isTrustedCodehashAccessFunction(method f) returns bool = ( f.selector == sig:streamer.isTrustedCodehash(bytes32).selector ); +definition isInitializerFunction(method f) returns bool = ( + f.selector == sig:streamer.initialize(address,address,address).selector +); + +definition isUUPSUpgradeableFunction(method f) returns bool = ( + f.selector == sig:streamer.proxiableUUID().selector || + f.selector == sig:streamer.UPGRADE_INTERFACE_VERSION().selector || + f.selector == sig:streamer.upgradeToAndCall(address, bytes).selector || + f.selector == sig:streamer.__TrustedCodehashAccess_init(address).selector +); + rule accountCanOnlyLeaveInEmergencyMode(method f) { env e; calldataarg args; @@ -49,6 +60,8 @@ rule accountCanOnlyLeaveInEmergencyMode(method f) { assert !isReverted => isViewFunction(f) || isOwnableFunction(f) || - isTrustedCodehashAccessFunction(f); + isTrustedCodehashAccessFunction(f) || + isInitializerFunction(f) || + isUUPSUpgradeableFunction(f); } diff --git a/certora/specs/RewardsStreamerMP.spec b/certora/specs/RewardsStreamerMP.spec index 90d105c..ff19673 100644 --- a/certora/specs/RewardsStreamerMP.spec +++ b/certora/specs/RewardsStreamerMP.spec @@ -41,13 +41,22 @@ function getAccountLockUntil(address account) returns uint256 { } invariant sumOfBalancesIsTotalStaked() - sumOfBalances == to_mathint(totalStaked()); + sumOfBalances == to_mathint(totalStaked()) + filtered { + f -> f.selector != sig:upgradeToAndCall(address,bytes).selector + } invariant accountMPLessEqualAccountMaxMP(address account) - to_mathint(getAccountMP(account)) <= to_mathint(getAccountMaxMP(account)); + to_mathint(getAccountMP(account)) <= to_mathint(getAccountMaxMP(account)) + filtered { + f -> f.selector != sig:upgradeToAndCall(address,bytes).selector + } invariant accountMPGreaterEqualAccountStakedBalance(address account) - to_mathint(getAccountMP(account)) >= to_mathint(getAccountStakedBalance(account)); + to_mathint(getAccountMP(account)) >= to_mathint(getAccountStakedBalance(account)) + filtered { + f -> f.selector != sig:upgradeToAndCall(address,bytes).selector + } rule stakingMintsMultiplierPoints1To1Ratio { diff --git a/foundry.toml b/foundry.toml index a7d437b..eb4a0d8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT bytecode_hash = "none" cbor_metadata = false - evm_version = "paris" + evm_version = "cancun" fuzz = { runs = 1_000 } gas_reports = ["*"] libs = ["lib"] @@ -16,7 +16,6 @@ solc = "0.8.26" src = "src" test = "test" - [profile.ci] fuzz = { runs = 10_000 } verbosity = 4 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..fa52531 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit fa525310e45f91eb20a6d3baa2644be8e0adba31 diff --git a/remappings.txt b/remappings.txt index 605286b..26a4078 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,2 +1,3 @@ forge-std/=lib/forge-std/src/ -@openzeppelin/contracts=./lib/openzeppelin-contracts/contracts +@openzeppelin/contracts=lib/openzeppelin-contracts/contracts +@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts \ No newline at end of file diff --git a/slither.config.json b/slither.config.json index 9a2fba9..5f260c0 100644 --- a/slither.config.json +++ b/slither.config.json @@ -2,6 +2,7 @@ "detectors_to_exclude": "naming-convention,reentrancy-events,solc-version,timestamp", "filter_paths": "(lib|test)", "solc_remaps": [ + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts/", "forge-std/=lib/forge-std/src/" ] diff --git a/src/RewardsStreamerMP.sol b/src/RewardsStreamerMP.sol index c5acb81..71b4497 100644 --- a/src/RewardsStreamerMP.sol +++ b/src/RewardsStreamerMP.sol @@ -2,12 +2,14 @@ pragma solidity ^0.8.26; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { IStakeManager } from "./interfaces/IStakeManager.sol"; import { TrustedCodehashAccess } from "./TrustedCodehashAccess.sol"; // Rewards Streamer with Multiplier Points -contract RewardsStreamerMP is IStakeManager, TrustedCodehashAccess, ReentrancyGuard { +contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAccess, ReentrancyGuardUpgradeable { error StakingManager__AmountCannotBeZero(); error StakingManager__TransferFailed(); error StakingManager__InsufficientBalance(); @@ -17,8 +19,8 @@ contract RewardsStreamerMP is IStakeManager, TrustedCodehashAccess, ReentrancyGu error StakingManager__AlreadyLocked(); error StakingManager__EmergencyModeEnabled(); - IERC20 public immutable STAKING_TOKEN; - IERC20 public immutable REWARD_TOKEN; + IERC20 public STAKING_TOKEN; + IERC20 public REWARD_TOKEN; uint256 public constant SCALE_FACTOR = 1e18; uint256 public constant MP_RATE_PER_YEAR = 1e18; @@ -53,12 +55,24 @@ contract RewardsStreamerMP is IStakeManager, TrustedCodehashAccess, ReentrancyGu _; } - constructor(address _owner, address _stakingToken, address _rewardToken) TrustedCodehashAccess(_owner) { + constructor() { + _disableInitializers(); + } + + function initialize(address _owner, address _stakingToken, address _rewardToken) public initializer { + __TrustedCodehashAccess_init(_owner); + __UUPSUpgradeable_init(); + __ReentrancyGuard_init(); + STAKING_TOKEN = IERC20(_stakingToken); REWARD_TOKEN = IERC20(_rewardToken); lastMPUpdatedTime = block.timestamp; } + function _authorizeUpgrade(address) internal view override { + _checkOwner(); + } + function stake(uint256 amount, uint256 lockPeriod) external onlyTrustedCodehash onlyNotEmergencyMode nonReentrant { if (amount == 0) { revert StakingManager__AmountCannotBeZero(); diff --git a/src/TrustedCodehashAccess.sol b/src/TrustedCodehashAccess.sol index d1579a4..2fe4eca 100644 --- a/src/TrustedCodehashAccess.sol +++ b/src/TrustedCodehashAccess.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { ITrustedCodehashAccess } from "./interfaces/ITrustedCodehashAccess.sol"; /** * @title TrustedCodehashAccess @@ -10,7 +10,7 @@ import { ITrustedCodehashAccess } from "./interfaces/ITrustedCodehashAccess.sol" * interact with the functions using the `onlyTrustedCodehash` modifier. */ -contract TrustedCodehashAccess is ITrustedCodehashAccess, Ownable { +abstract contract TrustedCodehashAccess is ITrustedCodehashAccess, OwnableUpgradeable { mapping(bytes32 codehash => bool permission) private trustedCodehashes; /** @@ -25,7 +25,9 @@ contract TrustedCodehashAccess is ITrustedCodehashAccess, Ownable { _; } - constructor(address _owner) Ownable(_owner) { } + function __TrustedCodehashAccess_init(address _initialOwner) public onlyInitializing { + __Ownable_init(_initialOwner); + } /** * @notice Allows the owner to set or update the trust status for a contract's codehash. diff --git a/test/RewardsStreamerMP.t.sol b/test/RewardsStreamerMP.t.sol index 5800f27..584eafd 100644 --- a/test/RewardsStreamerMP.t.sol +++ b/test/RewardsStreamerMP.t.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.26; import { Test } from "forge-std/Test.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; import { StakeVault } from "../src/StakeVault.sol"; import { MockToken } from "./mocks/MockToken.sol"; @@ -23,7 +25,12 @@ contract RewardsStreamerMPTest is Test { function setUp() public virtual { rewardToken = new MockToken("Reward Token", "RT"); stakingToken = new MockToken("Staking Token", "ST"); - streamer = new RewardsStreamerMP(address(this), address(stakingToken), address(rewardToken)); + + bytes memory initializeData = + abi.encodeCall(RewardsStreamerMP.initialize, (address(this), address(stakingToken), address(rewardToken))); + address impl = address(new RewardsStreamerMP()); + address proxy = address(new ERC1967Proxy(impl, initializeData)); + streamer = RewardsStreamerMP(proxy); address[4] memory accounts = [alice, bob, charlie, dave]; for (uint256 i = 0; i < accounts.length; i++) { @@ -1779,3 +1786,57 @@ contract EmergencyExitTest is RewardsStreamerMPTest { ); } } + +contract UpgradeTest is RewardsStreamerMPTest { + function _upgradeStakeManager() internal { + address newImpl = address(new RewardsStreamerMP()); + bytes memory initializeData; + UUPSUpgradeable(streamer).upgradeToAndCall(newImpl, initializeData); + } + + function setUp() public override { + super.setUp(); + } + + function test_RevertWhenNotOwner() public { + address newImpl = address(new RewardsStreamerMP()); + bytes memory initializeData; + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice)); + UUPSUpgradeable(streamer).upgradeToAndCall(newImpl, initializeData); + } + + function test_UpgradeStakeManager() public { + // first, change state of existing stake manager + _stake(alice, 10e18, 0); + + // check initial state + checkStreamer( + CheckStreamerParams({ + totalStaked: 10e18, + totalMP: 10e18, + totalMaxMP: 50e18, + stakingBalance: 10e18, + rewardBalance: 0, + rewardIndex: 0, + accountedRewards: 0 + }) + ); + + // next, upgrade the stake manager + _upgradeStakeManager(); + + // ensure state is available in upgraded contract + checkStreamer( + CheckStreamerParams({ + totalStaked: 10e18, + totalMP: 10e18, + totalMaxMP: 50e18, + stakingBalance: 10e18, + rewardBalance: 0, + rewardIndex: 0, + accountedRewards: 0 + }) + ); + } +} diff --git a/test/StakeVault.test.sol b/test/StakeVault.test.sol index 6d21020..d5d44a6 100644 --- a/test/StakeVault.test.sol +++ b/test/StakeVault.test.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.26; import { Test } from "forge-std/Test.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; import { StakeVault } from "../src/StakeVault.sol"; @@ -30,7 +31,12 @@ contract StakeVaultTest is Test { function setUp() public virtual { rewardToken = new MockToken("Reward Token", "RT"); stakingToken = new MockToken("Staking Token", "ST"); - streamer = new RewardsStreamerMP(address(this), address(stakingToken), address(rewardToken)); + address impl = address(new RewardsStreamerMP()); + bytes memory initializeData = abi.encodeWithSelector( + RewardsStreamerMP.initialize.selector, address(this), address(stakingToken), address(rewardToken) + ); + address proxy = address(new ERC1967Proxy(impl, initializeData)); + streamer = RewardsStreamerMP(proxy); stakingToken.mint(alice, 10_000e18); stakeVault = _createTestVault(alice);