From 167055ac64d5aedbac264da23189bd7dbede8fd0 Mon Sep 17 00:00:00 2001 From: Miguel de Elias Date: Mon, 2 Dec 2024 15:45:25 -0300 Subject: [PATCH] fix: round down tokens thawing when slashing (TRST-H04) --- .../contracts/staking/HorizonStaking.sol | 7 ++-- .../HorizonStakingShared.t.sol | 4 +- .../horizon/test/staking/slash/slash.t.sol | 42 +++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/packages/horizon/contracts/staking/HorizonStaking.sol b/packages/horizon/contracts/staking/HorizonStaking.sol index 0e9e4bba2..151bcaff4 100644 --- a/packages/horizon/contracts/staking/HorizonStaking.sol +++ b/packages/horizon/contracts/staking/HorizonStaking.sol @@ -461,8 +461,8 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain { _graphToken().burnTokens(providerTokensSlashed - tokensVerifier); // Provision accounting - // TODO check for rounding issues - uint256 provisionFractionSlashed = (providerTokensSlashed * FIXED_POINT_PRECISION) / prov.tokens; + uint256 provisionFractionSlashed = (providerTokensSlashed * FIXED_POINT_PRECISION + prov.tokens - 1) / + prov.tokens; prov.tokensThawing = (prov.tokensThawing * (FIXED_POINT_PRECISION - provisionFractionSlashed)) / (FIXED_POINT_PRECISION); @@ -497,7 +497,8 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain { _graphToken().burnTokens(tokensToSlash); // Delegation pool accounting - uint256 delegationFractionSlashed = (tokensToSlash * FIXED_POINT_PRECISION) / pool.tokens; + uint256 delegationFractionSlashed = (tokensToSlash * FIXED_POINT_PRECISION + pool.tokens - 1) / + pool.tokens; pool.tokens = pool.tokens - tokensToSlash; pool.tokensThawing = (pool.tokensThawing * (FIXED_POINT_PRECISION - delegationFractionSlashed)) / diff --git a/packages/horizon/test/shared/horizon-staking/HorizonStakingShared.t.sol b/packages/horizon/test/shared/horizon-staking/HorizonStakingShared.t.sol index ac86c83f5..c960b19bd 100644 --- a/packages/horizon/test/shared/horizon-staking/HorizonStakingShared.t.sol +++ b/packages/horizon/test/shared/horizon-staking/HorizonStakingShared.t.sol @@ -1488,7 +1488,7 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest { uint256 tokensSlashed = calcValues.providerTokensSlashed + (isDelegationSlashingEnabled ? calcValues.delegationTokensSlashed : 0); uint256 provisionThawingTokens = (before.provision.tokensThawing * - (1e18 - ((calcValues.providerTokensSlashed * 1e18) / before.provision.tokens))) / (1e18); + (1e18 - ((calcValues.providerTokensSlashed * 1e18 + before.provision.tokens - 1) / before.provision.tokens))) / (1e18); // assert assertEq(afterProvision.tokens + calcValues.providerTokensSlashed, before.provision.tokens); @@ -1506,7 +1506,7 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest { (before.provision.sharesThawing != 0 && afterProvision.sharesThawing == 0) ? before.provision.thawingNonce + 1 : before.provision.thawingNonce); if (isDelegationSlashingEnabled) { uint256 poolThawingTokens = (before.pool.tokensThawing * - (1e18 - ((calcValues.delegationTokensSlashed * 1e18) / before.pool.tokens))) / (1e18); + (1e18 - ((calcValues.delegationTokensSlashed * 1e18 + before.pool.tokens - 1) / before.pool.tokens))) / (1e18); assertEq(afterPool.tokens + calcValues.delegationTokensSlashed, before.pool.tokens); assertEq(afterPool.shares, before.pool.shares); assertEq(afterPool.tokensThawing, poolThawingTokens); diff --git a/packages/horizon/test/staking/slash/slash.t.sol b/packages/horizon/test/staking/slash/slash.t.sol index 7c7933419..a74403ed7 100644 --- a/packages/horizon/test/staking/slash/slash.t.sol +++ b/packages/horizon/test/staking/slash/slash.t.sol @@ -140,4 +140,46 @@ contract HorizonStakingSlashTest is HorizonStakingTest { vm.startPrank(subgraphDataServiceAddress); _slash(users.indexer, subgraphDataServiceAddress, tokens + delegationTokens, 0); } + + function testSlash_RoundDown_TokensThawing_Provision() public useIndexer { + uint256 tokens = 1 ether + 1; + _useProvision(subgraphDataServiceAddress, tokens, MAX_PPM, MAX_THAWING_PERIOD); + + _thaw(users.indexer, subgraphDataServiceAddress, tokens); + + resetPrank(subgraphDataServiceAddress); + _slash(users.indexer, subgraphDataServiceAddress, 1, 0); + + resetPrank(users.indexer); + Provision memory provision = staking.getProvision(users.indexer, subgraphDataServiceAddress); + assertEq(provision.tokens, tokens - 1); + // Tokens thawing should be rounded down + assertEq(provision.tokensThawing, tokens - 2); + } + + function testSlash_RoundDown_TokensThawing_Delegation( + uint256 tokens + ) public useIndexer useProvision(tokens, MAX_PPM, 0) useDelegationSlashing { + resetPrank(users.delegator); + uint256 delegationTokens = 1 ether + 1; + _delegate(users.indexer, subgraphDataServiceAddress, delegationTokens, 0); + + DelegationInternal memory delegation = _getStorage_Delegation( + users.indexer, + subgraphDataServiceAddress, + users.delegator, + false + ); + _undelegate(users.indexer, subgraphDataServiceAddress, delegation.shares); + + resetPrank(subgraphDataServiceAddress); + // Slash 1 token from delegation + _slash(users.indexer, subgraphDataServiceAddress, tokens + 1, 0); + + resetPrank(users.delegator); + DelegationPool memory pool = staking.getDelegationPool(users.indexer, subgraphDataServiceAddress); + assertEq(pool.tokens, delegationTokens - 1); + // Tokens thawing should be rounded down + assertEq(pool.tokensThawing, delegationTokens - 2); + } }