Skip to content

Commit c163ec4

Browse files
committed
fix: use solidity division instead of fixed point scaling (OZ L-04)
Signed-off-by: Tomás Migone <[email protected]>
1 parent b886ab4 commit c163ec4

File tree

4 files changed

+53
-49
lines changed

4 files changed

+53
-49
lines changed

Diff for: packages/horizon/contracts/staking/HorizonStaking.sol

+4-15
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
3232
using PPMMath for uint256;
3333
using LinkedList for LinkedList.List;
3434

35-
/// @dev Fixed point precision
36-
uint256 private constant FIXED_POINT_PRECISION = 1e18;
37-
3835
/// @dev Maximum number of simultaneous stake thaw requests (per provision) or undelegations (per delegation)
3936
uint256 private constant MAX_THAW_REQUESTS = 1_000;
4037

@@ -441,12 +438,8 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
441438
// Burn remainder
442439
_graphToken().burnTokens(providerTokensSlashed - tokensVerifier);
443440

444-
// Provision accounting
445-
uint256 provisionFractionSlashed = (providerTokensSlashed * FIXED_POINT_PRECISION + prov.tokens - 1) /
446-
prov.tokens;
447-
prov.tokensThawing =
448-
(prov.tokensThawing * (FIXED_POINT_PRECISION - provisionFractionSlashed)) /
449-
(FIXED_POINT_PRECISION);
441+
// Provision accounting - round down, 1 wei max precision loss
442+
prov.tokensThawing = (prov.tokensThawing * (prov.tokens - providerTokensSlashed)) / prov.tokens;
450443
prov.tokens = prov.tokens - providerTokensSlashed;
451444

452445
// If the slashing leaves the thawing shares with no thawing tokens, cancel pending thawings by:
@@ -477,13 +470,9 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
477470
// Burn tokens
478471
_graphToken().burnTokens(tokensToSlash);
479472

480-
// Delegation pool accounting
481-
uint256 delegationFractionSlashed = (tokensToSlash * FIXED_POINT_PRECISION + pool.tokens - 1) /
482-
pool.tokens;
473+
// Delegation pool accounting - round down, 1 wei max precision loss
474+
pool.tokensThawing = (pool.tokensThawing * (pool.tokens - tokensToSlash)) / pool.tokens;
483475
pool.tokens = pool.tokens - tokensToSlash;
484-
pool.tokensThawing =
485-
(pool.tokensThawing * (FIXED_POINT_PRECISION - delegationFractionSlashed)) /
486-
FIXED_POINT_PRECISION;
487476

488477
// If the slashing leaves the thawing shares with no thawing tokens, cancel pending thawings by:
489478
// - deleting all thawing shares

Diff for: packages/horizon/test/shared/horizon-staking/HorizonStakingShared.t.sol

+3-6
Original file line numberDiff line numberDiff line change
@@ -1525,9 +1525,7 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
15251525
uint256 tokensSlashed = calcValues.providerTokensSlashed +
15261526
(isDelegationSlashingEnabled ? calcValues.delegationTokensSlashed : 0);
15271527
uint256 provisionThawingTokens = (before.provision.tokensThawing *
1528-
(1e18 -
1529-
((calcValues.providerTokensSlashed * 1e18 + before.provision.tokens - 1) /
1530-
before.provision.tokens))) / (1e18);
1528+
(before.provision.tokens - calcValues.providerTokensSlashed)) / before.provision.tokens;
15311529

15321530
// assert
15331531
assertEq(afterProvision.tokens + calcValues.providerTokensSlashed, before.provision.tokens);
@@ -1548,9 +1546,8 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
15481546
);
15491547
if (isDelegationSlashingEnabled) {
15501548
uint256 poolThawingTokens = (before.pool.tokensThawing *
1551-
(1e18 -
1552-
((calcValues.delegationTokensSlashed * 1e18 + before.pool.tokens - 1) / before.pool.tokens))) /
1553-
(1e18);
1549+
(before.pool.tokens - calcValues.delegationTokensSlashed)) /
1550+
before.pool.tokens;
15541551
assertEq(afterPool.tokens + calcValues.delegationTokensSlashed, before.pool.tokens);
15551552
assertEq(afterPool.shares, before.pool.shares);
15561553
assertEq(afterPool.tokensThawing, poolThawingTokens);

Diff for: packages/horizon/test/staking/provision/parameters.t.sol

