diff --git a/.gas-report b/.gas-report index 5648f9f..2b7c4aa 100644 --- a/.gas-report +++ b/.gas-report @@ -46,13 +46,13 @@ +=========================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |---------------------------------------------------------------+-----------------+---------+---------+---------+---------| -| 3542177 | 17392 | | | | | +| 3233361 | 15956 | | | | | |---------------------------------------------------------------+-----------------+---------+---------+---------+---------| | | | | | | | |---------------------------------------------------------------+-----------------+---------+---------+---------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------------------------+-----------------+---------+---------+---------+---------| -| run | 2968362 | 2968362 | 2968362 | 2968362 | 33 | +| run | 2680741 | 2680741 | 2680741 | 2680741 | 9 | ╰---------------------------------------------------------------+-----------------+---------+---------+---------+---------╯ ╭-------------------------------------------------------------------+-----------------+---------+---------+---------+---------╮ @@ -80,7 +80,7 @@ |---------------------------------------------------------+-----------------+------+--------+------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------------------+-----------------+------+--------+------+---------| -| activeNetworkConfig | 455 | 2084 | 455 | 4455 | 540 | +| activeNetworkConfig | 455 | 2048 | 455 | 4455 | 492 | ╰---------------------------------------------------------+-----------------+------+--------+------+---------╯ ╭---------------------------------------------------------------------+-----------------+---------+---------+---------+---------╮ @@ -144,7 +144,7 @@ |------------------------------+-----------------+--------+--------+--------+---------| | setReward | 4845 | 144102 | 166754 | 166754 | 319 | |------------------------------+-----------------+--------+--------+--------+---------| -| slash | 4803 | 103655 | 85757 | 123125 | 520 | +| slash | 4803 | 103656 | 85757 | 123125 | 520 | |------------------------------+-----------------+--------+--------+--------+---------| | slashedAmountOf | 17682 | 28099 | 28120 | 28120 | 516 | |------------------------------+-----------------+--------+--------+--------+---------| @@ -192,29 +192,17 @@ +===============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| -| 0 | 5856 | | | | | +| 0 | 4420 | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+--------+--------+--------+---------| -| MAX_TIER_NAME_LENGTH | 240 | 240 | 240 | 240 | 1 | +| getTierCount | 2337 | 2337 | 2337 | 2337 | 2 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| activateTier | 23752 | 27234 | 25870 | 32081 | 3 | +| getTierIdByKarmaBalance | 7460 | 7942 | 7784 | 10020 | 8 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| addTier | 24619 | 138647 | 140518 | 163301 | 563 | -|----------------------------------------+-----------------+--------+--------+--------+---------| -| currentTierId | 2326 | 2326 | 2326 | 2326 | 2 | -|----------------------------------------+-----------------+--------+--------+--------+---------| -| deactivateTier | 23787 | 30495 | 32110 | 32110 | 9 | -|----------------------------------------+-----------------+--------+--------+--------+---------| -| getTierById | 555 | 12473 | 12448 | 14510 | 520 | -|----------------------------------------+-----------------+--------+--------+--------+---------| -| getTierCount | 2365 | 2365 | 2365 | 2365 | 5 | -|----------------------------------------+-----------------+--------+--------+--------+---------| -| getTierIdByKarmaBalance | 50511 | 50598 | 50618 | 50645 | 4 | -|----------------------------------------+-----------------+--------+--------+--------+---------| -| updateTier | 25139 | 52773 | 51895 | 74613 | 261 | +| updateTiers | 23978 | 107929 | 28296 | 301092 | 9 | ╰----------------------------------------+-----------------+--------+--------+--------+---------╯ ╭--------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -252,7 +240,7 @@ |--------------------------------------------+-----------------+--------+--------+--------+---------| | leave | 66348 | 66348 | 66348 | 66348 | 2 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| lock | 7040 | 43275 | 46713 | 87673 | 1034 | +| lock | 7040 | 43229 | 46713 | 87673 | 1034 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | migrateToVault | 9294 | 53513 | 17021 | 170715 | 4 | |--------------------------------------------+-----------------+--------+--------+--------+---------| @@ -280,7 +268,7 @@ |--------------------------------------------+-----------------+--------+--------+--------+---------| | setTrustedCodehash | 24238 | 24238 | 24238 | 24238 | 95 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| stake | 2639 | 130108 | 60725 | 228623 | 2670 | +| stake | 2639 | 129983 | 60725 | 228623 | 2670 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | stakedBalanceOf | 2622 | 2622 | 2622 | 2622 | 1 | |--------------------------------------------+-----------------+--------+--------+--------+---------| @@ -306,7 +294,7 @@ |--------------------------------------------+-----------------+--------+--------+--------+---------| | updateGlobalState | 15820 | 25876 | 29230 | 29230 | 8 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| updateVault | 31948 | 34036 | 31948 | 110579 | 1023 | +| updateVault | 31948 | 34013 | 31948 | 110579 | 1023 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | upgradeTo | 10279 | 10772 | 10279 | 12745 | 5 | |--------------------------------------------+-----------------+--------+--------+--------+---------| @@ -334,7 +322,7 @@ |----------------------------------------+-----------------+--------+--------+--------+---------| | leave | 12223 | 113137 | 84120 | 356508 | 5 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| lock | 12151 | 58767 | 62251 | 103208 | 1035 | +| lock | 12151 | 58722 | 62251 | 103208 | 1035 | |----------------------------------------+-----------------+--------+--------+--------+---------| | lockUntil | 2363 | 2363 | 2363 | 2363 | 7768 | |----------------------------------------+-----------------+--------+--------+--------+---------| @@ -344,7 +332,7 @@ |----------------------------------------+-----------------+--------+--------+--------+---------| | register | 12742 | 78218 | 78761 | 78761 | 374 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| stake | 12131 | 164075 | 76290 | 284275 | 2671 | +| stake | 12131 | 163920 | 76290 | 284275 | 2671 | |----------------------------------------+-----------------+--------+--------+--------+---------| | stakeManager | 393 | 393 | 393 | 393 | 373 | |----------------------------------------+-----------------+--------+--------+--------+---------| @@ -352,7 +340,7 @@ |----------------------------------------+-----------------+--------+--------+--------+---------| | unstake | 12108 | 58192 | 55296 | 110656 | 272 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| updateLockUntil | 4432 | 20802 | 21532 | 21532 | 488 | +| updateLockUntil | 4432 | 20798 | 21532 | 21532 | 485 | |----------------------------------------+-----------------+--------+--------+--------+---------| | withdraw | 20817 | 20817 | 20817 | 20817 | 1 | |----------------------------------------+-----------------+--------+--------+--------+---------| @@ -370,9 +358,9 @@ |----------------------------------------------------+-----------------+-------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------------------+-----------------+-------+--------+--------+---------| -| fallback | 5208 | 12818 | 7353 | 374054 | 23161 | +| fallback | 5208 | 12817 | 7353 | 374054 | 23161 | |----------------------------------------------------+-----------------+-------+--------+--------+---------| -| implementation | 346 | 2144 | 2346 | 2346 | 4850 | +| implementation | 346 | 2145 | 2346 | 2346 | 4847 | ╰----------------------------------------------------+-----------------+-------+--------+--------+---------╯ ╭--------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -396,7 +384,7 @@ +===============================================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| 1204853 | 6207 | | | | | +| 1204853 | 6015 | | | | | |------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------| diff --git a/.gas-snapshot b/.gas-snapshot index b7ae3f9..a95d711 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,6 +1,3 @@ -ActivateTierTests:test_ActivateTier_RevertWhen_InvalidTierId() (gas: 36310) -ActivateTierTests:test_ActivateTier_RevertWhen_NotOwner() (gas: 210441) -ActivateTierTests:test_ActivateTier_Success() (gas: 234495) AddRewardDistributorTest:testAddKarmaDistributorOnlyAdmin() (gas: 438258) AddRewardDistributorTest:testAddRewardDistributorAsOtherAdmin() (gas: 182935) AddRewardDistributorTest:testBalanceOf() (gas: 456642) @@ -11,20 +8,6 @@ AddRewardDistributorTest:testRemoveUnknownKarmaDistributor() (gas: 41666) AddRewardDistributorTest:testTotalSupply() (gas: 359391) AddRewardDistributorTest:testTransfersNotAllowed() (gas: 61947) AddRewardDistributorTest:test_RevertWhen_SenderIsNotDefaultAdmin() (gas: 68406) -AddTierTests:test_AddTier_MultipleSuccessiveTiers() (gas: 421808) -AddTierTests:test_AddTier_RevertWhen_EmptyName() (gas: 35216) -AddTierTests:test_AddTier_RevertWhen_InvalidRange() (gas: 36148) -AddTierTests:test_AddTier_RevertWhen_InvalidRangeEqual() (gas: 36214) -AddTierTests:test_AddTier_RevertWhen_NotOwner() (gas: 35540) -AddTierTests:test_AddTier_RevertWhen_OverlappingTiers() (gas: 189527) -AddTierTests:test_AddTier_RevertWhen_TierNameTooLong() (gas: 42578) -AddTierTests:test_AddTier_Success() (gas: 180523) -AddTierTests:test_AddTier_UnlimitedMaxKarma() (gas: 147634) -DeactivateActivateTierTests:test_DeactivateTier_RevertWhen_InvalidTierId() (gas: 36377) -DeactivateActivateTierTests:test_DeactivateTier_RevertWhen_NotOwner() (gas: 177408) -DeactivateActivateTierTests:test_DeactivateTier_Success() (gas: 201619) -EdgeCasesTest:test_OverlapValidation_EdgeCases() (gas: 312965) -EdgeCasesTest:test_UnlimitedTierOverlap() (gas: 208274) EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 93554) EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 336067) EmergencyExitTest:test_EmergencyExitBasic() (gas: 524580) @@ -33,21 +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: 1002, μ: 581651, ~: 549041) -FuzzTests:testFuzz_AccrueMP_Relock(uint128,uint64,uint64,uint64) (runs: 1002, μ: 806522, ~: 777234) -FuzzTests:testFuzz_AddTier_ValidInputs(string,uint256,uint256,uint32) (runs: 1001, μ: 171310, ~: 171538) +FuzzTests:testFuzz_AccrueMP(uint128,uint64,uint64) (runs: 1006, μ: 581521, ~: 549037) +FuzzTests:testFuzz_AccrueMP_Relock(uint128,uint64,uint64,uint64) (runs: 1006, μ: 806405, ~: 777234) FuzzTests:testFuzz_EmergencyExit(uint256,uint256) (runs: 1000, μ: 588178, ~: 578267) -FuzzTests:testFuzz_Lock(uint256,uint64) (runs: 1002, μ: 961278, ~: 961235) -FuzzTests:testFuzz_Relock(uint256,uint64,uint64) (runs: 1002, μ: 598418, ~: 574225) +FuzzTests:testFuzz_Lock(uint256,uint64) (runs: 1006, μ: 961274, ~: 961235) +FuzzTests:testFuzz_Relock(uint256,uint64,uint64) (runs: 1006, μ: 598321, ~: 574225) FuzzTests:testFuzz_Rewards(uint256,uint256,uint256,uint16,uint16) (runs: 1000, μ: 650441, ~: 653254) -FuzzTests:testFuzz_Stake(uint256,uint64) (runs: 1002, μ: 376429, ~: 346087) -FuzzTests:testFuzz_Unstake(uint128,uint64,uint16,uint128) (runs: 1002, μ: 801209, ~: 780598) -FuzzTests:testFuzz_UpdateTier_ValidInputs(string,uint256,uint256,string,uint256,uint256,uint32,uint32) (runs: 1000, μ: 226513, ~: 225288) -FuzzTests:testFuzz_UpdateVault(uint128,uint64,uint64) (runs: 1002, μ: 581674, ~: 549064) -GetTierIdByKarmaBalanceTest:test_GetTierIdByKarmaBalance_BelowMinKarma() (gas: 58622) -GetTierIdByKarmaBalanceTest:test_GetTierIdByKarmaBalance_InBronzeTier() (gas: 58727) -GetTierIdByKarmaBalanceTest:test_GetTierIdByKarmaBalance_InGoldTier() (gas: 58767) -GetTierIdByKarmaBalanceTest:test_GetTierIdByKarmaBalance_InSilverTier() (gas: 58759) +FuzzTests:testFuzz_Stake(uint256,uint64) (runs: 1006, μ: 376307, ~: 346086) +FuzzTests:testFuzz_Unstake(uint128,uint64,uint16,uint128) (runs: 1006, μ: 801126, ~: 780593) +FuzzTests:testFuzz_UpdateVault(uint128,uint64,uint64) (runs: 1006, μ: 581544, ~: 549060) IntegrationTest:testStakeFoo() (gas: 2348931) KarmaNFTTest:testApproveNotAllowed() (gas: 10507) KarmaNFTTest:testGetApproved() (gas: 10531) @@ -77,11 +54,20 @@ KarmaTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163437) KarmaTest:testRemoveUnknownKarmaDistributor() (gas: 41654) KarmaTest:testTotalSupply() (gas: 359391) KarmaTest:testTransfersNotAllowed() (gas: 61925) +KarmaTiersTest:test_GetTierIdByKarmaBalance_EdgeCases() (gas: 297868) +KarmaTiersTest:test_Revert_When_TierNameEmpty() (gas: 39361) +KarmaTiersTest:test_Revert_When_TierNameTooLong() (gas: 40510) +KarmaTiersTest:test_Revert_When_TiersAreEmpty() (gas: 34673) +KarmaTiersTest:test_Revert_When_TiersNotContiguous() (gas: 132711) +KarmaTiersTest:test_Revert_When_TiersNotStartingAtZero() (gas: 37667) +KarmaTiersTest:test_Revert_When_UpdateTiersCalledByNonOwner() (gas: 36642) +KarmaTiersTest:test_Success_When_LastTierIsUnlimited() (gas: 242295) +KarmaTiersTest:test_Success_When_TiersAreContiguous() (gas: 336294) 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: 1002, μ: 384561, ~: 384588) +LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1006, μ: 384561, ~: 384588) LockTest:test_LockFailsWithNoStake() (gas: 89700) LockTest:test_LockFailsWithZero() (gas: 343310) LockTest:test_LockMultipleTimesExceedMaxLock() (gas: 746921) @@ -145,7 +131,7 @@ 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: 1000, μ: 408297, ~: 409081) +SlashAmountOfTest:testFuzz_SlashAmountOf(uint256,uint256,uint256) (runs: 1002, μ: 408298, ~: 409081) SlashAmountOfTest:testMintOnlyAdmin() (gas: 429075) SlashAmountOfTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163437) SlashAmountOfTest:testRemoveUnknownKarmaDistributor() (gas: 41654) @@ -155,7 +141,7 @@ SlashAmountOfTest:test_SlashAmountOf() (gas: 327608) SlashTest:testAddKarmaDistributorOnlyAdmin() (gas: 438270) SlashTest:testBalanceOf() (gas: 456648) SlashTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83827) -SlashTest:testFuzz_Slash(uint256) (runs: 1002, μ: 280204, ~: 280146) +SlashTest:testFuzz_Slash(uint256) (runs: 1006, μ: 280204, ~: 280146) SlashTest:testMintOnlyAdmin() (gas: 429131) SlashTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163461) SlashTest:testRemoveRewardDistributorShouldReduceSlashAmount() (gas: 610762) @@ -218,19 +204,9 @@ UnstakeTest:test_UnstakeOneAccount() (gas: 759178) UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 719489) UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 673681) UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 722241) -UpdateTierTests:test_UpdateTier_RevertWhen_InvalidRange() (gas: 38505) -UpdateTierTests:test_UpdateTier_RevertWhen_InvalidTierId() (gas: 37814) -UpdateTierTests:test_UpdateTier_RevertWhen_NotOwner() (gas: 35753) -UpdateTierTests:test_UpdateTier_RevertWhen_OverlapWithOtherTier() (gas: 183290) -UpdateTierTests:test_UpdateTier_Success() (gas: 83795) UpdateVaultTest:test_UpdateAccount() (gas: 2587427) UpgradeTest:test_RevertWhenNotOwner() (gas: 3696209) UpgradeTest:test_UpgradeStakeManager() (gas: 9855347) VaultRegistrationTest:test_VaultRegistration() (gas: 90138) -ViewFunctionsTest:test_GetTierById_RevertWhen_InvalidTierId() (gas: 13153) -ViewFunctionsTest:test_GetTierById_RevertWhen_TierIdZero() (gas: 11056) -ViewFunctionsTest:test_GetTierById_Success() (gas: 169210) -ViewFunctionsTest:test_GetTierCount_IncreasesWithTiers() (gas: 299987) -ViewFunctionsTest:test_GetTierCount_InitiallyZero() (gas: 10505) WithdrawTest:testOwner() (gas: 15365) WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 373408) \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1f16f7..f0fd332 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,9 +133,8 @@ jobs: submodules: recursive - name: Install Python - uses: actions/setup-python@v2 - with: { python-version: 3.9 } - + uses: actions/setup-python@v4 + with: { python-version: "3.10" } - name: Install Java uses: actions/setup-java@v1 with: { java-version: "11", java-package: jre } diff --git a/src/KarmaTiers.sol b/src/KarmaTiers.sol index 744585d..5098e2d 100644 --- a/src/KarmaTiers.sol +++ b/src/KarmaTiers.sol @@ -9,25 +9,21 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; * @notice This contract allows efficient tier lookup for L2 nodes and tier management */ contract KarmaTiers is Ownable { - /// @notice Emitted when a new tier is added - event TierAdded(uint8 indexed tierId, string name, uint256 minKarma, uint256 maxKarma, uint32 txPerEpoch); - /// @notice Emitted when a tier is updated - event TierUpdated(uint8 indexed tierId, string name, uint256 minKarma, uint256 maxKarma, uint32 txPerEpoch); - /// @notice Emitted when a tier is deactivated - event TierDeactivated(uint8 indexed tierId); - /// @notice Emitted when a tier is activated - event TierActivated(uint8 indexed tierId); - + /// @notice Emitted when a tier list is updated + event TiersUpdated(); /// @notice Emitted when a transaction amount is invalid + error InvalidTxAmount(); /// @notice Emitted when a tier name is empty error EmptyTierName(); + /// @notice Emitted when a tier array is empty + error EmptyTiersArray(); /// @notice Emitted when a tier is not found error TierNotFound(); /// @notice Emitted when a tier name exceeds maximum length error TierNameTooLong(uint256 nameLength, uint256 maxLength); - /// @notice Emitted when a new tier overlaps with an existing one - error OverlappingTiers(uint8 existingTierId, uint256 newMinKarma, uint256 newMaxKarma); + /// @notice Emitted when tiers are not contiguous + error NonContiguousTiers(uint8 index, uint256 expectedMinKarma, uint256 actualMinKarma); /// @notice Emitted when a tier's minKarma is greater than or equal to maxKarma error InvalidTierRange(uint256 minKarma, uint256 maxKarma); @@ -36,11 +32,10 @@ contract KarmaTiers is Ownable { uint256 maxKarma; string name; uint32 txPerEpoch; - bool active; } modifier onlyValidTierId(uint8 tierId) { - if (tierId == 0 || tierId > currentTierId) { + if (tierId >= tiers.length) { revert TierNotFound(); } _; @@ -57,10 +52,8 @@ contract KarmaTiers is Ownable { STATE VARIABLES //////////////////////////////////////////////////////////////////////////*/ - /// @notice Mapping of tier IDs to Tier structs - mapping(uint8 id => Tier tier) public tiers; - /// @notice Current tier ID, incremented with each new tier added - uint8 public currentTierId; + /// @notice Array of tiers + Tier[] public tiers; /*////////////////////////////////////////////////////////////////////////// CONSTRUCTOR @@ -74,77 +67,37 @@ contract KarmaTiers is Ownable { USER-FACING FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ - /** - * @dev Add a new tier to the system - * @param name The name of the tier - * @param minKarma Minimum Karma required for this tier - * @param maxKarma Maximum Karma for this tier (0 for unlimited) - */ - function addTier(string calldata name, uint256 minKarma, uint256 maxKarma, uint32 txPerEpoch) external onlyOwner { - if (bytes(name).length == 0) revert EmptyTierName(); - if (maxKarma != 0 && maxKarma <= minKarma) revert InvalidTierRange(minKarma, maxKarma); - if (txPerEpoch == 0) revert InvalidTxAmount(); + function updateTiers(Tier[] calldata newTiers) external onlyOwner { + if (newTiers.length == 0) { + revert EmptyTiersArray(); + } + // Ensure the first tier starts at minKarma = 0 + if (newTiers[0].minKarma != 0) { + revert NonContiguousTiers(0, 0, newTiers[0].minKarma); + } - // Check for overlaps with existing tiers - _validateNoOverlap(minKarma, maxKarma, type(uint8).max); - _validateTierName(name); + delete tiers; // Clear existing tiers - currentTierId++; + uint256 lastMaxKarma = 0; + for (uint8 i = 0; i < newTiers.length; i++) { + Tier calldata input = newTiers[i]; - tiers[currentTierId] = - Tier({ minKarma: minKarma, maxKarma: maxKarma, name: name, active: true, txPerEpoch: txPerEpoch }); + _validateTierName(input.name); + if (input.maxKarma <= input.minKarma) { + revert InvalidTierRange(input.minKarma, input.maxKarma); + } - emit TierAdded(currentTierId, name, minKarma, maxKarma, txPerEpoch); - } + if (i > 0) { + uint256 expectedMinKarma = lastMaxKarma + 1; + if (input.minKarma != expectedMinKarma) { + revert NonContiguousTiers(i, expectedMinKarma, input.minKarma); + } + } + lastMaxKarma = input.maxKarma; + tiers.push(input); + } - /** - * @dev Update an existing tier - * @param name The name of the tier to update - * @param newMinKarma New minimum Karma requirement - * @param newMaxKarma New maximum Karma (0 for unlimited) - */ - function updateTier( - uint8 tierId, - string calldata name, - uint256 newMinKarma, - uint256 newMaxKarma, - uint32 newTxPerEpoch - ) - external - onlyOwner - onlyValidTierId(tierId) - { - if (newMaxKarma != 0 && newMaxKarma <= newMinKarma) revert InvalidTierRange(newMinKarma, newMaxKarma); - if (newTxPerEpoch == 0) revert InvalidTxAmount(); - - // Check for overlaps with other tiers (excluding the one being updated) - _validateNoOverlap(newMinKarma, newMaxKarma, tierId); - _validateTierName(name); - - tiers[tierId].name = name; - tiers[tierId].minKarma = newMinKarma; - tiers[tierId].maxKarma = newMaxKarma; - tiers[tierId].txPerEpoch = newTxPerEpoch; - - emit TierUpdated(tierId, name, newMinKarma, newMaxKarma, newTxPerEpoch); - } - - /** - * @dev Deactivate a tier (keeps it in storage but marks as inactive) - * @param tierId The ID of the tier to deactivate - */ - function deactivateTier(uint8 tierId) external onlyOwner onlyValidTierId(tierId) { - tiers[tierId].active = false; - emit TierDeactivated(tierId); - } - - /** - * @dev Reactivate a tier - * @param tierId The ID of the tier to reactivate - */ - function activateTier(uint8 tierId) external onlyOwner onlyValidTierId(tierId) { - tiers[tierId].active = true; - emit TierActivated(tierId); + emit TiersUpdated(); } /*////////////////////////////////////////////////////////////////////////// @@ -174,23 +127,14 @@ contract KarmaTiers is Ownable { * @return tierId The tier id that matches the karma balance */ function getTierIdByKarmaBalance(uint256 karmaBalance) external view returns (uint8) { - uint8 bestTierId = 0; - uint256 bestMinKarma = 0; - - for (uint8 i = 1; i <= currentTierId; i++) { - Tier memory currentTier = tiers[i]; - if (!currentTier.active) continue; - + for (uint8 i = 0; i < tiers.length; i++) { // Check if user meets the minimum requirement for this tier - if (karmaBalance >= currentTier.minKarma) { - // Only update if this tier has a higher minKarma requirement - if (currentTier.minKarma > bestMinKarma) { - bestTierId = i; - bestMinKarma = currentTier.minKarma; - } + if (karmaBalance < tiers[i].minKarma) { + // Can't run into underflow here because `karmaBalance == 0 => karmaBalance < tiers[0].minKarma` + return i - 1; // Return the previous tier if this one is not met } } - return bestTierId; + return uint8(tiers.length - 1); // If all tiers are met, return the highest tier } /** @@ -198,7 +142,7 @@ contract KarmaTiers is Ownable { * @return count Total number of tiers (including inactive) */ function getTierCount() external view returns (uint256 count) { - return currentTierId; + return tiers.length; } /** @@ -209,25 +153,4 @@ contract KarmaTiers is Ownable { function getTierById(uint8 tierId) external view onlyValidTierId(tierId) returns (Tier memory tier) { return tiers[tierId]; } - - /** - * @dev Internal function to validate no overlap exists - * @param minKarma Minimum Karma for the tier - * @param maxKarma Maximum Karma for the tier (0 = unlimited) - */ - function _validateNoOverlap(uint256 minKarma, uint256 maxKarma, uint8 excludeTierId) internal view { - for (uint8 i = 1; i <= currentTierId; i++) { - if (i == excludeTierId || !tiers[i].active) continue; - - Tier memory existingTier = tiers[i]; - uint256 existingMax = existingTier.maxKarma == 0 ? type(uint256).max : existingTier.maxKarma; - uint256 newMax = maxKarma == 0 ? type(uint256).max : maxKarma; - - // Check for overlap using: NOT (no overlap) = overlap - // No overlap means: newMax < existingMin OR newMin > existingMax - if (!(newMax < existingTier.minKarma || minKarma > existingMax)) { - revert OverlappingTiers(i, minKarma, maxKarma); - } - } - } } diff --git a/test/KarmaTiers.t.sol b/test/KarmaTiers.t.sol index 6947bd5..aceeb84 100644 --- a/test/KarmaTiers.t.sol +++ b/test/KarmaTiers.t.sol @@ -8,429 +8,118 @@ import { KarmaTiers } from "../src/KarmaTiers.sol"; contract KarmaTiersTest is Test { KarmaTiers public karmaTiers; - address public deployer; - address public nonOwner = makeAddr("nonOwner"); - - event TierAdded(uint8 indexed tierId, string name, uint256 minKarma, uint256 maxKarma, uint32 txPerEpoch); - event TierUpdated(uint8 indexed tierId, string name, uint256 minKarma, uint256 maxKarma, uint32 txPerEpoch); - event TierDeactivated(uint8 indexed tierId); - event TierActivated(uint8 indexed tierId); + address public owner; + address public nonOwner = address(0xBEEF); function setUp() public virtual { DeployKarmaTiersScript deployment = new DeployKarmaTiersScript(); (KarmaTiers _karmaTiers, DeploymentConfig deploymentConfig) = deployment.run(); (address _deployer,) = deploymentConfig.activeNetworkConfig(); - deployer = _deployer; + owner = _deployer; karmaTiers = _karmaTiers; } -} -contract AddTierTests is KarmaTiersTest { - function setUp() public override { - super.setUp(); + function test_Revert_When_UpdateTiersCalledByNonOwner() public { + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](1); + newTiers[0] = KarmaTiers.Tier({ minKarma: 0, maxKarma: 100, name: "Bronze", txPerEpoch: 5 }); + + vm.prank(nonOwner); + vm.expectRevert("Ownable: caller is not the owner"); + karmaTiers.updateTiers(newTiers); } - function test_AddTier_RevertWhen_EmptyName() public { - vm.prank(deployer); + function test_Revert_When_TiersAreEmpty() public { + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](0); + + vm.prank(owner); + vm.expectRevert(); // generic revert + karmaTiers.updateTiers(newTiers); + } + + function test_Revert_When_TiersNotStartingAtZero() public { + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](1); + newTiers[0] = KarmaTiers.Tier({ minKarma: 1, maxKarma: 100, name: "Bronze", txPerEpoch: 5 }); + + vm.prank(owner); + vm.expectRevert(abi.encodeWithSelector(KarmaTiers.NonContiguousTiers.selector, 0, 0, 1)); + karmaTiers.updateTiers(newTiers); + } + + function test_Revert_When_TierNameEmpty() public { + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](1); + newTiers[0] = KarmaTiers.Tier({ minKarma: 0, maxKarma: 100, name: "", txPerEpoch: 5 }); + + vm.prank(owner); vm.expectRevert(KarmaTiers.EmptyTierName.selector); - karmaTiers.addTier("", 100, 500, 5); + karmaTiers.updateTiers(newTiers); } - function test_AddTier_RevertWhen_InvalidRange() public { - vm.prank(deployer); - vm.expectRevert(abi.encodeWithSelector(KarmaTiers.InvalidTierRange.selector, 500, 100)); - karmaTiers.addTier("Invalid", 500, 100, 5); + function test_Revert_When_TierNameTooLong() public { + string memory longName = new string(33); + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](1); + newTiers[0] = KarmaTiers.Tier({ minKarma: 0, maxKarma: 100, name: longName, txPerEpoch: 5 }); + + vm.prank(owner); + vm.expectRevert(abi.encodeWithSelector(KarmaTiers.TierNameTooLong.selector, 33, 32)); + karmaTiers.updateTiers(newTiers); } - function test_AddTier_RevertWhen_InvalidRangeEqual() public { - vm.prank(deployer); - vm.expectRevert(abi.encodeWithSelector(KarmaTiers.InvalidTierRange.selector, 500, 500)); - karmaTiers.addTier("Invalid", 500, 500, 5); + function test_Revert_When_TiersNotContiguous() public { + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](2); + newTiers[0] = KarmaTiers.Tier({ minKarma: 0, maxKarma: 100, name: "Bronze", txPerEpoch: 5 }); + newTiers[1] = KarmaTiers.Tier({ minKarma: 102, maxKarma: 200, name: "Silver", txPerEpoch: 5 }); + + vm.prank(owner); + vm.expectRevert(abi.encodeWithSelector(KarmaTiers.NonContiguousTiers.selector, 1, 101, 102)); + karmaTiers.updateTiers(newTiers); } - function test_AddTier_RevertWhen_OverlappingTiers() public { - vm.prank(deployer); - karmaTiers.addTier("Bronze", 100, 500, 5); + function test_Success_When_TiersAreContiguous() public { + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](3); + newTiers[0] = KarmaTiers.Tier({ minKarma: 0, maxKarma: 100, name: "Bronze", txPerEpoch: 5 }); + newTiers[1] = KarmaTiers.Tier({ minKarma: 101, maxKarma: 200, name: "Silver", txPerEpoch: 10 }); + newTiers[2] = KarmaTiers.Tier({ minKarma: 201, maxKarma: 999, name: "Gold", txPerEpoch: 15 }); - vm.prank(deployer); - vm.expectRevert(abi.encodeWithSelector(KarmaTiers.OverlappingTiers.selector, 1, 400, 600)); - karmaTiers.addTier("Silver", 400, 600, 5); - } + vm.expectEmit(false, false, false, true); + emit KarmaTiers.TiersUpdated(); - function test_AddTier_RevertWhen_NotOwner() public { - vm.prank(nonOwner); - vm.expectRevert("Ownable: caller is not the owner"); - karmaTiers.addTier("Bronze", 100, 500, 5); - } + vm.prank(owner); + karmaTiers.updateTiers(newTiers); - function test_AddTier_RevertWhen_TierNameTooLong() public { - string memory longName = "ThisIsAVeryLongTierNameThatExceedsTheMaximumAllowedLength"; - - vm.expectRevert( - abi.encodeWithSelector( - KarmaTiers.TierNameTooLong.selector, bytes(longName).length, karmaTiers.MAX_TIER_NAME_LENGTH() - ) - ); - vm.prank(deployer); - karmaTiers.addTier(longName, 100, 500, 5); - } - - function test_AddTier_Success() public { - string memory tierName = "Bronze"; - uint256 minKarma = 100; - uint256 maxKarma = 500; - uint32 txPerEpoch = 5; - - vm.expectEmit(true, false, false, true); - emit TierAdded(1, tierName, minKarma, maxKarma, txPerEpoch); - - vm.prank(deployer); - karmaTiers.addTier(tierName, minKarma, maxKarma, txPerEpoch); - - assertEq(karmaTiers.currentTierId(), 1); - - KarmaTiers.Tier memory tier = karmaTiers.getTierById(1); - assertEq(tier.name, tierName); - assertEq(tier.minKarma, minKarma); - assertEq(tier.maxKarma, maxKarma); - assertTrue(tier.active); - } - - function test_AddTier_UnlimitedMaxKarma() public { - string memory tierName = "Unlimited"; - uint256 minKarma = 1000; - uint256 maxKarma = 0; // 0 means unlimited - uint32 txPerEpoch = 5; - - vm.prank(deployer); - karmaTiers.addTier(tierName, minKarma, maxKarma, txPerEpoch); - - KarmaTiers.Tier memory tier = karmaTiers.getTierById(1); - assertEq(tier.maxKarma, 0); - } - - function test_AddTier_MultipleSuccessiveTiers() public { - vm.startPrank(deployer); - karmaTiers.addTier("Bronze", 0, 100, 5); - karmaTiers.addTier("Silver", 101, 500, 5); - karmaTiers.addTier("Gold", 501, 1000, 5); - vm.stopPrank(); - - assertEq(karmaTiers.currentTierId(), 3); assertEq(karmaTiers.getTierCount(), 3); - } -} - -contract UpdateTierTests is KarmaTiersTest { - function setUp() public override { - super.setUp(); - vm.prank(deployer); - karmaTiers.addTier("Bronze", 100, 500, 5); // Add a tier to update - } - - function test_UpdateTier_RevertWhen_InvalidTierId() public { - vm.expectRevert(KarmaTiers.TierNotFound.selector); - vm.prank(deployer); - karmaTiers.updateTier(2, "Bronze", 100, 500, 5); - } - - function test_UpdateTier_RevertWhen_InvalidRange() public { - vm.expectRevert(abi.encodeWithSelector(KarmaTiers.InvalidTierRange.selector, 600, 400)); - vm.prank(deployer); - karmaTiers.updateTier(1, "Bronze", 600, 400, 5); - } - - function test_UpdateTier_RevertWhen_OverlapWithOtherTier() public { - vm.startPrank(deployer); - karmaTiers.addTier("Silver", 600, 1000, 5); - vm.stopPrank(); - - vm.expectRevert(abi.encodeWithSelector(KarmaTiers.OverlappingTiers.selector, 2, 550, 800)); - vm.prank(deployer); - karmaTiers.updateTier(1, "Bronze", 550, 800, 5); - } - - function test_UpdateTier_RevertWhen_NotOwner() public { - vm.prank(nonOwner); - vm.expectRevert("Ownable: caller is not the owner"); - karmaTiers.updateTier(1, "Updated Bronze", 150, 600, 5); - } - - function test_UpdateTier_Success() public { - string memory newName = "Updated Bronze"; - uint256 newMinKarma = 150; - uint256 newMaxKarma = 600; - uint32 newTxPerEpoch = 10; - - vm.expectEmit(true, false, false, true); - emit TierUpdated(1, newName, newMinKarma, newMaxKarma, newTxPerEpoch); - - vm.prank(deployer); - karmaTiers.updateTier(1, newName, newMinKarma, newMaxKarma, newTxPerEpoch); - - KarmaTiers.Tier memory tier = karmaTiers.getTierById(1); - assertEq(tier.name, newName); - assertEq(tier.minKarma, newMinKarma); - assertEq(tier.maxKarma, newMaxKarma); - } -} - -contract DeactivateActivateTierTests is KarmaTiersTest { - function setUp() public override { - super.setUp(); - } - - function test_DeactivateTier_RevertWhen_InvalidTierId() public { - vm.expectRevert(KarmaTiers.TierNotFound.selector); - vm.prank(deployer); - karmaTiers.deactivateTier(1); - } - - function test_DeactivateTier_RevertWhen_NotOwner() public { - vm.prank(deployer); - karmaTiers.addTier("Bronze", 100, 500, 5); - - vm.prank(nonOwner); - vm.expectRevert("Ownable: caller is not the owner"); - karmaTiers.deactivateTier(1); - } - - function test_DeactivateTier_Success() public { - vm.prank(deployer); - karmaTiers.addTier("Bronze", 100, 500, 5); - - vm.expectEmit(true, false, false, false); - emit TierDeactivated(1); - - vm.prank(deployer); - karmaTiers.deactivateTier(1); - - KarmaTiers.Tier memory tier = karmaTiers.getTierById(1); - assertFalse(tier.active); - } -} - -contract ActivateTierTests is KarmaTiersTest { - function setUp() public override { - super.setUp(); - } - - function test_ActivateTier_Success() public { - vm.startPrank(deployer); - karmaTiers.addTier("Bronze", 100, 500, 5); - karmaTiers.deactivateTier(1); - vm.stopPrank(); - - vm.expectEmit(true, false, false, false); - emit TierActivated(1); - - vm.prank(deployer); - karmaTiers.activateTier(1); - - KarmaTiers.Tier memory tier = karmaTiers.getTierById(1); - assertTrue(tier.active); - } - - function test_ActivateTier_RevertWhen_InvalidTierId() public { - vm.expectRevert(KarmaTiers.TierNotFound.selector); - vm.prank(deployer); - karmaTiers.activateTier(1); - } - - function test_ActivateTier_RevertWhen_NotOwner() public { - vm.prank(deployer); - karmaTiers.addTier("Bronze", 100, 500, 5); - vm.prank(deployer); - karmaTiers.deactivateTier(1); - - vm.prank(nonOwner); - vm.expectRevert("Ownable: caller is not the owner"); - karmaTiers.activateTier(1); - } -} - -contract ViewFunctionsTest is KarmaTiersTest { - function setUp() public override { - super.setUp(); - } - - function test_GetTierCount_InitiallyZero() public { - assertEq(karmaTiers.getTierCount(), 0); - } - - function test_GetTierCount_IncreasesWithTiers() public { - vm.prank(deployer); - karmaTiers.addTier("Bronze", 100, 500, 5); - assertEq(karmaTiers.getTierCount(), 1); - - vm.prank(deployer); - karmaTiers.addTier("Silver", 600, 1000, 5); - assertEq(karmaTiers.getTierCount(), 2); - } - - function test_GetTierById_Success() public { - string memory tierName = "Bronze"; - uint256 minKarma = 100; - uint256 maxKarma = 500; - uint32 txPerEpoch = 5; - - vm.prank(deployer); - karmaTiers.addTier(tierName, minKarma, maxKarma, txPerEpoch); - - KarmaTiers.Tier memory tier = karmaTiers.getTierById(1); - assertEq(tier.name, tierName); - assertEq(tier.minKarma, minKarma); - assertEq(tier.maxKarma, maxKarma); - assertTrue(tier.active); - } - - function test_GetTierById_RevertWhen_InvalidTierId() public { - vm.expectRevert(KarmaTiers.TierNotFound.selector); - karmaTiers.getTierById(1); - } - - function test_GetTierById_RevertWhen_TierIdZero() public { - vm.expectRevert(KarmaTiers.TierNotFound.selector); - karmaTiers.getTierById(0); - } -} - -contract EdgeCasesTest is KarmaTiersTest { - function setUp() public override { - super.setUp(); - } - - function test_OverlapValidation_EdgeCases() public { - // Test adjacent ranges (should not overlap) - vm.startPrank(deployer); - karmaTiers.addTier("Tier1", 0, 100, 5); - karmaTiers.addTier("Tier2", 101, 200, 5); - vm.stopPrank(); - - // Test touching ranges (100 and 101 are adjacent, should not overlap) - assertEq(karmaTiers.getTierCount(), 2); - - // Test exact boundary overlap (should fail) - vm.expectRevert(abi.encodeWithSelector(KarmaTiers.OverlappingTiers.selector, 1, 100, 150)); - vm.prank(deployer); - karmaTiers.addTier("Tier3", 100, 150, 5); - } - - function test_UnlimitedTierOverlap() public { - // Add unlimited tier - vm.prank(deployer); - karmaTiers.addTier("Unlimited", 1000, 0, 5); - - // Try to add tier that overlaps with unlimited tier - vm.expectRevert(abi.encodeWithSelector(KarmaTiers.OverlappingTiers.selector, 1, 1500, 2000)); - vm.prank(deployer); - karmaTiers.addTier("Overlap", 1500, 2000, 5); - - // Try to add tier that starts before unlimited tier - vm.expectRevert(abi.encodeWithSelector(KarmaTiers.OverlappingTiers.selector, 1, 500, 1500)); - vm.prank(deployer); - karmaTiers.addTier("Before", 500, 1500, 5); - } -} - -contract GetTierIdByKarmaBalanceTest is KarmaTiersTest { - function setUp() public override { - super.setUp(); - vm.startPrank(deployer); - karmaTiers.addTier("Bronze", 100, 500, 5); - karmaTiers.addTier("Silver", 501, 1000, 5); - karmaTiers.addTier("Gold", 1001, 1500, 5); - karmaTiers.addTier("Platinum", 5001, 10_000, 5); // creating a gap - // let's also take into account that tiers aren't sorted - karmaTiers.addTier("Wood", 10, 99, 5); - - karmaTiers.deactivateTier(3); // Deactivate Gold tier for testing - vm.stopPrank(); - } - - function test_GetTierIdByKarmaBalance_BelowMinKarma() public { - uint256 karmaBalance = 5; - uint8 tierId = karmaTiers.getTierIdByKarmaBalance(karmaBalance); - assertEq(tierId, 0); // Should return 0 for no tier - } - - function test_GetTierIdByKarmaBalance_InBronzeTier() public { - uint256 karmaBalance = 300; - uint8 tierId = karmaTiers.getTierIdByKarmaBalance(karmaBalance); + (uint8 tierId) = karmaTiers.getTierIdByKarmaBalance(150); assertEq(tierId, 1); } - function test_GetTierIdByKarmaBalance_InSilverTier() public { - uint256 karmaBalance = 800; - uint8 tierId = karmaTiers.getTierIdByKarmaBalance(karmaBalance); - assertEq(tierId, 2); + function test_Success_When_LastTierIsUnlimited() public { + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](2); + newTiers[0] = KarmaTiers.Tier({ minKarma: 0, maxKarma: 100, name: "Bronze", txPerEpoch: 5 }); + newTiers[1] = + KarmaTiers.Tier({ minKarma: 101, maxKarma: type(uint256).max, name: "Unlimited", txPerEpoch: 100 }); + + vm.expectEmit(false, false, false, true); + emit KarmaTiers.TiersUpdated(); + + vm.prank(owner); + karmaTiers.updateTiers(newTiers); + + assertEq(karmaTiers.getTierCount(), 2); + assertEq(karmaTiers.getTierIdByKarmaBalance(500_000), 1); } - function test_GetTierIdByKarmaBalance_InGoldTier() public { - uint256 karmaBalance = 1200; - uint8 tierId = karmaTiers.getTierIdByKarmaBalance(karmaBalance); - assertEq(tierId, 2); // Since Gold is deactivated, should return 2 for Silver - } -} - -contract FuzzTests is KarmaTiersTest { - function setUp() public override { - super.setUp(); - } - - function testFuzz_AddTier_ValidInputs( - string calldata name, - uint256 minKarma, - uint256 maxKarma, - uint32 txPerEpoch - ) - public - { - vm.assume(bytes(name).length > 0 && bytes(name).length <= 32); - vm.assume(maxKarma == 0 || maxKarma > minKarma); - vm.assume(minKarma < type(uint256).max); - vm.assume(txPerEpoch > 0); - - vm.prank(deployer); - karmaTiers.addTier(name, minKarma, maxKarma, txPerEpoch); - - KarmaTiers.Tier memory tier = karmaTiers.getTierById(1); - assertEq(tier.name, name); - assertEq(tier.minKarma, minKarma); - assertEq(tier.maxKarma, maxKarma); - assertTrue(tier.active); - } - - function testFuzz_UpdateTier_ValidInputs( - string calldata initialName, - uint256 initialMinKarma, - uint256 initialMaxKarma, - string calldata newName, - uint256 newMinKarma, - uint256 newMaxKarma, - uint32 initialTxPerEpoch, - uint32 newTxPerEpoch - ) - public - { - // Setup constraints for initial tier - vm.assume(bytes(initialName).length > 0 && bytes(initialName).length <= 32); - vm.assume(initialMaxKarma == 0 || initialMaxKarma > initialMinKarma); - - // Setup constraints for new tier - vm.assume(bytes(newName).length > 0 && bytes(newName).length <= 32); - vm.assume(newMaxKarma == 0 || newMaxKarma > newMinKarma); - vm.assume(initialTxPerEpoch > 0); - vm.assume(newTxPerEpoch > 0); - - vm.startPrank(deployer); - karmaTiers.addTier(initialName, initialMinKarma, initialMaxKarma, initialTxPerEpoch); - karmaTiers.updateTier(1, newName, newMinKarma, newMaxKarma, newTxPerEpoch); - vm.stopPrank(); - - KarmaTiers.Tier memory tier = karmaTiers.getTierById(1); - assertEq(tier.name, newName); - assertEq(tier.minKarma, newMinKarma); - assertEq(tier.maxKarma, newMaxKarma); + function test_GetTierIdByKarmaBalance_EdgeCases() public { + KarmaTiers.Tier[] memory newTiers = new KarmaTiers.Tier[](2); + newTiers[0] = KarmaTiers.Tier({ minKarma: 0, maxKarma: 100, name: "Bronze", txPerEpoch: 5 }); + newTiers[1] = KarmaTiers.Tier({ minKarma: 101, maxKarma: 200, name: "Silver", txPerEpoch: 5 }); + + vm.prank(owner); + karmaTiers.updateTiers(newTiers); + + assertEq(karmaTiers.getTierIdByKarmaBalance(0), 0); + assertEq(karmaTiers.getTierIdByKarmaBalance(99), 0); + assertEq(karmaTiers.getTierIdByKarmaBalance(100), 0); + assertEq(karmaTiers.getTierIdByKarmaBalance(101), 1); + assertEq(karmaTiers.getTierIdByKarmaBalance(200), 1); + assertEq(karmaTiers.getTierIdByKarmaBalance(250), 1); } }