Skip to content

Commit 6093785

Browse files
authored
Merge pull request #89 from keep-network/fully-backed-tweaks
Fully Backed Sortition Pools tweaks We removed interfaces for SortitionPools that were not implemented by the pools, so they were outdated. The interfaces are not being used in keep-ecdsa anyways as we're using the implementation of the contract. This PR contains also a couple of tweaks to Fully Backed Sortition Pools: - added minimum bondable value and current selection as we already did in Bonded Sortition Pools (#84), - defined IFullyBackedBonding interface that should be implemented by bonding contract dedicated for FullyBackedSortitionPool usage - the IFullyBackedBonding interface defines an isInitialized function to check if the operator delegation in a bonding contract has passed initialization period, - we check if the bonding initialization period has passed if not don't let the operator register in the pool, - updated existing unit tests and added new ones.
2 parents aa2a641 + 949fd09 commit 6093785

12 files changed

Lines changed: 413 additions & 227 deletions

contracts/FullyBackedSortitionPool.sol

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ pragma solidity 0.5.17;
22

33
import "./AbstractSortitionPool.sol";
44
import "./RNG.sol";
5-
import "./api/IStaking.sol";
6-
import "./api/IBonding.sol";
75
import "./DynamicArray.sol";
6+
import "./api/IFullyBackedBonding.sol";
87