+9-3
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,17 @@ contract HorizonStakingProvisionParametersTest is HorizonStakingTest {
7878
vm.assume(amount > 0);
7979
vm.assume(amount <= MAX_STAKING_TOKENS);
8080
vm.assume(maxVerifierCut <= MAX_PPM);
81-
81+
8282
// create provision with initial parameters
8383
uint32 initialMaxVerifierCut = 1000;
84-
uint64 initialThawingPeriod = 14 days; // Max thawing period is 28 days
85-
_createProvision(users.indexer, subgraphDataServiceAddress, amount, initialMaxVerifierCut, initialThawingPeriod);
84+
uint64 initialThawingPeriod = 14 days; // Max thawing period is 28 days
85+
_createProvision(
86+
users.indexer,
87+
subgraphDataServiceAddress,
88+
amount,
89+
initialMaxVerifierCut,
90+
initialThawingPeriod
91+
);
8692

8793
// change the max thawing period allowed so that the initial thawing period is not valid anymore
8894
uint64 newMaxThawingPeriod = 7 days;

Diff for: packages/horizon/test/staking/slash/slash.t.sol

+37-25
Original file line numberDiff line numberDiff line change
@@ -141,45 +141,57 @@ contract HorizonStakingSlashTest is HorizonStakingTest {
141141
_slash(users.indexer, subgraphDataServiceAddress, tokens + delegationTokens, 0);
142142
}
143143

144-
function testSlash_RoundDown_TokensThawing_Provision() public useIndexer {
145-
uint256 tokens = 1 ether + 1;
144+
function testSlash_RoundDown_TokensThawing_Provision(
145+
uint256 tokens,
146+
uint256 slashTokens,
147+
uint256 tokensToThaw
148+
) public useIndexer {
149+
vm.assume(slashTokens <= tokens);
150+
vm.assume(tokensToThaw <= tokens);
151+
vm.assume(tokensToThaw > 0);
152+
146153
_useProvision(subgraphDataServiceAddress, tokens, MAX_PPM, MAX_THAWING_PERIOD);
154+
_thaw(users.indexer, subgraphDataServiceAddress, tokensToThaw);
147155

148-
_thaw(users.indexer, subgraphDataServiceAddress, tokens);
156+
Provision memory beforeProvision = staking.getProvision(users.indexer, subgraphDataServiceAddress);
149157

150158
resetPrank(subgraphDataServiceAddress);
151-
_slash(users.indexer, subgraphDataServiceAddress, 1, 0);
159+
_slash(users.indexer, subgraphDataServiceAddress, slashTokens, 0);
152160

153-
resetPrank(users.indexer);
154-
Provision memory provision = staking.getProvision(users.indexer, subgraphDataServiceAddress);
155-
assertEq(provision.tokens, tokens - 1);
156-
// Tokens thawing should be rounded down
157-
assertEq(provision.tokensThawing, tokens - 2);
161+
Provision memory afterProvision = staking.getProvision(users.indexer, subgraphDataServiceAddress);
162+
assertEq(afterProvision.tokens, beforeProvision.tokens - slashTokens);
163+
assertEq(
164+
afterProvision.tokensThawing,
165+
(beforeProvision.tokensThawing * (beforeProvision.tokens - slashTokens)) / beforeProvision.tokens
166+
);
158167
}
159168

160169
function testSlash_RoundDown_TokensThawing_Delegation(
161-
uint256 tokens
170+
uint256 tokens,
171+
uint256 delegationTokensToSlash,
172+
uint256 delegationTokensToUndelegate
162173
) public useIndexer useProvision(tokens, MAX_PPM, 0) useDelegationSlashing {
174+
uint256 delegationTokens = 10 ether;
175+
176+
vm.assume(delegationTokensToSlash <= delegationTokens);
177+
vm.assume(delegationTokensToUndelegate <= delegationTokens);
178+
vm.assume(delegationTokensToUndelegate > 0);
179+
163180
resetPrank(users.delegator);
164-
uint256 delegationTokens = 1 ether + 1;
165181
_delegate(users.indexer, subgraphDataServiceAddress, delegationTokens, 0);
182+
_undelegate(users.indexer, subgraphDataServiceAddress, delegationTokensToUndelegate);
166183

167-
DelegationInternal memory delegation = _getStorage_Delegation(
168-
users.indexer,
169-
subgraphDataServiceAddress,
170-
users.delegator,
171-
false
172-
);
173-
_undelegate(users.indexer, subgraphDataServiceAddress, delegation.shares);
184+
DelegationPool memory beforePool = staking.getDelegationPool(users.indexer, subgraphDataServiceAddress);
174185

186+
// Slash
175187
resetPrank(subgraphDataServiceAddress);
176-
// Slash 1 token from delegation
177-
_slash(users.indexer, subgraphDataServiceAddress, tokens + 1, 0);
188+
_slash(users.indexer, subgraphDataServiceAddress, tokens + delegationTokensToSlash, 0);
178189

179-
resetPrank(users.delegator);
180-
DelegationPool memory pool = staking.getDelegationPool(users.indexer, subgraphDataServiceAddress);
181-
assertEq(pool.tokens, delegationTokens - 1);
182-
// Tokens thawing should be rounded down
183-
assertEq(pool.tokensThawing, delegationTokens - 2);
190+
DelegationPool memory afterPool = staking.getDelegationPool(users.indexer, subgraphDataServiceAddress);
191+
assertEq(afterPool.tokens, beforePool.tokens - delegationTokensToSlash);
192+
assertEq(
193+
afterPool.tokensThawing,
194+
(beforePool.tokensThawing * (beforePool.tokens - delegationTokensToSlash)) / beforePool.tokens
195+
);
184196
}
185197
}

0 commit comments

Comments
 (0)