Skip to content

Commit 91cda56

Browse files
committed
fix: added minimum delegation for provisions (TRST-M03)
1 parent f254897 commit 91cda56

File tree

11 files changed

+105
-49
lines changed

11 files changed

+105
-49
lines changed

packages/horizon/contracts/staking/HorizonStaking.sol

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
4141
/// @dev Address of the staking extension contract
4242
address private immutable STAKING_EXTENSION_ADDRESS;
4343

44+
/// @dev Minimum amount of delegation.
45+
uint256 private constant MIN_DELEGATION = 1e18;
46+
4447
/**
4548
* @notice Checks that the caller is authorized to operate over a provision.
4649
* @param serviceProvider The address of the service provider.
@@ -842,6 +845,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
842845
* have been done before calling this function.
843846
*/
844847
function _delegate(address _serviceProvider, address _verifier, uint256 _tokens, uint256 _minSharesOut) private {
848+
require(_tokens >= MIN_DELEGATION, HorizonStakingInsufficientTokens(_tokens, MIN_DELEGATION));
845849
require(
846850
_provisions[_serviceProvider][_verifier].createdAt != 0,
847851
HorizonStakingInvalidProvision(_serviceProvider, _verifier)
@@ -911,6 +915,13 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
911915

912916
pool.shares = pool.shares - _shares;
913917
delegation.shares = delegation.shares - _shares;
918+
if (delegation.shares != 0) {
919+
uint256 remainingTokens = (delegation.shares * (pool.tokens - pool.tokensThawing)) / pool.shares;
920+
require(
921+
remainingTokens >= MIN_DELEGATION,
922+
HorizonStakingInsufficientTokens(remainingTokens, MIN_DELEGATION)
923+
);
924+
}
914925

915926
bytes32 thawRequestId = _createThawRequest(
916927
_requestType,

packages/horizon/test/escrow/collect.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ contract GraphEscrowCollectTest is GraphEscrowTest {
101101
uint256 tokensDelegatoion = tokens * delegationFeeCut / MAX_PPM;
102102
vm.assume(tokensDataService < tokens - tokensProtocol - tokensDelegatoion);
103103

104-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
104+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
105105
resetPrank(users.delegator);
106106
_delegate(users.indexer, subgraphDataServiceAddress, delegationTokens, 0);
107107

packages/horizon/test/payments/GraphPayments.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ contract GraphPaymentsTest is HorizonStakingSharedTest {
119119
address escrowAddress = address(escrow);
120120

121121
// Delegate tokens
122-
tokensDelegate = bound(tokensDelegate, 1, MAX_STAKING_TOKENS);
122+
tokensDelegate = bound(tokensDelegate, MIN_DELEGATION, MAX_STAKING_TOKENS);
123123
vm.startPrank(users.delegator);
124124
_delegate(users.indexer, subgraphDataServiceAddress, tokensDelegate, 0);
125125

packages/horizon/test/staking/HorizonStaking.t.sol

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ contract HorizonStakingTest is HorizonStakingSharedTest {
3131
modifier useDelegation(uint256 delegationAmount) {
3232
address msgSender;
3333
(, msgSender, ) = vm.readCallers();
34-
vm.assume(delegationAmount > 1);
34+
vm.assume(delegationAmount >= MIN_DELEGATION);
3535
vm.assume(delegationAmount <= MAX_STAKING_TOKENS);
3636
vm.startPrank(users.delegator);
3737
_delegate(users.indexer, subgraphDataServiceAddress, delegationAmount, 0);
@@ -56,4 +56,30 @@ contract HorizonStakingTest is HorizonStakingSharedTest {
5656
resetPrank(msgSender);
5757
_;
5858
}
59+
60+
modifier useUndelegate(uint256 shares) {
61+
resetPrank(users.delegator);
62+
63+
DelegationPoolInternalTest memory pool = _getStorage_DelegationPoolInternal(
64+
users.indexer,
65+
subgraphDataServiceAddress,
66+
false
67+
);
68+
DelegationInternal memory delegation = _getStorage_Delegation(
69+
users.indexer,
70+
subgraphDataServiceAddress,
71+
users.delegator,
72+
false
73+
);
74+
75+
shares = bound(shares, 1, delegation.shares);
76+
uint256 tokens = (shares * (pool.tokens - pool.tokensThawing)) / pool.shares;
77+
if (shares < delegation.shares) {
78+
uint256 remainingTokens = (shares * (pool.tokens - pool.tokensThawing - tokens)) / pool.shares;
79+
vm.assume(remainingTokens >= MIN_DELEGATION);
80+
}
81+
82+
_undelegate(users.indexer, subgraphDataServiceAddress, shares);
83+
_;
84+
}
5985
}

packages/horizon/test/staking/delegation/addToPool.t.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ contract HorizonStakingDelegationAddToPoolTest is HorizonStakingTest {
1010

1111
modifier useValidDelegationAmount(uint256 tokens) {
1212
vm.assume(tokens <= MAX_STAKING_TOKENS);
13+
vm.assume(tokens >= MIN_DELEGATION);
1314
_;
1415
}
1516

@@ -93,7 +94,7 @@ contract HorizonStakingDelegationAddToPoolTest is HorizonStakingTest {
9394
uint256 recoverAmount
9495
) public useIndexer useProvision(tokens, 0, 0) useDelegationSlashing() {
9596
recoverAmount = bound(recoverAmount, 1, MAX_STAKING_TOKENS);
96-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
97+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
9798

9899
// create delegation pool
99100
resetPrank(users.delegator);
@@ -116,7 +117,7 @@ contract HorizonStakingDelegationAddToPoolTest is HorizonStakingTest {
116117
uint256 recoverAmount
117118
) public useIndexer useProvision(tokens, 0, 0) useDelegationSlashing() {
118119
recoverAmount = bound(recoverAmount, 1, MAX_STAKING_TOKENS);
119-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
120+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
120121

121122
// create delegation pool
122123
resetPrank(users.delegator);

packages/horizon/test/staking/delegation/delegate.t.sol

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,25 @@ contract HorizonStakingDelegateTest is HorizonStakingTest {
5959
staking.delegate(users.indexer, subgraphDataServiceAddress, 0, 0);
6060
}
6161

62+
function testDelegate_RevertWhen_UnderMinDelegation(
63+
uint256 amount,
64+
uint256 delegationAmount
65+
) public useIndexer useProvision(amount, 0, 0) {
66+
delegationAmount = bound(delegationAmount, 1, MIN_DELEGATION - 1);
67+
vm.startPrank(users.delegator);
68+
token.approve(address(staking), delegationAmount);
69+
bytes memory expectedError = abi.encodeWithSelector(
70+
IHorizonStakingMain.HorizonStakingInsufficientTokens.selector,
71+
delegationAmount,
72+
MIN_DELEGATION
73+
);
74+
vm.expectRevert(expectedError);
75+
staking.delegate(users.indexer, subgraphDataServiceAddress, delegationAmount, 0);
76+
}
77+
6278
function testDelegate_LegacySubgraphService(uint256 amount, uint256 delegationAmount) public useIndexer {
6379
amount = bound(amount, 1 ether, MAX_STAKING_TOKENS);
64-
delegationAmount = bound(delegationAmount, 1, MAX_STAKING_TOKENS);
80+
delegationAmount = bound(delegationAmount, MIN_DELEGATION, MAX_STAKING_TOKENS);
6581
_createProvision(users.indexer, subgraphDataServiceLegacyAddress, amount, 0, 0);
6682

6783
resetPrank(users.delegator);
@@ -72,7 +88,7 @@ contract HorizonStakingDelegateTest is HorizonStakingTest {
7288
uint256 tokens,
7389
uint256 delegationTokens
7490
) public useIndexer useProvision(tokens, 0, 0) useDelegationSlashing() {
75-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
91+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
7692

7793
resetPrank(users.delegator);
7894
_delegate(users.indexer, subgraphDataServiceAddress, delegationTokens, 0);
@@ -96,7 +112,7 @@ contract HorizonStakingDelegateTest is HorizonStakingTest {
96112
uint256 tokens,
97113
uint256 delegationTokens
98114
) public useIndexer useProvision(tokens, 0, 0) useDelegationSlashing() {
99-
delegationTokens = bound(delegationTokens, 2, MAX_STAKING_TOKENS);
115+
delegationTokens = bound(delegationTokens, MIN_DELEGATION * 2, MAX_STAKING_TOKENS);
100116

101117
resetPrank(users.delegator);
102118
_delegate(users.indexer, subgraphDataServiceAddress, delegationTokens, 0);
@@ -126,7 +142,7 @@ contract HorizonStakingDelegateTest is HorizonStakingTest {
126142
uint256 recoverAmount
127143
) public useIndexer useProvision(tokens, 0, 0) useDelegationSlashing() {
128144
recoverAmount = bound(recoverAmount, 1, MAX_STAKING_TOKENS);
129-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
145+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
130146

131147
// create delegation pool
132148
resetPrank(users.delegator);

packages/horizon/test/staking/delegation/redelegate.t.sol

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,6 @@ import { HorizonStakingTest } from "../HorizonStaking.t.sol";
99

1010
contract HorizonStakingWithdrawDelegationTest is HorizonStakingTest {
1111

12-
/*
13-
* MODIFIERS
14-
*/
15-
16-
modifier useUndelegate(uint256 shares) {
17-
resetPrank(users.delegator);
18-
DelegationInternal memory delegation = _getStorage_Delegation(users.indexer, subgraphDataServiceAddress, users.delegator, false);
19-
shares = bound(shares, 1, delegation.shares);
20-
21-
_undelegate(users.indexer, subgraphDataServiceAddress, shares);
22-
_;
23-
}
24-
2512
/*
2613
* HELPERS
2714
*/

packages/horizon/test/staking/delegation/undelegate.t.sol

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ contract HorizonStakingUndelegateTest is HorizonStakingTest {
3232
uint256 undelegateSteps
3333
) public useIndexer useProvision(amount, 0, 0) {
3434
undelegateSteps = bound(undelegateSteps, 1, 10);
35-
delegationAmount = bound(delegationAmount, 10 wei, MAX_STAKING_TOKENS);
35+
delegationAmount = bound(delegationAmount, MIN_DELEGATION * undelegateSteps, MAX_STAKING_TOKENS);
3636

3737
resetPrank(users.delegator);
3838
_delegate(users.indexer, subgraphDataServiceAddress, delegationAmount, 0);
@@ -44,9 +44,17 @@ contract HorizonStakingUndelegateTest is HorizonStakingTest {
4444
);
4545

4646
uint256 undelegateAmount = delegation.shares / undelegateSteps;
47-
for (uint i = 0; i < undelegateSteps; i++) {
47+
for (uint i = 0; i < undelegateSteps - 1; i++) {
4848
_undelegate(users.indexer, subgraphDataServiceAddress, undelegateAmount);
4949
}
50+
51+
delegation = _getStorage_Delegation(
52+
users.indexer,
53+
subgraphDataServiceAddress,
54+
users.delegator,
55+
false
56+
);
57+
_undelegate(users.indexer, subgraphDataServiceAddress, delegation.shares);
5058
}
5159

5260
function testUndelegate_WithBeneficiary(
@@ -60,6 +68,29 @@ contract HorizonStakingUndelegateTest is HorizonStakingTest {
6068
_undelegateWithBeneficiary(users.indexer, subgraphDataServiceAddress, delegation.shares, beneficiary);
6169
}
6270

71+
function testUndelegate_RevertWhen_InsuficientTokens(
72+
uint256 amount,
73+
uint256 delegationAmount,
74+
uint256 undelegateAmount
75+
) public useIndexer useProvision(amount, 0, 0) useDelegation(delegationAmount) {
76+
undelegateAmount = bound(undelegateAmount, 1, delegationAmount);
77+
resetPrank(users.delegator);
78+
DelegationInternal memory delegation = _getStorage_Delegation(
79+
users.indexer,
80+
subgraphDataServiceAddress,
81+
users.delegator,
82+
false
83+
);
84+
undelegateAmount = bound(undelegateAmount, delegation.shares - MIN_DELEGATION + 1, delegation.shares - 1);
85+
bytes memory expectedError = abi.encodeWithSelector(
86+
IHorizonStakingMain.HorizonStakingInsufficientTokens.selector,
87+
delegation.shares - undelegateAmount,
88+
MIN_DELEGATION
89+
);
90+
vm.expectRevert(expectedError);
91+
staking.undelegate(users.indexer, subgraphDataServiceAddress, undelegateAmount);
92+
}
93+
6394
function testUndelegate_RevertWhen_TooManyUndelegations()
6495
public
6596
useIndexer
@@ -112,7 +143,7 @@ contract HorizonStakingUndelegateTest is HorizonStakingTest {
112143

113144
function testUndelegate_LegacySubgraphService(uint256 amount, uint256 delegationAmount) public useIndexer {
114145
amount = bound(amount, 1, MAX_STAKING_TOKENS);
115-
delegationAmount = bound(delegationAmount, 1, MAX_STAKING_TOKENS);
146+
delegationAmount = bound(delegationAmount, MIN_DELEGATION, MAX_STAKING_TOKENS);
116147
_createProvision(users.indexer, subgraphDataServiceLegacyAddress, amount, 0, 0);
117148

118149
resetPrank(users.delegator);
@@ -131,7 +162,7 @@ contract HorizonStakingUndelegateTest is HorizonStakingTest {
131162
uint256 tokens,
132163
uint256 delegationTokens
133164
) public useIndexer useProvision(tokens, 0, 0) useDelegationSlashing() {
134-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
165+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
135166

136167
resetPrank(users.delegator);
137168
_delegate(users.indexer, subgraphDataServiceAddress, delegationTokens, 0);
@@ -162,7 +193,7 @@ contract HorizonStakingUndelegateTest is HorizonStakingTest {
162193
uint256 tokens,
163194
uint256 delegationTokens
164195
) public useIndexer useProvision(tokens, 0, 0) useDelegationSlashing {
165-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
196+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
166197

167198
// delegate
168199
resetPrank(users.delegator);

packages/horizon/test/staking/delegation/withdraw.t.sol

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,6 @@ import { LinkedList } from "../../../contracts/libraries/LinkedList.sol";
1010
import { HorizonStakingTest } from "../HorizonStaking.t.sol";
1111

1212
contract HorizonStakingWithdrawDelegationTest is HorizonStakingTest {
13-
/*
14-
* MODIFIERS
15-
*/
16-
17-
modifier useUndelegate(uint256 shares) {
18-
resetPrank(users.delegator);
19-
DelegationInternal memory delegation = _getStorage_Delegation(
20-
users.indexer,
21-
subgraphDataServiceAddress,
22-
users.delegator,
23-
false
24-
);
25-
shares = bound(shares, 1, delegation.shares);
26-
27-
_undelegate(users.indexer, subgraphDataServiceAddress, shares);
28-
_;
29-
}
3013

3114
/*
3215
* TESTS
@@ -81,7 +64,7 @@ contract HorizonStakingWithdrawDelegationTest is HorizonStakingTest {
8164
}
8265

8366
function testWithdrawDelegation_LegacySubgraphService(uint256 delegationAmount) public useIndexer {
84-
delegationAmount = bound(delegationAmount, 1, MAX_STAKING_TOKENS);
67+
delegationAmount = bound(delegationAmount, MIN_DELEGATION, MAX_STAKING_TOKENS);
8568
_createProvision(users.indexer, subgraphDataServiceLegacyAddress, 10_000_000 ether, 0, MAX_THAWING_PERIOD);
8669

8770
resetPrank(users.delegator);
@@ -111,7 +94,7 @@ contract HorizonStakingWithdrawDelegationTest is HorizonStakingTest {
11194
uint256 tokens,
11295
uint256 delegationTokens
11396
) public useIndexer useProvision(tokens, 0, MAX_THAWING_PERIOD) useDelegationSlashing() {
114-
delegationTokens = bound(delegationTokens, 2, MAX_STAKING_TOKENS);
97+
delegationTokens = bound(delegationTokens, MIN_DELEGATION * 2, MAX_STAKING_TOKENS);
11598

11699
resetPrank(users.delegator);
117100
_delegate(users.indexer, subgraphDataServiceAddress, delegationTokens, 0);

packages/horizon/test/staking/slash/slash.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ contract HorizonStakingSlashTest is HorizonStakingTest {
5454
uint256 delegationTokens
5555
) public useIndexer useProvision(tokens, MAX_PPM, 0) {
5656
vm.assume(slashTokens > tokens);
57-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
57+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
5858
verifierCutAmount = bound(verifierCutAmount, 0, MAX_PPM);
5959
vm.assume(verifierCutAmount <= tokens);
6060

@@ -71,7 +71,7 @@ contract HorizonStakingSlashTest is HorizonStakingTest {
7171
uint256 verifierCutAmount,
7272
uint256 delegationTokens
7373
) public useIndexer useProvision(tokens, MAX_PPM, 0) useDelegationSlashing() {
74-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
74+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
7575
slashTokens = bound(slashTokens, tokens + 1, tokens + 1 + delegationTokens);
7676
verifierCutAmount = bound(verifierCutAmount, 0, tokens);
7777

@@ -110,7 +110,7 @@ contract HorizonStakingSlashTest is HorizonStakingTest {
110110
uint256 tokens,
111111
uint256 delegationTokens
112112
) public useIndexer useProvision(tokens, MAX_PPM, 0) useDelegationSlashing {
113-
delegationTokens = bound(delegationTokens, 1, MAX_STAKING_TOKENS);
113+
delegationTokens = bound(delegationTokens, MIN_DELEGATION, MAX_STAKING_TOKENS);
114114

115115
resetPrank(users.delegator);
116116
_delegate(users.indexer, subgraphDataServiceAddress, delegationTokens, 0);

0 commit comments

Comments
 (0)