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:
r4bbit
2025-05-16 17:23:24 +02:00
parent 753e379682
commit 56d98ed44e
7 changed files with 552 additions and 151 deletions

View File

@@ -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 |
╰---------------------------------------------+-----------------+-------+--------+-------+---------╯
╭-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╮

View File

@@ -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)

View File

@@ -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": [

View 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;
}
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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);
}
}