mirror of
https://github.com/vacp2p/staking-reward-streamer.git
synced 2026-01-07 22:43:53 -05:00
feat(Karma): add ability to slash Karma
This commit introduces the ability for accounts with the necessary privileges to slash other accounts. The amount to be slashed is controlled via the `slashPercentage`. The amount to be slashed will be calculated from the account's current Karma balance, which is the total Karma across all distributors, minus the known `slashAmount` for that account. Under the hood, it calculates the slash amount for each item (distributor or internal balance). This ensure we reduce the slash amount correctly, if a reward distributor is removed. **Example**: For example, if the account has 100 Karma and hasn't been slashed before, the account's balances would look like this: ``` rawBalance: 100 slashAmount: 0 balance: 100 ``` Therefore, `balanceOf(account)` will return `100`. If slashing burns 10% of the account's balance, then, after calling `slash(account)`, the `slashAmount` will be increased accordingly: ``` rawBalance: 100 slashAmount: 10 balance: 90 ``` Notice that `rawBalance` isn't actually a new contract property, but there's a new internal function `_rawBalanceAndSlashAmountOf(account)`, which is used by `balanceOf(account)` to determine the effective balance of an account. **Authorization** In order to slash accounts, the message sender needs to have the newly introduced `SLASHER_ROLE`. Closes #212
This commit is contained in:
116
.gas-report
116
.gas-report
@@ -1,30 +1,30 @@
|
||||
|
||||
╭-------------------------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╮
|
||||
| lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy Contract | | | | | |
|
||||
+==================================================================================================================================================+
|
||||
| Deployment Cost | Deployment Size | | | | |
|
||||
|-------------------------------------------------------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| 0 | 1374 | | | | |
|
||||
|-------------------------------------------------------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| | | | | | |
|
||||
|-------------------------------------------------------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| Function Name | Min | Avg | Median | Max | # Calls |
|
||||
|-------------------------------------------------------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| fallback | 5166 | 113929 | 97112 | 193429 | 693 |
|
||||
╰-------------------------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╯
|
||||
╭-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------╮
|
||||
| lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy Contract | | | | | |
|
||||
+=================================================================================================================================================+
|
||||
| Deployment Cost | Deployment Size | | | | |
|
||||
|-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------|
|
||||
| 0 | 1374 | | | | |
|
||||
|-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------|
|
||||
| | | | | | |
|
||||
|-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------|
|
||||
| Function Name | Min | Avg | Median | Max | # Calls |
|
||||
|-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------|
|
||||
| fallback | 5145 | 65850 | 33119 | 193478 | 3440 |
|
||||
╰-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------╯
|
||||
|
||||
╭-----------------------------------------------------+-----------------+---------+---------+---------+---------╮
|
||||
| script/DeployKarma.s.sol:DeployKarmaScript Contract | | | | | |
|
||||
+===============================================================================================================+
|
||||
| Deployment Cost | Deployment Size | | | | |
|
||||
|-----------------------------------------------------+-----------------+---------+---------+---------+---------|
|
||||
| 5135751 | 24812 | | | | |
|
||||
| 5471828 | 26377 | | | | |
|
||||
|-----------------------------------------------------+-----------------+---------+---------+---------+---------|
|
||||
| | | | | | |
|
||||
|-----------------------------------------------------+-----------------+---------+---------+---------+---------|
|
||||
| Function Name | Min | Avg | Median | Max | # Calls |
|
||||
|-----------------------------------------------------+-----------------+---------+---------+---------+---------|
|
||||
| run | 4330203 | 4330203 | 4330203 | 4330203 | 152 |
|
||||
| run | 4666141 | 4666141 | 4666141 | 4666141 | 176 |
|
||||
╰-----------------------------------------------------+-----------------+---------+---------+---------+---------╯
|
||||
|
||||
╭-----------------------------------------------------------+-----------------+---------+---------+---------+---------╮
|
||||
@@ -66,7 +66,7 @@
|
||||
|---------------------------------------------------------+-----------------+------+--------+------+---------|
|
||||
| Function Name | Min | Avg | Median | Max | # Calls |
|
||||
|---------------------------------------------------------+-----------------+------+--------+------+---------|
|
||||
| activeNetworkConfig | 455 | 1971 | 455 | 4455 | 414 |
|
||||
| activeNetworkConfig | 455 | 2022 | 455 | 4455 | 462 |
|
||||
╰---------------------------------------------------------+-----------------+------+--------+------+---------╯
|
||||
|
||||
╭---------------------------------------------------------------------+-----------------+---------+---------+---------+---------╮
|
||||
@@ -88,43 +88,57 @@
|
||||
+=====================================================================================+
|
||||
| Deployment Cost | Deployment Size | | | | |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| 0 | 11694 | | | | |
|
||||
| 0 | 13259 | | | | |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| | | | | | |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| Function Name | Min | Avg | Median | Max | # Calls |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| DEFAULT_ADMIN_ROLE | 306 | 306 | 306 | 306 | 19 |
|
||||
| DEFAULT_ADMIN_ROLE | 285 | 285 | 285 | 285 | 23 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| OPERATOR_ROLE | 283 | 283 | 283 | 283 | 2 |
|
||||
| MIN_SLASH_AMOUNT | 264 | 264 | 264 | 264 | 1 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| addRewardDistributor | 29894 | 64182 | 70797 | 70797 | 232 |
|
||||
| OPERATOR_ROLE | 262 | 262 | 262 | 262 | 2 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| allowance | 504 | 504 | 504 | 504 | 6 |
|
||||
| SLASHER_ROLE | 262 | 262 | 262 | 262 | 24 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| approve | 441 | 441 | 441 | 441 | 6 |
|
||||
| accountSlashAmount | 2611 | 2611 | 2611 | 2611 | 2 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| balanceOf | 21085 | 21085 | 21085 | 21085 | 18 |
|
||||
| addRewardDistributor | 29975 | 63645 | 70903 | 70903 | 284 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| getRewardDistributors | 5119 | 7759 | 9607 | 9607 | 17 |
|
||||
| allowance | 573 | 573 | 573 | 573 | 8 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| grantRole | 29440 | 29440 | 29440 | 29440 | 5 |
|
||||
| approve | 453 | 453 | 453 | 453 | 8 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| hasRole | 2685 | 2685 | 2685 | 2685 | 4 |
|
||||
| balanceOf | 17795 | 28157 | 28160 | 28233 | 287 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| initialize | 94595 | 94595 | 94595 | 94595 | 152 |
|
||||
| calculateSlashAmount | 2763 | 2801 | 2804 | 2804 | 774 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| mint | 4790 | 38817 | 51239 | 51239 | 26 |
|
||||
| getRewardDistributors | 5132 | 7710 | 9644 | 9644 | 21 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| removeRewardDistributor | 5044 | 22060 | 29223 | 29959 | 21 |
|
||||
| grantRole | 29490 | 29490 | 29490 | 29490 | 29 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| setReward | 4832 | 147872 | 166705 | 166705 | 307 |
|
||||
| hasRole | 2754 | 2754 | 2754 | 2754 | 4 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| totalSupply | 22567 | 22567 | 22567 | 22567 | 18 |
|
||||
| initialize | 116796 | 116796 | 116796 | 116796 | 176 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| transfer | 439 | 439 | 439 | 439 | 6 |
|
||||
| mint | 4869 | 50368 | 51342 | 51342 | 550 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| transferFrom | 511 | 511 | 511 | 511 | 6 |
|
||||
| removeRewardDistributor | 5080 | 22644 | 29995 | 30358 | 28 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| rewardDistributorSlashAmount | 2781 | 2781 | 2781 | 2781 | 1 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| setReward | 4845 | 144102 | 166754 | 166754 | 319 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| slash | 4803 | 103614 | 85757 | 123125 | 519 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| slashedAmountOf | 17682 | 28099 | 28120 | 28120 | 516 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| totalSupply | 22591 | 22591 | 22591 | 22591 | 24 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| transfer | 451 | 451 | 451 | 451 | 8 |
|
||||
|------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| transferFrom | 580 | 580 | 580 | 580 | 8 |
|
||||
╰------------------------------+-----------------+--------+--------+--------+---------╯
|
||||
|
||||
╭-------------------------------------------------+-----------------+-------+--------+-------+---------╮
|
||||
@@ -194,7 +208,7 @@
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| leave | 66348 | 66348 | 66348 | 66348 | 2 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| lock | 7040 | 43591 | 46713 | 87964 | 1034 |
|
||||
| lock | 7040 | 43452 | 46713 | 87964 | 1034 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| migrateToVault | 9294 | 53513 | 17021 | 170715 | 4 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
@@ -222,7 +236,7 @@
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| setTrustedCodehash | 24238 | 24238 | 24238 | 24238 | 95 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| stake | 2639 | 131862 | 60725 | 228623 | 2670 |
|
||||
| stake | 2639 | 131319 | 60725 | 228623 | 2670 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| stakedBalanceOf | 2622 | 2622 | 2622 | 2622 | 1 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
@@ -242,13 +256,13 @@
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| totalStaked | 2408 | 2408 | 2408 | 2408 | 4169 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| unstake | 9886 | 41391 | 39781 | 79550 | 271 |
|
||||
| unstake | 9886 | 41365 | 39781 | 79550 | 271 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| updateAccount | 347677 | 347677 | 347677 | 347677 | 1 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| updateGlobalState | 15820 | 25876 | 29230 | 29230 | 8 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| updateVault | 31948 | 34543 | 31948 | 110579 | 1024 |
|
||||
| updateVault | 31948 | 34373 | 31948 | 110579 | 1024 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| upgradeTo | 10279 | 10772 | 10279 | 12745 | 5 |
|
||||
|--------------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
@@ -276,9 +290,9 @@
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| leave | 12223 | 113137 | 84120 | 356508 | 5 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| lock | 12151 | 59083 | 62251 | 103499 | 1035 |
|
||||
| lock | 12151 | 58945 | 62251 | 103499 | 1035 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| lockUntil | 2363 | 2363 | 2363 | 2363 | 7769 |
|
||||
| lockUntil | 2363 | 2363 | 2363 | 2363 | 7768 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| migrateToVault | 24910 | 77530 | 32637 | 219937 | 4 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
@@ -286,15 +300,15 @@
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| register | 12742 | 78218 | 78761 | 78761 | 374 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| stake | 12131 | 166249 | 76290 | 284275 | 2671 |
|
||||
| stake | 12131 | 165586 | 76290 | 284275 | 2671 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| stakeManager | 393 | 393 | 393 | 393 | 373 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| trustStakeManager | 7650 | 7650 | 7650 | 7650 | 1 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| unstake | 12108 | 58059 | 55296 | 110656 | 272 |
|
||||
| unstake | 12108 | 58033 | 55296 | 110656 | 272 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| updateLockUntil | 4432 | 20722 | 21532 | 21532 | 524 |
|
||||
| updateLockUntil | 4432 | 20797 | 21532 | 21532 | 508 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
| withdraw | 20817 | 20817 | 20817 | 20817 | 1 |
|
||||
|----------------------------------------+-----------------+--------+--------+--------+---------|
|
||||
@@ -312,9 +326,9 @@
|
||||
|----------------------------------------------------+-----------------+-------+--------+--------+---------|
|
||||
| Function Name | Min | Avg | Median | Max | # Calls |
|
||||
|----------------------------------------------------+-----------------+-------+--------+--------+---------|
|
||||
| fallback | 5208 | 12842 | 7353 | 374054 | 23167 |
|
||||
| fallback | 5208 | 12834 | 7353 | 374054 | 23167 |
|
||||
|----------------------------------------------------+-----------------+-------+--------+--------+---------|
|
||||
| implementation | 346 | 2131 | 2346 | 2346 | 4886 |
|
||||
| implementation | 346 | 2137 | 2346 | 2346 | 4870 |
|
||||
╰----------------------------------------------------+-----------------+-------+--------+--------+---------╯
|
||||
|
||||
╭--------------------------------------------+-----------------+--------+--------+--------+---------╮
|
||||
@@ -460,13 +474,13 @@
|
||||
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| Function Name | Min | Avg | Median | Max | # Calls |
|
||||
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| rewardsBalanceOfAccount | 2549 | 2549 | 2549 | 2549 | 36 |
|
||||
| rewardsBalanceOfAccount | 549 | 1986 | 2549 | 2549 | 3675 |
|
||||
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| setTotalKarmaShares | 43589 | 43589 | 43589 | 43589 | 36 |
|
||||
| setTotalKarmaShares | 43589 | 43589 | 43589 | 43589 | 48 |
|
||||
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| setUserKarmaShare | 44194 | 44194 | 44194 | 44194 | 12 |
|
||||
| setUserKarmaShare | 24210 | 44068 | 44134 | 44266 | 530 |
|
||||
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| totalRewardsSupply | 2324 | 2324 | 2324 | 2324 | 36 |
|
||||
| totalRewardsSupply | 2324 | 2324 | 2324 | 2324 | 48 |
|
||||
╰-------------------------------------------------------------------+-----------------+-------+--------+-------+---------╯
|
||||
|
||||
╭---------------------------------------------------------------------+-----------------+-------+--------+-------+---------╮
|
||||
@@ -488,17 +502,17 @@
|
||||
+==================================================================================================+
|
||||
| Deployment Cost | Deployment Size | | | | |
|
||||
|---------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| 770741 | 3987 | | | | |
|
||||
| 770657 | 3987 | | | | |
|
||||
|---------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| | | | | | |
|
||||
|---------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| Function Name | Min | Avg | Median | Max | # Calls |
|
||||
|---------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| approve | 29075 | 31544 | 29183 | 46259 | 2676 |
|
||||
| approve | 29075 | 31545 | 29183 | 46259 | 2676 |
|
||||
|---------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| balanceOf | 2561 | 2561 | 2561 | 2561 | 4960 |
|
||||
|---------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| mint | 33964 | 37189 | 34072 | 68248 | 2685 |
|
||||
| mint | 33964 | 37190 | 34072 | 68248 | 2685 |
|
||||
╰---------------------------------------------+-----------------+-------+--------+-------+---------╯
|
||||
|
||||
╭-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╮
|
||||
|
||||
180
.gas-snapshot
180
.gas-snapshot
@@ -1,13 +1,13 @@
|
||||
AddRewardDistributorTest:testAddKarmaDistributorOnlyAdmin() (gas: 438055)
|
||||
AddRewardDistributorTest:testAddRewardDistributorAsOtherAdmin() (gas: 182763)
|
||||
AddRewardDistributorTest:testBalanceOf() (gas: 449293)
|
||||
AddRewardDistributorTest:testBalanceOfWithNoSystemTotalKarma() (gas: 69655)
|
||||
AddRewardDistributorTest:testMintOnlyAdmin() (gas: 428769)
|
||||
AddRewardDistributorTest:testRemoveKarmaDistributorOnlyOwner() (gas: 162308)
|
||||
AddRewardDistributorTest:testRemoveUnknownKarmaDistributor() (gas: 41630)
|
||||
AddRewardDistributorTest:testTotalSupply() (gas: 359166)
|
||||
AddRewardDistributorTest:testTransfersNotAllowed() (gas: 61785)
|
||||
AddRewardDistributorTest:test_RevertWhen_SenderIsNotDefaultAdmin() (gas: 68325)
|
||||
AddRewardDistributorTest:testAddKarmaDistributorOnlyAdmin() (gas: 438258)
|
||||
AddRewardDistributorTest:testAddRewardDistributorAsOtherAdmin() (gas: 182935)
|
||||
AddRewardDistributorTest:testBalanceOf() (gas: 456642)
|
||||
AddRewardDistributorTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83805)
|
||||
AddRewardDistributorTest:testMintOnlyAdmin() (gas: 429097)
|
||||
AddRewardDistributorTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163471)
|
||||
AddRewardDistributorTest:testRemoveUnknownKarmaDistributor() (gas: 41666)
|
||||
AddRewardDistributorTest:testTotalSupply() (gas: 359391)
|
||||
AddRewardDistributorTest:testTransfersNotAllowed() (gas: 61947)
|
||||
AddRewardDistributorTest:test_RevertWhen_SenderIsNotDefaultAdmin() (gas: 68406)
|
||||
EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 93554)
|
||||
EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 336067)
|
||||
EmergencyExitTest:test_EmergencyExitBasic() (gas: 524580)
|
||||
@@ -16,15 +16,15 @@ EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 479110)
|
||||
EmergencyExitTest:test_EmergencyExitWithLock() (gas: 452444)
|
||||
EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 484810)
|
||||
EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39176)
|
||||
FuzzTests:testFuzz_AccrueMP(uint128,uint64,uint64) (runs: 1009, μ: 586777, ~: 549070)
|
||||
FuzzTests:testFuzz_AccrueMP_Relock(uint128,uint64,uint64,uint64) (runs: 1009, μ: 811994, ~: 777237)
|
||||
FuzzTests:testFuzz_EmergencyExit(uint256,uint256) (runs: 1001, μ: 588323, ~: 578267)
|
||||
FuzzTests:testFuzz_Lock(uint256,uint64) (runs: 1008, μ: 961825, ~: 961235)
|
||||
FuzzTests:testFuzz_Relock(uint256,uint64,uint64) (runs: 1008, μ: 598425, ~: 574225)
|
||||
FuzzTests:testFuzz_Rewards(uint256,uint256,uint256,uint16,uint16) (runs: 1001, μ: 650378, ~: 653205)
|
||||
FuzzTests:testFuzz_Stake(uint256,uint64) (runs: 1008, μ: 375317, ~: 346086)
|
||||
FuzzTests:testFuzz_Unstake(uint128,uint64,uint16,uint128) (runs: 1009, μ: 806735, ~: 780622)
|
||||
FuzzTests:testFuzz_UpdateVault(uint128,uint64,uint64) (runs: 1009, μ: 586800, ~: 549093)
|
||||
FuzzTests:testFuzz_AccrueMP(uint128,uint64,uint64) (runs: 1009, μ: 583242, ~: 549046)
|
||||
FuzzTests:testFuzz_AccrueMP_Relock(uint128,uint64,uint64,uint64) (runs: 1009, μ: 808244, ~: 777237)
|
||||
FuzzTests:testFuzz_EmergencyExit(uint256,uint256) (runs: 1001, μ: 588167, ~: 578267)
|
||||
FuzzTests:testFuzz_Lock(uint256,uint64) (runs: 1008, μ: 961506, ~: 961235)
|
||||
FuzzTests:testFuzz_Relock(uint256,uint64,uint64) (runs: 1008, μ: 600126, ~: 574225)
|
||||
FuzzTests:testFuzz_Rewards(uint256,uint256,uint256,uint16,uint16) (runs: 1001, μ: 650444, ~: 653254)
|
||||
FuzzTests:testFuzz_Stake(uint256,uint64) (runs: 1008, μ: 377931, ~: 346087)
|
||||
FuzzTests:testFuzz_Unstake(uint128,uint64,uint16,uint128) (runs: 1009, μ: 803049, ~: 780598)
|
||||
FuzzTests:testFuzz_UpdateVault(uint128,uint64,uint64) (runs: 1009, μ: 583265, ~: 549069)
|
||||
IntegrationTest:testStakeFoo() (gas: 2348931)
|
||||
KarmaNFTTest:testApproveNotAllowed() (gas: 10507)
|
||||
KarmaNFTTest:testGetApproved() (gas: 10531)
|
||||
@@ -36,29 +36,29 @@ KarmaNFTTest:testSetMetadataGenerator() (gas: 1012377)
|
||||
KarmaNFTTest:testSetMetadataGeneratorRevert() (gas: 1006937)
|
||||
KarmaNFTTest:testTokenURI() (gas: 1112435)
|
||||
KarmaNFTTest:testTransferNotAllowed() (gas: 10701)
|
||||
KarmaOwnershipTest:testAddKarmaDistributorOnlyAdmin() (gas: 438043)
|
||||
KarmaOwnershipTest:testBalanceOf() (gas: 449293)
|
||||
KarmaOwnershipTest:testBalanceOfWithNoSystemTotalKarma() (gas: 69677)
|
||||
KarmaOwnershipTest:testInitialOwner() (gas: 20539)
|
||||
KarmaOwnershipTest:testMintOnlyAdmin() (gas: 428791)
|
||||
KarmaOwnershipTest:testOwnershipTransfer() (gas: 94343)
|
||||
KarmaOwnershipTest:testRemoveKarmaDistributorOnlyOwner() (gas: 162229)
|
||||
KarmaOwnershipTest:testRemoveUnknownKarmaDistributor() (gas: 41618)
|
||||
KarmaOwnershipTest:testTotalSupply() (gas: 359166)
|
||||
KarmaOwnershipTest:testTransfersNotAllowed() (gas: 61785)
|
||||
KarmaTest:testAddKarmaDistributorOnlyAdmin() (gas: 438021)
|
||||
KarmaTest:testBalanceOf() (gas: 449293)
|
||||
KarmaTest:testBalanceOfWithNoSystemTotalKarma() (gas: 69655)
|
||||
KarmaTest:testMintOnlyAdmin() (gas: 428769)
|
||||
KarmaTest:testRemoveKarmaDistributorOnlyOwner() (gas: 162274)
|
||||
KarmaTest:testRemoveUnknownKarmaDistributor() (gas: 41618)
|
||||
KarmaTest:testTotalSupply() (gas: 359166)
|
||||
KarmaTest:testTransfersNotAllowed() (gas: 61763)
|
||||
KarmaOwnershipTest:testAddKarmaDistributorOnlyAdmin() (gas: 438246)
|
||||
KarmaOwnershipTest:testBalanceOf() (gas: 456642)
|
||||
KarmaOwnershipTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83827)
|
||||
KarmaOwnershipTest:testInitialOwner() (gas: 20587)
|
||||
KarmaOwnershipTest:testMintOnlyAdmin() (gas: 429119)
|
||||
KarmaOwnershipTest:testOwnershipTransfer() (gas: 94420)
|
||||
KarmaOwnershipTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163392)
|
||||
KarmaOwnershipTest:testRemoveUnknownKarmaDistributor() (gas: 41654)
|
||||
KarmaOwnershipTest:testTotalSupply() (gas: 359391)
|
||||
KarmaOwnershipTest:testTransfersNotAllowed() (gas: 61947)
|
||||
KarmaTest:testAddKarmaDistributorOnlyAdmin() (gas: 438224)
|
||||
KarmaTest:testBalanceOf() (gas: 456642)
|
||||
KarmaTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83805)
|
||||
KarmaTest:testMintOnlyAdmin() (gas: 429097)
|
||||
KarmaTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163437)
|
||||
KarmaTest:testRemoveUnknownKarmaDistributor() (gas: 41654)
|
||||
KarmaTest:testTotalSupply() (gas: 359391)
|
||||
KarmaTest:testTransfersNotAllowed() (gas: 61925)
|
||||
LeaveTest:test_LeaveShouldKeepFundsLockedInStakeVault() (gas: 9938411)
|
||||
LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 10011059)
|
||||
LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 333238)
|
||||
LeaveTest:test_TrustNewStakeManager() (gas: 9944491)
|
||||
LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1008, μ: 384561, ~: 384588)
|
||||
LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1008, μ: 384560, ~: 384588)
|
||||
LockTest:test_LockFailsWithNoStake() (gas: 89700)
|
||||
LockTest:test_LockFailsWithZero() (gas: 343310)
|
||||
LockTest:test_LockMultipleTimesExceedMaxLock() (gas: 746921)
|
||||
@@ -79,16 +79,16 @@ NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35891)
|
||||
NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 108341)
|
||||
NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 59131)
|
||||
NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 36066)
|
||||
OverflowTest:testAddKarmaDistributorOnlyAdmin() (gas: 438043)
|
||||
OverflowTest:testBalanceOf() (gas: 449293)
|
||||
OverflowTest:testBalanceOfWithNoSystemTotalKarma() (gas: 69655)
|
||||
OverflowTest:testMintOnlyAdmin() (gas: 428769)
|
||||
OverflowTest:testRemoveKarmaDistributorOnlyOwner() (gas: 162274)
|
||||
OverflowTest:testRemoveUnknownKarmaDistributor() (gas: 41630)
|
||||
OverflowTest:testTotalSupply() (gas: 359166)
|
||||
OverflowTest:testTransfersNotAllowed() (gas: 61763)
|
||||
OverflowTest:test_RevertWhen_MintingCausesOverflow() (gas: 129464)
|
||||
OverflowTest:test_RevertWhen_SettingRewardCausesOverflow() (gas: 127792)
|
||||
OverflowTest:testAddKarmaDistributorOnlyAdmin() (gas: 438246)
|
||||
OverflowTest:testBalanceOf() (gas: 456642)
|
||||
OverflowTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83805)
|
||||
OverflowTest:testMintOnlyAdmin() (gas: 429097)
|
||||
OverflowTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163437)
|
||||
OverflowTest:testRemoveUnknownKarmaDistributor() (gas: 41666)
|
||||
OverflowTest:testTotalSupply() (gas: 359391)
|
||||
OverflowTest:testTransfersNotAllowed() (gas: 61925)
|
||||
OverflowTest:test_RevertWhen_MintingCausesOverflow() (gas: 129592)
|
||||
OverflowTest:test_RevertWhen_SettingRewardCausesOverflow() (gas: 127920)
|
||||
RLNTest:test_initial_state() (gas: 65400)
|
||||
RLNTest:test_register_fails_when_amount_lt_minimal_deposit() (gas: 161453)
|
||||
RLNTest:test_register_fails_when_duplicate_identity_commitments() (gas: 444949)
|
||||
@@ -106,36 +106,60 @@ RLNTest:test_withdraw_fails_when_already_underways() (gas: 468594)
|
||||
RLNTest:test_withdraw_fails_when_invalid_proof() (gas: 399356)
|
||||
RLNTest:test_withdraw_fails_when_not_registered() (gas: 57129)
|
||||
RLNTest:test_withdraw_succeeds() (gas: 480413)
|
||||
RemoveRewardDistributorTest:testAddKarmaDistributorOnlyAdmin() (gas: 438045)
|
||||
RemoveRewardDistributorTest:testBalanceOf() (gas: 449366)
|
||||
RemoveRewardDistributorTest:testBalanceOfWithNoSystemTotalKarma() (gas: 69633)
|
||||
RemoveRewardDistributorTest:testMintOnlyAdmin() (gas: 428759)
|
||||
RemoveRewardDistributorTest:testRemoveKarmaDistributorOnlyOwner() (gas: 162298)
|
||||
RemoveRewardDistributorTest:testRemoveRewardDistributor() (gas: 162118)
|
||||
RemoveRewardDistributorTest:testRemoveRewardDistributorAsOtherAdmin() (gas: 242200)
|
||||
RemoveRewardDistributorTest:testRemoveUnknownKarmaDistributor() (gas: 41636)
|
||||
RemoveRewardDistributorTest:testTotalSupply() (gas: 359239)
|
||||
RemoveRewardDistributorTest:testTransfersNotAllowed() (gas: 61763)
|
||||
RemoveRewardDistributorTest:test_RevertWhen_SenderIsNotDefaultAdmin() (gas: 66507)
|
||||
SetRewardTest:testAddKarmaDistributorOnlyAdmin() (gas: 438077)
|
||||
SetRewardTest:testBalanceOf() (gas: 449293)
|
||||
SetRewardTest:testBalanceOfWithNoSystemTotalKarma() (gas: 69677)
|
||||
SetRewardTest:testMintOnlyAdmin() (gas: 428791)
|
||||
SetRewardTest:testRemoveKarmaDistributorOnlyOwner() (gas: 162241)
|
||||
SetRewardTest:testRemoveUnknownKarmaDistributor() (gas: 41630)
|
||||
SetRewardTest:testSetRewardAsAdmin() (gas: 134934)
|
||||
SetRewardTest:testSetRewardAsOperator() (gas: 143714)
|
||||
SetRewardTest:testSetRewardAsOtherAdmin() (gas: 203920)
|
||||
SetRewardTest:testTotalSupply() (gas: 359211)
|
||||
SetRewardTest:testTransfersNotAllowed() (gas: 61807)
|
||||
SetRewardTest:test_RevertWhen_SenderIsNotDefaultAdmin() (gas: 43559)
|
||||
SetRewardTest:test_RevertWhen_SenderIsNotOperator() (gas: 61832)
|
||||
StakeManager_RewardsTest:testRewardsBalanceOf() (gas: 2712035)
|
||||
StakeManager_RewardsTest:testSetRewards() (gas: 278100)
|
||||
StakeManager_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 63751)
|
||||
StakeManager_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 103509)
|
||||
RemoveRewardDistributorTest:testAddKarmaDistributorOnlyAdmin() (gas: 438248)
|
||||
RemoveRewardDistributorTest:testBalanceOf() (gas: 456715)
|
||||
RemoveRewardDistributorTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83783)
|
||||
RemoveRewardDistributorTest:testMintOnlyAdmin() (gas: 429087)
|
||||
RemoveRewardDistributorTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163461)
|
||||
RemoveRewardDistributorTest:testRemoveRewardDistributor() (gas: 162967)
|
||||
RemoveRewardDistributorTest:testRemoveRewardDistributorAsOtherAdmin() (gas: 243532)
|
||||
RemoveRewardDistributorTest:testRemoveUnknownKarmaDistributor() (gas: 41672)
|
||||
RemoveRewardDistributorTest:testTotalSupply() (gas: 359464)
|
||||
RemoveRewardDistributorTest:testTransfersNotAllowed() (gas: 61925)
|
||||
RemoveRewardDistributorTest:test_RevertWhen_SenderIsNotDefaultAdmin() (gas: 66543)
|
||||
SetRewardTest:testAddKarmaDistributorOnlyAdmin() (gas: 438280)
|
||||
SetRewardTest:testBalanceOf() (gas: 456642)
|
||||
SetRewardTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83827)
|
||||
SetRewardTest:testMintOnlyAdmin() (gas: 429119)
|
||||
SetRewardTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163404)
|
||||
SetRewardTest:testRemoveUnknownKarmaDistributor() (gas: 41666)
|
||||
SetRewardTest:testSetRewardAsAdmin() (gas: 135089)
|
||||
SetRewardTest:testSetRewardAsOperator() (gas: 143840)
|
||||
SetRewardTest:testSetRewardAsOtherAdmin() (gas: 204104)
|
||||
SetRewardTest:testTotalSupply() (gas: 359436)
|
||||
SetRewardTest:testTransfersNotAllowed() (gas: 61969)
|
||||
SetRewardTest:test_RevertWhen_SenderIsNotDefaultAdmin() (gas: 43572)
|
||||
SetRewardTest:test_RevertWhen_SenderIsNotOperator() (gas: 61893)
|
||||
SlashAmountOfTest:testAddKarmaDistributorOnlyAdmin() (gas: 438224)
|
||||
SlashAmountOfTest:testBalanceOf() (gas: 456642)
|
||||
SlashAmountOfTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83783)
|
||||
SlashAmountOfTest:testFuzz_SlashAmountOf(uint256,uint256,uint256) (runs: 1002, μ: 407788, ~: 408571)
|
||||
SlashAmountOfTest:testMintOnlyAdmin() (gas: 429075)
|
||||
SlashAmountOfTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163437)
|
||||
SlashAmountOfTest:testRemoveUnknownKarmaDistributor() (gas: 41654)
|
||||
SlashAmountOfTest:testTotalSupply() (gas: 359391)
|
||||
SlashAmountOfTest:testTransfersNotAllowed() (gas: 61990)
|
||||
SlashAmountOfTest:test_SlashAmountOf() (gas: 327608)
|
||||
SlashTest:testAddKarmaDistributorOnlyAdmin() (gas: 438270)
|
||||
SlashTest:testBalanceOf() (gas: 456648)
|
||||
SlashTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83827)
|
||||
SlashTest:testFuzz_Slash(uint256) (runs: 1009, μ: 280204, ~: 280146)
|
||||
SlashTest:testMintOnlyAdmin() (gas: 429131)
|
||||
SlashTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163461)
|
||||
SlashTest:testRemoveRewardDistributorShouldReduceSlashAmount() (gas: 610762)
|
||||
SlashTest:testRemoveUnknownKarmaDistributor() (gas: 41683)
|
||||
SlashTest:testTotalSupply() (gas: 359420)
|
||||
SlashTest:testTransfersNotAllowed() (gas: 61969)
|
||||
SlashTest:test_RevertWhen_KarmaBalanceIsInvalid() (gas: 71550)
|
||||
SlashTest:test_RevertWhen_SenderIsNotDefaultAdminOrSlasher() (gas: 43232)
|
||||
SlashTest:test_Slash() (gas: 428385)
|
||||
SlashTest:test_SlashRemainingBalanceIfBalanceIsLow() (gas: 251800)
|
||||
StakeManager_RewardsTest:testRewardsBalanceOf() (gas: 2712133)
|
||||
StakeManager_RewardsTest:testSetRewards() (gas: 278149)
|
||||
StakeManager_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 63800)
|
||||
StakeManager_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 103558)
|
||||
StakeManager_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39367)
|
||||
StakeManager_RewardsTest:testTotalRewardsSupply() (gas: 1280724)
|
||||
StakeManager_RewardsTest:testTotalRewardsSupply() (gas: 1280822)
|
||||
StakeTest:test_StakeMultipleAccounts() (gas: 666808)
|
||||
StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 721800)
|
||||
StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 1324461)
|
||||
@@ -182,7 +206,7 @@ UnstakeTest:test_UnstakeOneAccount() (gas: 759178)
|
||||
UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 719489)
|
||||
UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 673681)
|
||||
UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 722241)
|
||||
UpdateVaultTest:test_UpdateAccount() (gas: 2587378)
|
||||
UpdateVaultTest:test_UpdateAccount() (gas: 2587427)
|
||||
UpgradeTest:test_RevertWhenNotOwner() (gas: 3696209)
|
||||
UpgradeTest:test_UpgradeStakeManager() (gas: 9855347)
|
||||
VaultRegistrationTest:test_VaultRegistration() (gas: 90138)
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
"files": [
|
||||
"src/StakeManager.sol",
|
||||
"src/Karma.sol",
|
||||
"certora/harness/KarmaHarness.sol",
|
||||
],
|
||||
"msg": "Verifying Karma.sol",
|
||||
"rule_sanity": "basic",
|
||||
"verify": "Karma:certora/specs/Karma.spec",
|
||||
"parametric_contracts": ["Karma"],
|
||||
"verify": "KarmaHarness:certora/specs/Karma.spec",
|
||||
"parametric_contracts": ["KarmaHarness"],
|
||||
"optimistic_loop": true,
|
||||
"loop_iter": "3",
|
||||
"packages": [
|
||||
|
||||
14
certora/harness/KarmaHarness.sol
Normal file
14
certora/harness/KarmaHarness.sol
Normal file
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import { Karma } from "../../src/Karma.sol";
|
||||
|
||||
contract KarmaHarness is Karma {
|
||||
function rawBalanceOf(address account) public view returns (uint256) {
|
||||
(uint256 rawBalance,) = _rawBalanceAndSlashAmountOf(account);
|
||||
return rawBalance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
using Karma as karma;
|
||||
using KarmaHarness as karma;
|
||||
|
||||
methods {
|
||||
function balanceOf(address) external returns (uint256) envfree;
|
||||
function rawBalanceOf(address) external returns (uint256) envfree;
|
||||
function totalDistributorAllocation() external returns (uint256) envfree;
|
||||
function totalSupply() external returns (uint256) envfree;
|
||||
function externalSupply() external returns (uint256) envfree;
|
||||
function accountSlashAmount(address) external returns (uint256) envfree;
|
||||
function _.setReward(uint256, uint256) external => DISPATCHER(true);
|
||||
function _.rewardsBalanceOfAccount(address) external => DISPATCHER(true);
|
||||
function hasRole(bytes32, address) external returns (bool) envfree;
|
||||
function DEFAULT_ADMIN_ROLE() external returns (bytes32) envfree;
|
||||
function OPERATOR_ROLE() external returns (bytes32) envfree;
|
||||
function Math.mulDiv(uint256 a, uint256 b, uint256 c) internal returns uint256 => mulDivSummary(a,b,c);
|
||||
}
|
||||
|
||||
function mulDivSummary(uint256 a, uint256 b, uint256 c) returns uint256 {
|
||||
require c != 0;
|
||||
return require_uint256(a*b/c);
|
||||
}
|
||||
|
||||
persistent ghost mathint sumOfDistributorAllocations {
|
||||
@@ -18,17 +28,6 @@ hook Sstore rewardDistributorAllocations[KEY address addr] uint256 newValue (uin
|
||||
sumOfDistributorAllocations = sumOfDistributorAllocations - oldValue + newValue;
|
||||
}
|
||||
|
||||
invariant totalDistributorAllocationIsSumOfDistributorAllocations()
|
||||
to_mathint(totalDistributorAllocation()) == sumOfDistributorAllocations
|
||||
filtered {
|
||||
f -> !isUpgradeFunction(f)
|
||||
}
|
||||
|
||||
rule externalSupplyIsLessOrEqThanTotalDistributorAllocation() {
|
||||
assert externalSupply() <= totalDistributorAllocation();
|
||||
}
|
||||
|
||||
|
||||
definition isUpgradeFunction(method f) returns bool = (
|
||||
f.selector == sig:karma.upgradeToAndCall(address, bytes).selector ||
|
||||
f.selector == sig:karma.upgradeTo(address).selector
|
||||
@@ -51,6 +50,22 @@ definition isOperatorFunction(method f) returns bool = (
|
||||
|| f.selector == sig:karma.mint(address, uint256).selector
|
||||
);
|
||||
|
||||
invariant totalDistributorAllocationIsSumOfDistributorAllocations()
|
||||
to_mathint(totalDistributorAllocation()) == sumOfDistributorAllocations
|
||||
filtered {
|
||||
f -> !isUpgradeFunction(f)
|
||||
}
|
||||
|
||||
// invariant slashAmountIsLessEqualToKarmaBalance(address account)
|
||||
// rawBalanceOf(account) >= getSlashAmountForAccount(account)
|
||||
// filtered {
|
||||
// f -> !isUpgradeFunction(f) && !isERC20TransferFunction(f)
|
||||
// }
|
||||
|
||||
// rule externalSupplyIsLessOrEqThanTotalDistributorAllocation() {
|
||||
// assert externalSupply() <= totalDistributorAllocation();
|
||||
// }
|
||||
|
||||
rule erc20TransferIsDisabled(method f) {
|
||||
env e;
|
||||
calldataarg args;
|
||||
|
||||
184
src/Karma.sol
184
src/Karma.sol
@@ -7,6 +7,7 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils
|
||||
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
|
||||
import { IRewardDistributor } from "./interfaces/IRewardDistributor.sol";
|
||||
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
|
||||
|
||||
/**
|
||||
* @title Karma
|
||||
@@ -26,8 +27,28 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
error Karma__UnknownDistributor();
|
||||
/// @notice Emitted sender does not have the required role
|
||||
error Karma__Unauthorized();
|
||||
/// @notice Emitted when slash percentage to set is invalid
|
||||
error Karma__InvalidSlashPercentage();
|
||||
/// @notice Emitted when balance to slash is invalid
|
||||
error Karma__CannotSlashZeroBalance();
|
||||
|
||||
/// @notice Emitted when a reward distributor is added
|
||||
event RewardDistributorAdded(address distributor);
|
||||
/// @notice Emitted when a reward distributor is removed
|
||||
event RewardDistributorRemoved(address distributor);
|
||||
/// @notice Emitted when an account is slashed
|
||||
event AccountSlashed(address indexed account, uint256 amount);
|
||||
/// @notice Emitted when the slash percentage is updated
|
||||
event SlashPercentageUpdated(uint256 oldPercentage, uint256 newPercentage);
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
CONSTATNS
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/// @notice Maximum slash percentage (in basis points: 100% = 10000)
|
||||
uint256 public constant MAX_SLASH_PERCENTAGE = 10_000;
|
||||
/// @notice Minimum slash amount
|
||||
uint256 public constant MIN_SLASH_AMOUNT = 1 ether;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
STATE VARIABLES
|
||||
@@ -39,13 +60,23 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
string public constant SYMBOL = "KARMA";
|
||||
/// @notice The total allocation for all reward distributors
|
||||
uint256 public totalDistributorAllocation;
|
||||
uint256 public totalSlashAmount;
|
||||
/// @notice Set of reward distributors
|
||||
EnumerableSet.AddressSet private rewardDistributors;
|
||||
/// @notice Mapping of reward distributor to allocation
|
||||
/// @notice Mapping of reward distributor allocations
|
||||
mapping(address distributor => uint256 allocation) public rewardDistributorAllocations;
|
||||
/// @notice Mapping of reward distributor slash amounts for individual accounts
|
||||
mapping(address distributor => mapping(address account => uint256 slashAmount)) public rewardDistributorSlashAmount;
|
||||
/// @notice Mapping of accounts to their slashed amount for internal balance
|
||||
mapping(address account => uint256 slashAmount) public accountSlashAmount;
|
||||
/// @notice Percentage of Karma to slash (in basis points: 1% = 100, 10% = 1000, 100% = 10000)
|
||||
uint256 public slashPercentage;
|
||||
|
||||
/// @notice Operator role keccak256("OPERATOR_ROLE")
|
||||
bytes32 public constant OPERATOR_ROLE = 0x97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929;
|
||||
/// @notice Slasher role keccak256("SLASHER_ROLE")
|
||||
bytes32 public constant SLASHER_ROLE = 0x12b42e8a160f6064dc959c6f251e3af0750ad213dbecf573b4710d67d6c28e39;
|
||||
|
||||
/// @notice Gap for upgrade safety.
|
||||
// solhint-disable-next-line
|
||||
uint256[30] private __gap_Karma;
|
||||
@@ -58,6 +89,14 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
_;
|
||||
}
|
||||
|
||||
/// @notice Modifier to check if sender has slasher role
|
||||
modifier onlySlasher() {
|
||||
if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender) && !hasRole(SLASHER_ROLE, msg.sender)) {
|
||||
revert Karma__Unauthorized();
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
CONSTRUCTOR
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -77,7 +116,9 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
__ERC20_init(NAME, SYMBOL);
|
||||
__UUPSUpgradeable_init();
|
||||
__AccessControl_init();
|
||||
|
||||
_setupRole(DEFAULT_ADMIN_ROLE, _owner);
|
||||
slashPercentage = 5000; // 50%
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -103,6 +144,20 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
_removeRewardDistributor(distributor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the slash percentage for the contract.
|
||||
* @dev Only the admin configure the slash percentage
|
||||
* @param percentage The percentage to set (in basis points: 1% = 100, 10% = 1000, 100% = 10000)
|
||||
*/
|
||||
function setSlashPercentage(uint256 percentage) public onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
if (percentage > 10_000) {
|
||||
revert Karma__InvalidSlashPercentage();
|
||||
}
|
||||
uint256 oldPercentage = slashPercentage;
|
||||
slashPercentage = percentage;
|
||||
emit SlashPercentageUpdated(oldPercentage, percentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the reward for a reward distributor.
|
||||
* @dev Only the owner can set the reward for a reward distributor.
|
||||
@@ -135,6 +190,20 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
_mint(account, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Slashes karma from an account based on the current slashing percentage
|
||||
* @dev Only accounts with the SLASHER_ROLE can call this function
|
||||
* @param account Account to slash
|
||||
* @return slashedAmount The amount of karma that was slashed
|
||||
*/
|
||||
function slash(address account) public virtual onlySlasher returns (uint256) {
|
||||
return _slash(account);
|
||||
}
|
||||
|
||||
function calculateSlashAmount(uint256 value) public view returns (uint256) {
|
||||
return _calculateSlashAmount(value);
|
||||
}
|
||||
|
||||
function transfer(address, uint256) public pure override returns (bool) {
|
||||
revert Karma__TransfersNotAllowed();
|
||||
}
|
||||
@@ -155,6 +224,15 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
return super.totalSupply() + _externalSupply();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the internal total supply of the token.
|
||||
* @dev The internal total supply is the total supply of the token without the external supply.
|
||||
* @return The internal total supply of the token.
|
||||
*/
|
||||
function _internalTotalSupply() internal view returns (uint256) {
|
||||
return super.totalSupply();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the external supply of the token.
|
||||
* @dev The external supply is the sum of the rewards from all reward distributors.
|
||||
@@ -176,6 +254,16 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
return externalSupply;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the internal balance of an account.
|
||||
* @dev This function is used to get the internal balance of an account.
|
||||
* @param account The address of the account to get the internal balance of.
|
||||
* @return The internal balance of the account.
|
||||
*/
|
||||
function _internalBalanceOf(address account) internal view returns (uint256) {
|
||||
return super.balanceOf(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Authorizes contract upgrades via UUPS.
|
||||
* @dev This function is only callable by the owner.
|
||||
@@ -194,7 +282,6 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
if (rewardDistributors.contains(distributor)) {
|
||||
revert Karma__DistributorAlreadyAdded();
|
||||
}
|
||||
|
||||
rewardDistributors.add(distributor);
|
||||
emit RewardDistributorAdded(distributor);
|
||||
}
|
||||
@@ -208,6 +295,7 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
revert Karma__UnknownDistributor();
|
||||
}
|
||||
rewardDistributors.remove(distributor);
|
||||
emit RewardDistributorRemoved(distributor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,6 +312,78 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
IRewardDistributor(rewardsDistributor).setReward(amount, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Slashes karma from an account based on the current slashing percentage
|
||||
* @param account Account to slash
|
||||
* @return slashedAmount The amount of karma that was slashed
|
||||
*/
|
||||
function _slash(address account) internal virtual returns (uint256) {
|
||||
uint256 currentBalance = balanceOf(account);
|
||||
if (currentBalance == 0) {
|
||||
revert Karma__CannotSlashZeroBalance();
|
||||
}
|
||||
|
||||
uint256 totalAmountToSlash;
|
||||
|
||||
// first, slash all reward distributors
|
||||
for (uint256 i = 0; i < rewardDistributors.length(); i++) {
|
||||
address distributor = rewardDistributors.at(i);
|
||||
uint256 currentDistributorAccountBalance = IRewardDistributor(distributor).rewardsBalanceOfAccount(account);
|
||||
uint256 currentDistributorSlashAmount = rewardDistributorSlashAmount[distributor][account];
|
||||
|
||||
uint256 distributorAmountToSlash =
|
||||
_calculateSlashAmount(currentDistributorAccountBalance - currentDistributorSlashAmount);
|
||||
|
||||
rewardDistributorSlashAmount[distributor][account] += distributorAmountToSlash;
|
||||
totalAmountToSlash += distributorAmountToSlash;
|
||||
}
|
||||
|
||||
// then, slash internal balance
|
||||
uint256 amountToSlash = _calculateSlashAmount(super.balanceOf(account) - accountSlashAmount[account]);
|
||||
accountSlashAmount[account] += amountToSlash;
|
||||
totalAmountToSlash += amountToSlash;
|
||||
totalSlashAmount += totalAmountToSlash;
|
||||
|
||||
emit AccountSlashed(account, totalAmountToSlash);
|
||||
return totalAmountToSlash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Calculates the amount to slash from a given balance, falling back to the minimum slash amount if
|
||||
* necessary.
|
||||
*/
|
||||
function _calculateSlashAmount(uint256 balance) internal view returns (uint256) {
|
||||
uint256 amountToSlash = Math.mulDiv(balance, slashPercentage, MAX_SLASH_PERCENTAGE);
|
||||
if (amountToSlash < MIN_SLASH_AMOUNT) {
|
||||
if (balance < MIN_SLASH_AMOUNT) {
|
||||
// Not enough balance for minimum slash, slash entire balance
|
||||
amountToSlash = balance;
|
||||
} else {
|
||||
amountToSlash = MIN_SLASH_AMOUNT;
|
||||
}
|
||||
}
|
||||
return amountToSlash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the raw balance of an account.
|
||||
*/
|
||||
function _rawBalanceAndSlashAmountOf(address account) internal view returns (uint256, uint256) {
|
||||
uint256 externalBalance;
|
||||
uint256 slashedAmount;
|
||||
|
||||
// first, aggregate all slashed amounts from reward distributors
|
||||
for (uint256 i = 0; i < rewardDistributors.length(); i++) {
|
||||
address distributor = rewardDistributors.at(i);
|
||||
externalBalance += IRewardDistributor(distributor).rewardsBalanceOfAccount(account);
|
||||
slashedAmount += rewardDistributorSlashAmount[distributor][account];
|
||||
}
|
||||
// then, add the slashed amount from the internal balance
|
||||
slashedAmount += accountSlashAmount[account];
|
||||
|
||||
return (super.balanceOf(account) + externalBalance, slashedAmount);
|
||||
}
|
||||
|
||||
function _overflowCheck(uint256 amount) internal view {
|
||||
// This will revert if `amount` overflows the total supply
|
||||
super.totalSupply() + totalDistributorAllocation + amount;
|
||||
@@ -257,14 +417,22 @@ contract Karma is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessContro
|
||||
* @return The balance of the account.
|
||||
*/
|
||||
function balanceOf(address account) public view override returns (uint256) {
|
||||
uint256 externalBalance;
|
||||
|
||||
for (uint256 i = 0; i < rewardDistributors.length(); i++) {
|
||||
address distributor = rewardDistributors.at(i);
|
||||
externalBalance += IRewardDistributor(distributor).rewardsBalanceOfAccount(account);
|
||||
(uint256 rawBalance, uint256 slashedAmount) = _rawBalanceAndSlashAmountOf(account);
|
||||
// Subtract slashed amount
|
||||
if (slashedAmount >= rawBalance) {
|
||||
return 0;
|
||||
}
|
||||
return rawBalance - slashedAmount;
|
||||
}
|
||||
|
||||
return super.balanceOf(account) + externalBalance;
|
||||
/**
|
||||
* @notice Returns the total slash amount of an account
|
||||
* @param account The account to get the slash amount of.
|
||||
* @return The slash amount of the account.
|
||||
*/
|
||||
function slashedAmountOf(address account) public view returns (uint256) {
|
||||
(, uint256 slashAmount) = _rawBalanceAndSlashAmountOf(account);
|
||||
return slashAmount;
|
||||
}
|
||||
|
||||
function allowance(address, address) public pure override returns (uint256) {
|
||||
|
||||
165
test/Karma.t.sol
165
test/Karma.t.sol
@@ -341,3 +341,168 @@ contract OverflowTest is KarmaTest {
|
||||
karma.setReward(address(distributor1), 1e18, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
contract SlashAmountOfTest is KarmaTest {
|
||||
address public slasher = makeAddr("slasher");
|
||||
|
||||
function _mintKarmaToAccount(address account, uint256 amount) internal {
|
||||
vm.startPrank(owner);
|
||||
karma.mint(account, amount);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function setUp() public override {
|
||||
super.setUp();
|
||||
|
||||
vm.startPrank(owner);
|
||||
karma.grantRole(karma.SLASHER_ROLE(), slasher);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_SlashAmountOf() public {
|
||||
uint256 accountBalance = 100 ether;
|
||||
uint256 distributorBalance = 50 ether;
|
||||
_mintKarmaToAccount(alice, accountBalance);
|
||||
vm.prank(owner);
|
||||
distributor1.setUserKarmaShare(alice, distributorBalance);
|
||||
|
||||
vm.prank(owner);
|
||||
karma.slash(alice);
|
||||
|
||||
uint256 slashedAmount = karma.slashedAmountOf(alice);
|
||||
assertEq(
|
||||
slashedAmount, karma.calculateSlashAmount(accountBalance) + karma.calculateSlashAmount(distributorBalance)
|
||||
);
|
||||
}
|
||||
|
||||
function testFuzz_SlashAmountOf(
|
||||
uint256 accountBalance,
|
||||
uint256 distributor1Balance,
|
||||
uint256 distributor2Balance
|
||||
)
|
||||
public
|
||||
{
|
||||
// adding some bounds here to ensure we don't overflow
|
||||
vm.assume(accountBalance < 1e30);
|
||||
vm.assume(distributor1Balance < 1e30);
|
||||
vm.assume(distributor2Balance < 1e30);
|
||||
|
||||
_mintKarmaToAccount(alice, accountBalance);
|
||||
vm.startPrank(owner);
|
||||
distributor1.setUserKarmaShare(alice, distributor1Balance);
|
||||
distributor2.setUserKarmaShare(alice, distributor2Balance);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.prank(owner);
|
||||
karma.slash(alice);
|
||||
|
||||
uint256 slashedAmount = karma.slashedAmountOf(alice);
|
||||
uint256 expectedSlashAmount = karma.calculateSlashAmount(accountBalance)
|
||||
+ karma.calculateSlashAmount(distributor1Balance) + karma.calculateSlashAmount(distributor2Balance);
|
||||
assertEq(slashedAmount, expectedSlashAmount);
|
||||
}
|
||||
}
|
||||
|
||||
contract SlashTest is KarmaTest {
|
||||
address public slasher = makeAddr("slasher");
|
||||
|
||||
function _mintKarmaToAccount(address account, uint256 amount) internal {
|
||||
vm.startPrank(owner);
|
||||
karma.mint(account, amount);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function setUp() public override {
|
||||
super.setUp();
|
||||
|
||||
vm.startPrank(owner);
|
||||
karma.grantRole(karma.SLASHER_ROLE(), slasher);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_RevertWhen_SenderIsNotDefaultAdminOrSlasher() public {
|
||||
vm.prank(makeAddr("someone"));
|
||||
vm.expectRevert(Karma.Karma__Unauthorized.selector);
|
||||
karma.slash(alice);
|
||||
}
|
||||
|
||||
function test_RevertWhen_KarmaBalanceIsInvalid() public {
|
||||
vm.prank(slasher);
|
||||
vm.expectRevert(Karma.Karma__CannotSlashZeroBalance.selector);
|
||||
karma.slash(alice);
|
||||
}
|
||||
|
||||
function test_SlashRemainingBalanceIfBalanceIsLow() public {
|
||||
_mintKarmaToAccount(alice, karma.MIN_SLASH_AMOUNT() - 1);
|
||||
|
||||
vm.prank(slasher);
|
||||
karma.slash(alice);
|
||||
|
||||
assertEq(karma.balanceOf(alice), 0);
|
||||
}
|
||||
|
||||
function test_Slash() public {
|
||||
// ensure rewards
|
||||
uint256 currentBalance = 100 ether;
|
||||
_mintKarmaToAccount(alice, currentBalance);
|
||||
uint256 slashedAmount = karma.calculateSlashAmount(currentBalance);
|
||||
|
||||
// slash the account
|
||||
vm.prank(slasher);
|
||||
karma.slash(alice);
|
||||
assertEq(karma.balanceOf(alice), currentBalance - slashedAmount);
|
||||
|
||||
currentBalance = karma.balanceOf(alice);
|
||||
slashedAmount = karma.calculateSlashAmount(currentBalance);
|
||||
|
||||
// slash again
|
||||
vm.prank(slasher);
|
||||
karma.slash(alice);
|
||||
|
||||
assertEq(karma.balanceOf(alice), currentBalance - slashedAmount);
|
||||
}
|
||||
|
||||
function testFuzz_Slash(uint256 rewardsAmount) public {
|
||||
vm.assume(rewardsAmount > 0);
|
||||
_mintKarmaToAccount(alice, rewardsAmount);
|
||||
|
||||
vm.prank(slasher);
|
||||
karma.slash(alice);
|
||||
uint256 slashedAmount = karma.slashedAmountOf(alice);
|
||||
|
||||
assertEq(karma.balanceOf(alice), rewardsAmount - slashedAmount);
|
||||
}
|
||||
|
||||
function testRemoveRewardDistributorShouldReduceSlashAmount() public {
|
||||
uint256 distributorRewards = 1000 ether;
|
||||
uint256 mintedRewards = 1000 ether;
|
||||
uint256 totalRewards = distributorRewards + mintedRewards;
|
||||
|
||||
// set up rewards for alice
|
||||
vm.prank(owner);
|
||||
distributor1.setUserKarmaShare(alice, distributorRewards);
|
||||
_mintKarmaToAccount(alice, mintedRewards);
|
||||
assertEq(distributor1.rewardsBalanceOfAccount(alice), distributorRewards);
|
||||
assertEq(karma.balanceOf(alice), totalRewards);
|
||||
|
||||
// slash alice
|
||||
uint256 accountSlashAmount = karma.calculateSlashAmount(mintedRewards);
|
||||
uint256 distributorSlashAmount = karma.calculateSlashAmount(distributorRewards);
|
||||
vm.prank(owner);
|
||||
karma.slash(alice);
|
||||
|
||||
uint256 totalSlashAmount = karma.slashedAmountOf(alice);
|
||||
|
||||
assertEq(karma.accountSlashAmount(alice), accountSlashAmount);
|
||||
assertEq(karma.rewardDistributorSlashAmount(address(distributor1), alice), distributorSlashAmount);
|
||||
assertEq(karma.slashedAmountOf(alice), totalSlashAmount);
|
||||
assertEq(karma.balanceOf(alice), totalRewards - totalSlashAmount);
|
||||
|
||||
// remove the distributor
|
||||
vm.prank(owner);
|
||||
karma.removeRewardDistributor(address(distributor1));
|
||||
totalSlashAmount = karma.slashedAmountOf(alice);
|
||||
assertEq(karma.accountSlashAmount(alice), accountSlashAmount);
|
||||
assertEq(karma.balanceOf(alice), totalRewards - distributorRewards - totalSlashAmount);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user