Skip to content

Commit 5b238d9

Browse files
authored
Prevent over-allocated runners from moving stake and add recovery guard (#451)
* Prevent over-allocated runners from moving stake and add recovery guard * update abi
1 parent 638aaa0 commit 5b238d9

5 files changed

Lines changed: 68 additions & 16 deletions

File tree

contracts/RewardsBooster.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,7 @@ contract RewardsBooster is Initializable, OwnableUpgradeable, IRewardsBooster, S
10181018

10191019
runnerDeplReward.accRewardsPerToken = accRewardsPerAllocatedToken;
10201020
uint256 burnt;
1021+
sa.syncOverflowStatus(_runner);
10211022
uint256 totalOverflowTime = sa.overAllocationTime(_runner);
10221023
(reward, burnt) = _fixRewardsWithMissedLaborAndOverflow(
10231024
reward,
@@ -1078,6 +1079,7 @@ contract RewardsBooster is Initializable, OwnableUpgradeable, IRewardsBooster, S
10781079

10791080
runnerDeplReward.accRewardsPerToken = accRewardsPerAllocatedToken;
10801081
uint256 burnt;
1082+
sa.syncOverflowStatus(_runner);
10811083
uint256 totalOverflowTime = sa.overAllocationTime(_runner);
10821084
(reward, burnt) = _fixRewardsWithMissedLaborAndOverflow(
10831085
reward,

contracts/StakingAllocation.sol

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,7 @@ contract StakingAllocation is IStakingAllocation, Initializable, OwnableUpgradea
8080
.getEffectiveTotalStake(_runner);
8181

8282
if (ia.overflowAt == 0 && ia.total < ia.used) {
83-
// new overflow
84-
emit OverAllocationStarted(_runner, block.timestamp);
85-
86-
ia.overflowAt = block.timestamp;
83+
_startOverAllocation(ia, _runner);
8784
} else if (ia.overflowAt != 0 && ia.total >= ia.used) {
8885
// recover from overflow
8986
emit OverAllocationEnded(_runner, block.timestamp, block.timestamp - ia.overflowAt);
@@ -97,7 +94,7 @@ contract StakingAllocation is IStakingAllocation, Initializable, OwnableUpgradea
9794
require(_isAuth(_runner), 'SAL02');
9895
require(
9996
IProjectRegistry(settings.getContractAddress(SQContracts.ProjectRegistry))
100-
.isServiceAvailable(_deployment, _runner),
97+
.isServiceAvailable(_deployment, _runner),
10198
'SAL05'
10299
);
103100

@@ -114,10 +111,17 @@ contract StakingAllocation is IStakingAllocation, Initializable, OwnableUpgradea
114111
_removeAllocation(_deployment, _runner, _amount);
115112
}
116113

117-
function moveAllocation(bytes32 _deploymentFrom, bytes32 _deploymentTo, address _runner, uint256 _amount) external {
114+
function moveAllocation(
115+
bytes32 _deploymentFrom,
116+
bytes32 _deploymentTo,
117+
address _runner,
118+
uint256 _amount
119+
) external {
118120
require(_isAuth(_runner), 'SAL02');
119121
require(allocatedTokens[_runner][_deploymentFrom] >= _amount, 'SAL04');
120122
require(_deploymentFrom != _deploymentTo, 'SAL07');
123+
RunnerAllocation storage ia = _runnerAllocations[_runner];
124+
require(ia.total >= ia.used, 'SAL03');
121125

122126
_removeAllocation(_deploymentFrom, _runner, _amount);
123127
_addAllocation(_deploymentTo, _runner, _amount);
@@ -176,12 +180,19 @@ contract StakingAllocation is IStakingAllocation, Initializable, OwnableUpgradea
176180
return _runnerAllocations[_runner];
177181
}
178182

183+
function syncOverflowStatus(address _runner) external override {
184+
_syncOverflowStatus(_runner);
185+
}
186+
179187
/**
180188
* @notice this returns the accumulated overflowTime of given runner
181189
*/
182190
function overAllocationTime(address _runner) external view returns (uint256) {
183191
RunnerAllocation memory ia = _runnerAllocations[_runner];
184192
if (ia.total < ia.used) {
193+
if (ia.overflowAt == 0) {
194+
return ia.overflowTime;
195+
}
185196
return ia.overflowTime + block.timestamp - ia.overflowAt;
186197
} else {
187198
return ia.overflowTime;
@@ -199,4 +210,16 @@ contract StakingAllocation is IStakingAllocation, Initializable, OwnableUpgradea
199210
address controller = indexerRegistry.getController(_runner);
200211
return msg.sender == _runner || msg.sender == controller;
201212
}
213+
214+
function _syncOverflowStatus(address _runner) private {
215+
RunnerAllocation storage ia = _runnerAllocations[_runner];
216+
if (ia.overflowAt == 0 && ia.total < ia.used) {
217+
_startOverAllocation(ia, _runner);
218+
}
219+
}
220+
221+
function _startOverAllocation(RunnerAllocation storage ia, address _runner) private {
222+
emit OverAllocationStarted(_runner, block.timestamp);
223+
ia.overflowAt = block.timestamp;
224+
}
202225
}

contracts/interfaces/IStakingAllocation.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ interface IStakingAllocation {
1616

1717
function allocatedTokens(address _runner, bytes32 _deployment) external view returns (uint256);
1818

19+
function syncOverflowStatus(address _runner) external;
20+
1921
function runnerAllocation(address _runner) external view returns (RunnerAllocation memory);
2022

2123
function overAllocationTime(address _runner) external view returns (uint256);

publish/ABI/StakingAllocation.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,19 @@
411411
"stateMutability": "nonpayable",
412412
"type": "function"
413413
},
414+
{
415+
"inputs": [
416+
{
417+
"internalType": "address",
418+
"name": "_runner",
419+
"type": "address"
420+
}
421+
],
422+
"name": "syncOverflowStatus",
423+
"outputs": [],
424+
"stateMutability": "nonpayable",
425+
"type": "function"
426+
},
414427
{
415428
"inputs": [
416429
{

test/StakingAllocation.test.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ describe('StakingAllocation Contract', () => {
242242
expect(da0).to.eq(etherParse('14000'));
243243
expect(da1).to.eq(etherParse('1000'));
244244

245-
246245
await expect(
247246
stakingAllocation.connect(runner0).addAllocation(deploymentIds[1], runner0.address, etherParse('5001'))
248247
).to.be.revertedWith('SAL03');
@@ -257,7 +256,6 @@ describe('StakingAllocation Contract', () => {
257256
await checkAllocation(runner1, etherParse('9000'), etherParse('10000'), true, false);
258257
await timeTravel(10);
259258

260-
261259
await stakingAllocation
262260
.connect(runner1)
263261
.removeAllocation(deploymentIds[0], runner1.address, etherParse('500'));
@@ -268,14 +266,17 @@ describe('StakingAllocation Contract', () => {
268266
await checkAllocation(runner1, etherParse('9000'), etherParse('9000'), false, true);
269267

270268
await expect(
271-
stakingAllocation.connect(runner1).moveAllocation(deploymentIds[0], deploymentIds[0], runner1.address, etherParse('1000'))
269+
stakingAllocation
270+
.connect(runner1)
271+
.moveAllocation(deploymentIds[0], deploymentIds[0], runner1.address, etherParse('1000'))
272272
).to.revertedWith('SAL07');
273273

274274
await expect(
275-
stakingAllocation.connect(runner1).moveAllocation(deploymentIds[0], deploymentIds[1], runner1.address, etherParse('1199000'))
275+
stakingAllocation
276+
.connect(runner1)
277+
.moveAllocation(deploymentIds[0], deploymentIds[1], runner1.address, etherParse('1199000'))
276278
).to.revertedWith('SAL04');
277279

278-
279280
await stakingAllocation
280281
.connect(runner1)
281282
.moveAllocation(deploymentIds[0], deploymentIds[1], runner1.address, etherParse('1000'));
@@ -292,10 +293,7 @@ describe('StakingAllocation Contract', () => {
292293
expect(await stakingAllocation.allocatedTokens(runner1.address, deploymentIds[0])).to.eq(
293294
etherParse('9000')
294295
);
295-
expect(await stakingAllocation.allocatedTokens(runner1.address, deploymentIds[1])).to.eq(
296-
etherParse('0')
297-
);
298-
296+
expect(await stakingAllocation.allocatedTokens(runner1.address, deploymentIds[1])).to.eq(etherParse('0'));
299297
});
300298

301299
it('add allocation to a stopped project', async () => {
@@ -310,7 +308,6 @@ describe('StakingAllocation Contract', () => {
310308

311309
await stakingAllocation.connect(runner0).addAllocation(deploymentId0, runner0.address, etherParse('5000'));
312310
await checkAllocation(runner0, etherParse('10000'), etherParse('5000'), false, false);
313-
314311
});
315312

316313
it('over-allocate and recover', async () => {
@@ -351,6 +348,21 @@ describe('StakingAllocation Contract', () => {
351348
).to.revertedWith('SAL03');
352349
});
353350

351+
it('blocks move allocation when runner is over-allocated', async () => {
352+
await stakingAllocation
353+
.connect(runner0)
354+
.addAllocation(deploymentIds[0], runner0.address, etherParse('10000'));
355+
356+
await stakingManager.connect(runner0).unstake(runner0.address, etherParse('1000'));
357+
await applyStaking(runner0, runner0);
358+
359+
await expect(
360+
stakingAllocation
361+
.connect(runner0)
362+
.moveAllocation(deploymentIds[0], deploymentIds[1], runner0.address, etherParse('1000'))
363+
).to.be.revertedWith('SAL03');
364+
});
365+
354366
it('remove allocation when stop service', async () => {
355367
await checkAllocation(runner0, etherParse('10000'), 0, false, false);
356368
await stakingManager.connect(runner0).stake(runner0.address, etherParse('10000'));

0 commit comments

Comments
 (0)