98
/// @title Fully Backed Sortition Pool
109
/// @notice A logarithmic data structure
@@ -24,6 +23,8 @@ import "./DynamicArray.sol";
2423
/// If the changes would be detrimental to the operator,
2524
/// the operator selection is performed again with the updated input
2625
/// to ensure correctness.
26+
/// The pool should specify a reasonable minimum bondable value for operators
27+
/// trying to join the pool, to prevent griefing the selection.
2728
contract FullyBackedSortitionPool is AbstractSortitionPool {
2829
using DynamicArray for DynamicArray.UintArray;
2930
using DynamicArray for DynamicArray.AddressArray;
@@ -35,8 +36,15 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
3536
// this value can be set to equal the most recent request's bondValue.
3637

3738
struct PoolParams {
38-
IBonding bondingContract;
39-
uint256 minimumAvailableBond;
39+
IFullyBackedBonding bondingContract;
40+
// Defines the minimum unbounded value the operator needs to have to be
41+
// eligible to join and stay in the sortition pool. Operators not
42+
// satisfying minimum bondable value are removed from the pool.
43+
uint256 minimumBondableValue;
44+
// Bond required from each operator for the currently pending group
45+
// selection. If operator does not have at least this unbounded value,
46+
// it is skipped during the selection.
47+
uint256 requestedBond;
4048
// Because the minimum available bond may fluctuate,
4149
// we use a constant pool weight divisor.
4250
// When we receive the available bond,
@@ -49,16 +57,17 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
4957
PoolParams poolParams;
5058

5159
constructor(
52-
IBonding _bondingContract,
53-
uint256 _initialMinimumStake,
60+
IFullyBackedBonding _bondingContract,
61+
uint256 _initialMinimumBondableValue,
5462
uint256 _bondWeightDivisor,
5563
address _poolOwner
5664
) public {
5765
require(_bondWeightDivisor > 0, "Weight divisor must be nonzero");
5866

5967
poolParams = PoolParams(
6068
_bondingContract,
61-
_initialMinimumStake,
69+
_initialMinimumBondableValue,
70+
0,
6271
_bondWeightDivisor,
6372
_poolOwner
6473
);
@@ -89,15 +98,35 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
8998
return generalizedSelectGroup(groupSize, seed, paramsPtr, true);
9099
}
91100

101+
/// @notice Sets the minimum bondable value required from the operator
102+
/// so that it is eligible to be in the pool. The pool should specify
103+
/// a reasonable minimum requirement for operators trying to join the pool
104+
/// to prevent griefing group selection.
105+
/// @param minimumBondableValue The minimum bondable value required from the
106+
/// operator.
107+
function setMinimumBondableValue(uint256 minimumBondableValue) public {
108+
require(
109+
msg.sender == poolParams.owner,
110+
"Only owner may update minimum bond value"
111+
);
112+
113+
poolParams.minimumBondableValue = minimumBondableValue;
114+
}
115+
116+
/// @notice Returns the minimum bondable value required from the operator
117+
/// so that it is eligible to be in the pool.
118+
function getMinimumBondableValue() public view returns (uint256) {
119+
return poolParams.minimumBondableValue;
120+
}
121+
92122
function initializeSelectionParams(uint256 bondValue)
93123
internal
94124
returns (PoolParams memory params)
95125
{
96126
params = poolParams;
97127

98-
if (params.minimumAvailableBond != bondValue) {
99-
params.minimumAvailableBond = bondValue;
100-
poolParams.minimumAvailableBond = bondValue;
128+
if (params.requestedBond != bondValue) {
129+
params.requestedBond = bondValue;
101130
}
102131

103132
return params;
@@ -118,13 +147,23 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
118147
);
119148

120149
// Don't query stake if bond is insufficient.
121-
if (bondableValue < poolParams.minimumAvailableBond) {
150+
if (bondableValue < poolParams.minimumBondableValue) {
151+
return 0;
152+
}
153+
154+
// Check if a bonding delegation is initialized.
155+
bool isBondingInitialized = poolParams.bondingContract.isInitialized(
156+
operator,
157+
ownerAddress
158+
);
159+
160+
// If a delegation is not yet initialized return 0 = ineligible.
161+
if (!isBondingInitialized) {
122162
return 0;
123163
}
124164

125165
// Weight = floor(eligibleStake / mimimumStake)
126166
// Ethereum uint256 division performs implicit floor
127-
// If eligibleStake < minimumStake, return 0 = ineligible.
128167
return (bondableValue / poolParams.bondWeightDivisor);
129168
}
130169

@@ -155,14 +194,21 @@ contract FullyBackedSortitionPool is AbstractSortitionPool {
155194
address(this)
156195
);
157196

158-
// Don't proceed further if bond is insufficient.
159-
if (preStake < params.minimumAvailableBond) {
197+
// If unbonded value is insufficient for the operator to be in the pool,
198+
// delete the operator.
199+
if (preStake < params.minimumBondableValue) {
160200
return Fate(Decision.Delete, 0);
161201
}
162202

203+
// If unbonded value is sufficient for the operator to be in the pool
204+
// but it is not sufficient for the current selection, skip the operator.
205+
if (preStake < params.requestedBond) {
206+
return Fate(Decision.Skip, 0);
207+
}
208+
163209
// Calculate the bond-stake that would be left after selection
164210
// Doesn't underflow because preStake >= minimum
165-
uint256 postStake = preStake - params.minimumAvailableBond;
211+
uint256 postStake = preStake - params.minimumBondableValue;
166212

167213
// Calculate the eligible pre-selection weight
168214
// based on the constant weight divisor.

contracts/FullyBackedSortitionPoolFactory.sol

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
pragma solidity 0.5.17;
22

33
import "./FullyBackedSortitionPool.sol";
4-
import "./api/IBonding.sol";
4+
import "./api/IFullyBackedBonding.sol";
55
import "./api/IStaking.sol";
66

77
/// @title Fully-Backed Sortition Pool Factory
88
/// @notice Factory for the creation of fully-backed sortition pools.
99
contract FullyBackedSortitionPoolFactory {
1010
/// @notice Creates a new fully-backed sortition pool instance.
11-
/// @param bondingContract Keep Bonding contract reference.
12-
/// @param minimumStake Minimum stake value making the operator eligible to
13-
/// join the network.
11+
/// @param bondingContract Fully Backed Bonding contract reference.
12+
/// @param minimumBondableValue Minimum unbonded value making the operator
13+
/// eligible to join the network.
1414
/// @param bondWeightDivisor Constant divisor for the available bond used to
1515
/// evalate the applicable weight.
1616
/// @return Address of the new fully-backed sortition pool contract instance.
1717
function createSortitionPool(
18-
IBonding bondingContract,
19-
uint256 minimumStake,
18+
IFullyBackedBonding bondingContract,
19+
uint256 minimumBondableValue,
2020
uint256 bondWeightDivisor
2121
) public returns (address) {
2222
return
2323
address(
2424
new FullyBackedSortitionPool(
2525
bondingContract,
26-
minimumStake,
26+
minimumBondableValue,
2727
bondWeightDivisor,
2828
msg.sender
2929
)

contracts/api/IBondedSortitionPool.sol

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pragma solidity 0.5.17;
2+
3+
import "./IBonding.sol";
4+
5+
/// @title Fully Backed Bonding contract interface.
6+
/// @notice The interface should be implemented by a bonding contract used for
7+
/// Fully Backed Sortition Pool.
8+
contract IFullyBackedBonding is IBonding {
9+
/// @notice Checks if the operator for the given bond creator contract
10+
/// has passed the initialization period.
11+
/// @param operator The operator address.
12+
/// @param bondCreator The bond creator contract address.
13+
/// @return True if operator has passed initialization period for given
14+
/// bond creator contract, false otherwise.
15+
function isInitialized(address operator, address bondCreator)
16+
public
17+
view
18+
returns (bool);
19+
}

contracts/api/ISortitionPool.sol

Lines changed: 0 additions & 37 deletions
This file was deleted.

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
"eslint": "^6.8.0",
4848
"eslint-config-keep": "github:keep-network/eslint-config-keep#0.3.0",
4949
"eth-gas-reporter": "^0.1.12",
50+
"bn-chai": "^1.0.1",
51+
"chai": "^4.2.0",
5052
"ethlint": "^1.2.5",
5153
"prettier": "^2.0.2",
5254
"prettier-plugin-solidity": "^1.0.0-alpha.47",
Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
pragma solidity 0.5.17;
22

3-
contract BondingContractStub {
4-
mapping(address => uint) public unbondedValue;
3+
import "../../contracts/api/IBonding.sol";
54

6-
function setBondableValue(address operator, uint256 value) public {
7-
unbondedValue[operator] = value;
8-
}
5+
contract BondingContractStub is IBonding {
6+
mapping(address => uint256) public unbondedValue;
97

10-
function availableUnbondedValue(
11-
address operator,
12-
address, // bondCreator,
13-
address // additionalAuthorizedContract
14-
) external view returns (uint256) {
15-
return unbondedValue[operator];
16-
}
8+
function setBondableValue(address operator, uint256 value) public {
9+
unbondedValue[operator] = value;
10+
}
11+
12+
function availableUnbondedValue(
13+
address operator,
14+
address, // bondCreator,
15+
address // additionalAuthorizedContract
16+
) external view returns (uint256) {
17+
return unbondedValue[operator];
18+
}
1719
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pragma solidity 0.5.17;
2+
3+
import "../../contracts/api/IFullyBackedBonding.sol";
4+
import "./BondingContractStub.sol";
5+
6+
contract FullyBackedBondingStub is IFullyBackedBonding, BondingContractStub {
7+
mapping(address => bool) initialized;
8+
9+
function setInitialized(address operator, bool value) public {
10+
initialized[operator] = value;
11+
}
12+
13+
function isInitialized(
14+
address operator,
15+
address // bondCreator
16+
) public view returns (bool) {
17+
return initialized[operator];
18+
}
19+
}

test/fullyBackedFactoryTest.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
const FullyBackedSortitionPoolFactory = artifacts.require(
2-
"./contracts/FullyBackedSortitionPoolFactory.sol",
2+
"FullyBackedSortitionPoolFactory",
33
)
4-
const FullyBackedSortitionPool = artifacts.require(
5-
"./contracts/FullyBackedSortitionPool.sol",
6-
)
7-
const BondingContractStub = artifacts.require("BondingContractStub.sol")
4+
const FullyBackedSortitionPool = artifacts.require("FullyBackedSortitionPool")
5+
const FullyBackedBondingStub = artifacts.require("FullyBackedBondingStub")
86

97
contract("FullyBackedSortitionPoolFactory", (accounts) => {
108
let bondingContract
@@ -15,7 +13,7 @@ contract("FullyBackedSortitionPoolFactory", (accounts) => {
1513

1614
before(async () => {
1715
factory = await FullyBackedSortitionPoolFactory.deployed()
18-
bondingContract = await BondingContractStub.new()
16+
bondingContract = await FullyBackedBondingStub.new()
1917
})
2018

2119
describe("createSortitionPool()", async () => {
@@ -46,8 +44,11 @@ contract("FullyBackedSortitionPoolFactory", (accounts) => {
4644

4745
assert.notEqual(pool1Address, pool2Address)
4846

49-
bondingContract.setBondableValue(accounts[1], 100)
50-
bondingContract.setBondableValue(accounts[2], 200)
47+
await bondingContract.setBondableValue(accounts[1], 100)
48+
await bondingContract.setBondableValue(accounts[2], 200)
49+
50+
await bondingContract.setInitialized(accounts[1], true)
51+
await bondingContract.setInitialized(accounts[2], true)
5152

5253
assert.equal(await pool1.operatorsInPool(), 0)
5354
assert.equal(await pool2.operatorsInPool(), 0)

0 commit comments

Comments
 (0)