Skip to content

Commit dbcadd9

Browse files
authored
Merge pull request #96 from keep-network/fully-backed-ban
Ban operator in FullyBackedSortitionPool Added functionality of operators banning in FullyBackedSortitionPool contract. A pool owner can call a function to ban an operator. Once an operator gets banned it is removed from the pool. The operator won't get selected to new groups. An operator can be banned even if it has never been registered or is not currently registered in the pool. A banned operator cannot join a pool.
2 parents 6093785 + 55902ed commit dbcadd9

2 files changed

Lines changed: 122 additions & 3 deletions

File tree

contracts/FullyBackedSortitionPool.sol

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
5656

5757
PoolParams poolParams;
5858

59+
// Banned operator addresses. Banned operators are removed from the pool; they
60+
// won't be selected to groups and won't be able to join the pool.
61+
mapping(address => bool) public bannedOperators;
62+
5963
constructor(
6064
IFullyBackedBonding _bondingContract,
6165
uint256 _initialMinimumBondableValue,
@@ -87,9 +91,9 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
8791
uint256 groupSize,
8892
bytes32 seed,
8993
uint256 bondValue
90-
) public returns (address[] memory) {
94+
) public onlyOwner() returns (address[] memory) {
9195
PoolParams memory params = initializeSelectionParams(bondValue);
92-
require(msg.sender == params.owner, "Only owner may select groups");
96+
9397
uint256 paramsPtr;
9498
// solium-disable-next-line security/no-inline-assembly
9599
assembly {
@@ -119,6 +123,18 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
119123
return poolParams.minimumBondableValue;
120124
}
121125

126+
/// @notice Bans an operator in the pool. The operator is added to banned
127+
/// operators list. If the operator is already registered in the pool it gets
128+
/// removed. Only onwer of the pool can call this function.
129+
/// @param operator An operator address.
130+
function ban(address operator) public onlyOwner() {
131+
bannedOperators[operator] = true;
132+
133+
if (isOperatorRegistered(operator)) {
134+
removeOperator(operator);
135+
}
136+
}
137+
122138
function initializeSelectionParams(uint256 bondValue)
123139
internal
124140
returns (PoolParams memory params)
@@ -136,6 +152,11 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
136152
// which may differ from the weight in the pool.
137153
// Return 0 if ineligible.
138154
function getEligibleWeight(address operator) internal view returns (uint256) {
155+
// Check if the operator has been banned.
156+
if (bannedOperators[operator]) {
157+
return 0;
158+
}
159+
139160
address ownerAddress = poolParams.owner;
140161
// Get the amount of bondable value available for this pool.
141162
// We only care that this covers one single bond
@@ -234,4 +255,9 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
234255
// but reasonable to begin with.
235256
return Fate(Decision.UpdateSelect, postWeight);
236257
}
258+
259+
modifier onlyOwner() {
260+
require(msg.sender == poolParams.owner, "Caller is not the owner");
261+
_;
262+
}
237263
}

test/fullyBackedSortitionPoolTest.js

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,19 @@ contract("FullyBackedSortitionPool", (accounts) => {
9494

9595
assert.isTrue(await pool.isOperatorInitialized(alice))
9696
})
97+
98+
it("reverts when operator gets banned in the pool", async () => {
99+
await prepareOperator(alice, bond)
100+
101+
await mineBlocks(operatorInitBlocks.addn(1))
102+
103+
await pool.ban(alice, {from: owner})
104+
105+
expectRevert(
106+
pool.isOperatorInitialized(alice),
107+
"Operator is not in the pool",
108+
)
109+
})
97110
})
98111

99112
describe("selectSetGroup", async () => {
@@ -160,7 +173,7 @@ contract("FullyBackedSortitionPool", (accounts) => {
160173

161174
await expectRevert(
162175
pool.selectSetGroup(3, seed, minimumBondableValue, {from: alice}),
163-
"Only owner may select groups",
176+
"Caller is not the owner",
164177
)
165178
})
166179

@@ -221,6 +234,34 @@ contract("FullyBackedSortitionPool", (accounts) => {
221234
await pool.selectSetGroup(3, seed, minimumBondableValue, {from: owner})
222235
})
223236

237+
it("reverts when operator gets banned in the sortition pool", async () => {
238+
// Register three operators.
239+
await prepareOperator(alice, bond)
240+
await prepareOperator(bob, bond)
241+
await prepareOperator(carol, bond)
242+
243+
assert.equal(await pool.operatorsInPool(), 3)
244+
245+
// Initialization period passed.
246+
await mineBlocks(operatorInitBlocks.addn(1))
247+
248+
await pool.selectSetGroup(2, seed, minimumBondableValue, {from: owner})
249+
250+
// Ban an operator.
251+
await pool.ban(carol, {from: owner})
252+
253+
assert.equal(
254+
await pool.operatorsInPool(),
255+
2,
256+
"incorrect number of operators after operator ban",
257+
)
258+
259+
await expectRevert(
260+
pool.selectSetGroup(3, seed, minimumBondableValue, {from: owner}),
261+
"Not enough operators in pool",
262+
)
263+
})
264+
224265
it("removes minimum-bond-ineligible operators and still works afterwards", async () => {
225266
await prepareOperator(alice, bond)
226267
await prepareOperator(bob, bond)
@@ -382,5 +423,57 @@ contract("FullyBackedSortitionPool", (accounts) => {
382423
"Operator is already registered in the pool",
383424
)
384425
})
426+
427+
it("fails for banned operator", async () => {
428+
await pool.ban(alice, {from: owner})
429+
430+
await bonding.setBondableValue(alice, minimumBondableValue)
431+
await bonding.setInitialized(alice, true)
432+
433+
await expectRevert(pool.joinPool(alice), "Operator not eligible")
434+
})
435+
})
436+
437+
describe("ban", async () => {
438+
it("adds operator to banned operators", async () => {
439+
expect(await pool.bannedOperators(alice)).to.be.false
440+
441+
await pool.ban(alice, {from: owner})
442+
443+
expect(await pool.bannedOperators(alice)).to.be.true
444+
})
445+
446+
it("does not revert when called multiple times", async () => {
447+
expect(await pool.bannedOperators(alice)).to.be.false
448+
449+
await pool.ban(alice, {from: owner})
450+
await pool.ban(alice, {from: owner})
451+
452+
expect(await pool.bannedOperators(alice)).to.be.true
453+
})
454+
455+
it("does not revert when operator is not registered", async () => {
456+
expect(await pool.isOperatorRegistered(alice)).to.be.false
457+
458+
await pool.ban(alice, {from: owner})
459+
})
460+
461+
it("removes operator from the pool", async () => {
462+
await bonding.setBondableValue(alice, minimumBondableValue)
463+
await bonding.setInitialized(alice, true)
464+
await pool.joinPool(alice)
465+
466+
expect(await pool.isOperatorRegistered(alice)).to.be.true
467+
468+
await pool.ban(alice, {from: owner})
469+
470+
expect(await pool.isOperatorRegistered(alice)).to.be.false
471+
expect(await pool.isOperatorInPool(alice)).to.be.false
472+
expect(await pool.getPoolWeight(alice)).to.eq.BN(0)
473+
})
474+
475+
it("reverts when called by non-owner", async () => {
476+
await expectRevert(pool.ban(alice), "Caller is not the owner")
477+
})
385478
})
386479
})

0 commit comments

Comments
 (0)