Skip to content

Commit b3f3fe1

Browse files
authored
Merge pull request #1449 from keep-network/check-for-operator-stake-when-seizing
Check for operator stake when seizing We to cover a scenario, when a seize amount is greater than the operator balance. In this case, we are zeroing balance and the balance become our new amount to be seized.
2 parents da724e8 + 07af993 commit b3f3fe1

File tree

3 files changed

+71
-19
lines changed

3 files changed

+71
-19
lines changed

contracts/solidity/contracts/StakeDelegatable.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
pragma solidity ^0.5.4;
2-
pragma solidity ^0.5.4;
32

43
import "openzeppelin-solidity/contracts/token/ERC20/ERC20Burnable.sol";
54
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";

contracts/solidity/contracts/TokenStaking.sol

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,31 +205,42 @@ contract TokenStaking is StakeDelegatable {
205205
* @dev Seize provided token amount from every member in the misbehaved
206206
* operators array. The tattletale is rewarded with 5% of the total seized
207207
* amount scaled by the reward adjustment parameter and the rest 95% is burned.
208-
* @param amount Token amount to seize from every misbehaved operator.
208+
* @param amountToSeize Token amount to seize from every misbehaved operator.
209209
* @param rewardMultiplier Reward adjustment in percentage. Min 1% and 100% max.
210210
* @param tattletale Address to receive the 5% reward.
211211
* @param misbehavedOperators Array of addresses to seize the tokens from.
212212
*/
213213
function seize(
214-
uint256 amount,
214+
uint256 amountToSeize,
215215
uint256 rewardMultiplier,
216216
address tattletale,
217217
address[] memory misbehavedOperators
218218
) public onlyApprovedOperatorContract(msg.sender) {
219+
uint256 totalAmountToBurn = 0;
219220
for (uint i = 0; i < misbehavedOperators.length; i++) {
220221
address operator = misbehavedOperators[i];
221222
require(authorizations[msg.sender][operator], "Not authorized");
223+
222224
uint256 operatorParams = operators[operator].packedParams;
223-
uint256 oldAmount = operatorParams.getAmount();
224-
uint256 newAmount = oldAmount.sub(amount);
225-
operators[operator].packedParams = operatorParams.setAmount(newAmount);
225+
uint256 currentAmount = operatorParams.getAmount();
226+
227+
if (currentAmount < amountToSeize) {
228+
totalAmountToBurn = totalAmountToBurn.add(currentAmount);
229+
230+
uint256 newAmount = 0;
231+
operators[operator].packedParams = operatorParams.setAmount(newAmount);
232+
} else {
233+
totalAmountToBurn = totalAmountToBurn.add(amountToSeize);
234+
235+
uint256 newAmount = currentAmount.sub(amountToSeize);
236+
operators[operator].packedParams = operatorParams.setAmount(newAmount);
237+
}
226238
}
227239

228-
uint256 total = misbehavedOperators.length.mul(amount);
229-
uint256 tattletaleReward = (total.mul(5).div(100)).mul(rewardMultiplier).div(100);
240+
uint256 tattletaleReward = (totalAmountToBurn.mul(5).div(100)).mul(rewardMultiplier).div(100);
230241

231242
token.safeTransfer(tattletale, tattletaleReward);
232-
token.burn(total.sub(tattletaleReward));
243+
token.burn(totalAmountToBurn.sub(tattletaleReward));
233244
}
234245

235246
/**

contracts/solidity/test/TestKeepRandomBeaconOperatorSlashing.js

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ contract('KeepRandomBeaconOperator', function(accounts) {
4747

4848
entryFeeEstimate = await serviceContract.entryFeeEstimate(0)
4949
await serviceContract.methods['requestRelayEntry()']({value: entryFeeEstimate})
50+
51+
await registry.setRegistryKeeper(registryKeeper)
52+
await registry.approveOperatorContract(anotherOperatorContract, {from: registryKeeper})
53+
await stakingContract.authorizeOperatorContract(operator1, anotherOperatorContract, {from: authorizer})
5054
})
5155

5256
beforeEach(async () => {
@@ -58,29 +62,67 @@ contract('KeepRandomBeaconOperator', function(accounts) {
5862
})
5963

6064
it("should slash token amount", async () => {
61-
await registry.setRegistryKeeper(registryKeeper)
62-
await registry.approveOperatorContract(anotherOperatorContract, {from: registryKeeper})
63-
await stakingContract.authorizeOperatorContract(operator1, anotherOperatorContract, {from: authorizer})
64-
6565
let amountToSlash = web3.utils.toBN(42000000);
6666
let balanceBeforeSlashing = await stakingContract.balanceOf(operator1)
67-
await stakingContract.slash(web3.utils.toBN(amountToSlash), [operator1], {from: anotherOperatorContract})
67+
await stakingContract.slash(amountToSlash, [operator1], {from: anotherOperatorContract})
6868
let balanceAfterSlashing = await stakingContract.balanceOf(operator1)
6969

7070
assert.isTrue((balanceBeforeSlashing.sub(amountToSlash)).eq(balanceAfterSlashing), "Unexpected balance after token slasing")
7171
})
7272

7373
it("should slash no more than available operator's amount", async () => {
74-
await registry.setRegistryKeeper(registryKeeper)
75-
await registry.approveOperatorContract(anotherOperatorContract, {from: registryKeeper})
76-
await stakingContract.authorizeOperatorContract(operator1, anotherOperatorContract, {from: authorizer})
77-
7874
let amountToSlash = largeStake.add(web3.utils.toBN(100));
79-
await stakingContract.slash(web3.utils.toBN(amountToSlash), [operator1], {from: anotherOperatorContract})
75+
await stakingContract.slash(amountToSlash, [operator1], {from: anotherOperatorContract})
8076

8177
assert.isTrue((await stakingContract.balanceOf(operator1)).isZero(), "Unexpected balance after token slasing")
8278
})
8379

80+
it("should seize token amount", async () => {
81+
let operatorBalanceBeforeSeizing = await stakingContract.balanceOf(operator1)
82+
let tattletaleBalanceBeforeSeizing = await token.balanceOf(tattletale)
83+
84+
let amountToSeize = web3.utils.toBN(42000000);
85+
let rewardMultiplier = web3.utils.toBN(25)
86+
await stakingContract.seize(amountToSeize, rewardMultiplier, tattletale, [operator1], {from: anotherOperatorContract})
87+
88+
let operatorBalanceAfterSeizing = await stakingContract.balanceOf(operator1)
89+
let tattletaleBalanceAfterSeizing = await token.balanceOf(tattletale)
90+
91+
assert.isTrue(
92+
(operatorBalanceBeforeSeizing.sub(amountToSeize)).eq(operatorBalanceAfterSeizing),
93+
"Unexpected balance for operator after token seizing"
94+
)
95+
96+
// 525000 = (42000000 * 5 / 100) * 25 / 100
97+
let expectedTattletaleReward = web3.utils.toBN(525000)
98+
assert.isTrue(
99+
(tattletaleBalanceBeforeSeizing.add(expectedTattletaleReward)).eq(tattletaleBalanceAfterSeizing),
100+
"Unexpected balance for tattletale after token seizing"
101+
)
102+
})
103+
104+
it("should seize no more than available operator's amount", async () => {
105+
let tattletaleBalanceBeforeSeizing = await token.balanceOf(tattletale)
106+
107+
let amountToSeize = largeStake.add(web3.utils.toBN(100)); // 400000000000000000000100
108+
let rewardMultiplier = web3.utils.toBN(10)
109+
await stakingContract.seize(amountToSeize, rewardMultiplier, tattletale, [operator1], {from: anotherOperatorContract})
110+
111+
let tattletaleBalanceAfterSeizing = await token.balanceOf(tattletale)
112+
113+
assert.isTrue(
114+
(await stakingContract.balanceOf(operator1)).isZero(),
115+
"Unexpected balance for operator after token seizing"
116+
)
117+
118+
// 2000000000000000000000 = (400000000000000000000100 * 5 / 100) * 10 / 100
119+
let expectedTattletaleReward = web3.utils.toBN("2000000000000000000000")
120+
assert.isTrue(
121+
(tattletaleBalanceBeforeSeizing.add(expectedTattletaleReward)).eq(tattletaleBalanceAfterSeizing),
122+
"Unexpected balance for tattletale after token seizing"
123+
)
124+
})
125+
84126
it("should be able to report unauthorized signing", async () => {
85127
await operatorContract.reportUnauthorizedSigning(
86128
groupIndex,

0 commit comments

Comments
 (0)