Skip to content

Commit 0c8bbe9

Browse files
committed
fix: ensure invalid thaw requests are ignored by getThawedTokens (OZ L-01)
Signed-off-by: Tomás Migone <[email protected]>
1 parent 5926d44 commit 0c8bbe9

File tree

5 files changed

+93
-11
lines changed

5 files changed

+93
-11
lines changed

Diff for: packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol

+3-1
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ interface IHorizonStakingMain {
254254
* @param shares The amount of shares being thawed
255255
* @param thawingUntil The timestamp until the stake is thawed
256256
* @param thawRequestId The ID of the thaw request
257+
* @param nonce The nonce of the thaw request
257258
*/
258259
event ThawRequestCreated(
259260
IHorizonStakingTypes.ThawRequestType indexed requestType,
@@ -262,7 +263,8 @@ interface IHorizonStakingMain {
262263
address owner,
263264
uint256 shares,
264265
uint64 thawingUntil,
265-
bytes32 thawRequestId
266+
bytes32 thawRequestId,
267+
uint256 nonce
266268
);
267269

268270
/**

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,8 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
10651065
_owner,
10661066
_shares,
10671067
_thawingUntil,
1068-
thawRequestId
1068+
thawRequestId,
1069+
_thawingNonce
10691070
);
10701071
return thawRequestId;
10711072
}
@@ -1173,6 +1174,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
11731174
uint256 tokens = 0;
11741175
bool validThawRequest = thawRequest.thawingNonce == thawingNonce;
11751176
if (validThawRequest) {
1177+
// sharesThawing cannot be zero if there is a valid thaw request so the next division is safe
11761178
tokens = (thawRequest.shares * tokensThawing) / sharesThawing;
11771179
tokensThawing = tokensThawing - tokens;
11781180
sharesThawing = sharesThawing - thawRequest.shares;

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

+11-7
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,18 @@ abstract contract HorizonStakingBase is
182182
bytes32 thawRequestId = thawRequestList.head;
183183
while (thawRequestId != bytes32(0)) {
184184
ThawRequest storage thawRequest = _getThawRequest(requestType, thawRequestId);
185-
if (thawRequest.thawingUntil <= block.timestamp) {
186-
uint256 tokens = (thawRequest.shares * tokensThawing) / sharesThawing;
187-
tokensThawing = tokensThawing - tokens;
188-
sharesThawing = sharesThawing - thawRequest.shares;
189-
thawedTokens = thawedTokens + tokens;
190-
} else {
191-
break;
185+
if (thawRequest.thawingNonce == prov.thawingNonce) {
186+
if (thawRequest.thawingUntil <= block.timestamp) {
187+
// sharesThawing cannot be zero if there is a valid thaw request so the next division is safe
188+
uint256 tokens = (thawRequest.shares * tokensThawing) / sharesThawing;
189+
tokensThawing = tokensThawing - tokens;
190+
sharesThawing = sharesThawing - thawRequest.shares;
191+
thawedTokens = thawedTokens + tokens;
192+
} else {
193+
break;
194+
}
192195
}
196+
193197
thawRequestId = thawRequest.next;
194198
}
195199
return thawedTokens;

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,8 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
428428
serviceProvider,
429429
thawingShares,
430430
uint64(block.timestamp + beforeProvision.thawingPeriod),
431-
expectedThawRequestId
431+
expectedThawRequestId,
432+
beforeProvision.thawingNonce
432433
);
433434
vm.expectEmit(address(staking));
434435
emit IHorizonStakingMain.ProvisionThawed(serviceProvider, verifier, tokens);
@@ -1019,7 +1020,8 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
10191020
beneficiary,
10201021
calcValues.thawingShares,
10211022
calcValues.thawingUntil,
1022-
calcValues.thawRequestId
1023+
calcValues.thawRequestId,
1024+
beforeValues.pool.thawingNonce
10231025
);
10241026
vm.expectEmit();
10251027
emit IHorizonStakingMain.TokensUndelegated(serviceProvider, verifier, delegator, calcValues.tokens);

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

+72
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,76 @@ contract HorizonStakingThawTest is HorizonStakingTest {
162162
);
163163
vm.assertEq(thawedTokens, thawAmount * thawSteps);
164164
}
165+
166+
function testThaw_GetThawedTokens_AfterProvisionFullySlashed(
167+
uint256 amount,
168+
uint64 thawingPeriod,
169+
uint256 thawAmount
170+
) public useIndexer useProvision(amount, 0, thawingPeriod) {
171+
// thaw some funds so there are some shares thawing and tokens thawing
172+
thawAmount = bound(thawAmount, 1, amount);
173+
_thaw(users.indexer, subgraphDataServiceAddress, thawAmount);
174+
175+
// skip to after the thawing period has passed
176+
skip(thawingPeriod + 1);
177+
178+
// slash all of it
179+
resetPrank(subgraphDataServiceAddress);
180+
_slash(users.indexer, subgraphDataServiceAddress, amount, 0);
181+
182+
// get the thawed tokens - should be zero now as the pool was reset
183+
uint256 thawedTokens = staking.getThawedTokens(
184+
ThawRequestType.Provision,
185+
users.indexer,
186+
subgraphDataServiceAddress,
187+
users.indexer
188+
);
189+
vm.assertEq(thawedTokens, 0);
190+
}
191+
192+
function testThaw_GetThawedTokens_AfterRecoveringProvision(
193+
uint256 amount,
194+
uint64 thawingPeriod,
195+
uint256 thawAmount
196+
) public useIndexer useProvision(amount, 0, thawingPeriod) {
197+
// thaw some funds so there are some shares thawing and tokens thawing
198+
thawAmount = bound(thawAmount, 1, amount);
199+
_thaw(users.indexer, subgraphDataServiceAddress, thawAmount);
200+
201+
// skip to after the thawing period has passed
202+
skip(thawingPeriod + 1);
203+
204+
// slash all of it
205+
resetPrank(subgraphDataServiceAddress);
206+
_slash(users.indexer, subgraphDataServiceAddress, amount, 0);
207+
208+
// get the thawed tokens - should be zero now as the pool was reset
209+
uint256 thawedTokens = staking.getThawedTokens(
210+
ThawRequestType.Provision,
211+
users.indexer,
212+
subgraphDataServiceAddress,
213+
users.indexer
214+
);
215+
vm.assertEq(thawedTokens, 0);
216+
217+
// put some funds back in
218+
resetPrank(users.indexer);
219+
_stake(amount);
220+
_addToProvision(users.indexer, subgraphDataServiceAddress, amount);
221+
222+
// thaw some more funds
223+
_thaw(users.indexer, subgraphDataServiceAddress, thawAmount);
224+
225+
// skip to after the thawing period has passed
226+
skip(block.timestamp + thawingPeriod + 1);
227+
228+
// get the thawed tokens - should be the amount we thawed
229+
thawedTokens = staking.getThawedTokens(
230+
ThawRequestType.Provision,
231+
users.indexer,
232+
subgraphDataServiceAddress,
233+
users.indexer
234+
);
235+
vm.assertEq(thawedTokens, thawAmount);
236+
}
165237
}

0 commit comments

Comments
 (0)