From 3fe30e84e190390fadefdb024a119fff036f0344 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 2 Oct 2020 12:00:39 +0200 Subject: [PATCH 1/5] Renamed minimumBond to defaultMinimimBond This constant is a default value for sortition pool initialization. The actual value can be updated by an application. Current name of the property was confusing. --- .../contracts/fully-backed/FullyBackedECDSAKeepFactory.sol | 4 ++-- solidity/test/FullyBackedECDSAKeepFactoryTest.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol b/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol index 9793310d1..299086836 100644 --- a/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol +++ b/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol @@ -68,7 +68,7 @@ contract FullyBackedECDSAKeepFactory is // in `FullyBackedBonding` contract. Minimum delegation deposit determines // a minimum value of ether that should be transferred to the bonding contract // by an owner when delegating to an operator. - uint256 public constant minimumBond = 20 ether; + uint256 public constant defaultMinimumBond = 20 ether; // Signer candidates in bonded sortition pool are weighted by their eligible // stake divided by a constant divisor. The divisor is set to 1 ETH so that @@ -240,7 +240,7 @@ contract FullyBackedECDSAKeepFactory is return sortitionPoolFactory.createSortitionPool( IFullyBackedBonding(address(bonding)), - minimumBond, + defaultMinimumBond, bondWeightDivisor ); } diff --git a/solidity/test/FullyBackedECDSAKeepFactoryTest.js b/solidity/test/FullyBackedECDSAKeepFactoryTest.js index aac804220..6e49ff0e8 100644 --- a/solidity/test/FullyBackedECDSAKeepFactoryTest.js +++ b/solidity/test/FullyBackedECDSAKeepFactoryTest.js @@ -824,7 +824,7 @@ describe("FullyBackedECDSAKeepFactory", function () { }) it("reverts if one member has insufficient unbonded value", async () => { - const minimumBond = await keepFactory.minimumBond.call() + const minimumBond = await keepFactory.defaultMinimumBond.call() const availableUnbonded = await bonding.availableUnbondedValue( members[2], keepFactory.address, @@ -1451,7 +1451,7 @@ describe("FullyBackedECDSAKeepFactory", function () { describe("getKeepAtIndex", async () => { before(async () => { await initializeNewFactory() - const minimumBond = await keepFactory.minimumBond.call() + const minimumBond = await keepFactory.defaultMinimumBond.call() const memberBond = minimumBond.muln(2) // want to be able to open 2 keeps await initializeMemberCandidates(memberBond) await registerMemberCandidates() From ffc6fabd72d9f7be501902cc238f089f80316655 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 2 Oct 2020 12:14:49 +0200 Subject: [PATCH 2/5] Ban keep members from FullyBackedECDSAKeepFactory Exposed a function to ban in a sortition pool members of keep calling the function. A keep is determined as a caller of the function. The caller can only be a keep created by the factory. This function requires linking keep with an application address for which keep was created. We introduced a mapping filled on a keep creation. --- .../FullyBackedECDSAKeepFactory.sol | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol b/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol index 299086836..7ab33e906 100644 --- a/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol +++ b/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol @@ -76,6 +76,10 @@ contract FullyBackedECDSAKeepFactory is // eligible weight for signer selection. uint256 public constant bondWeightDivisor = 1 ether; + // Maps a keep to an application for which the keep was created. + // keep address -> application address + mapping(address => address) keepApplication; + // Notification that a new keep has been created. event FullyBackedECDSAKeepCreated( address indexed keepAddress, @@ -85,6 +89,10 @@ contract FullyBackedECDSAKeepFactory is uint256 honestThreshold ); + // Notification when an operator gets banned in a sortition pool for + // an application. + event OperatorBanned(address indexed operator, address indexed application); + constructor( address _masterKeepAddress, address _sortitionPoolFactoryAddress, @@ -194,6 +202,8 @@ contract FullyBackedECDSAKeepFactory is ); } + keepApplication[keepAddress] = application; + emit FullyBackedECDSAKeepCreated( keepAddress, members, @@ -236,6 +246,29 @@ contract FullyBackedECDSAKeepFactory is return bonding.isAuthorizedForOperator(_operator, address(this)); } + /// @notice Bans members of a calling keep in an sortition pools associated + /// with the application for which the keep was created. + /// @dev The function can be called only by a keep created by this factory. + function banKeepMembers() public onlyKeep() { + FullyBackedECDSAKeep keep = FullyBackedECDSAKeep(msg.sender); + + address[] memory members = keep.getMembers(); + + address application = keepApplication[address(keep)]; + + FullyBackedSortitionPool sortitionPool = FullyBackedSortitionPool( + getSortitionPool(application) + ); + + for (uint256 i = 0; i < members.length; i++) { + address operator = members[i]; + + sortitionPool.ban(operator); + + emit OperatorBanned(operator, application); + } + } + function newSortitionPool(address _application) internal returns (address) { return sortitionPoolFactory.createSortitionPool( @@ -262,4 +295,13 @@ contract FullyBackedECDSAKeepFactory is // Ex. (100 + 3 - 1) / 3 = 34 return (_keepBond.add(_groupSize).sub(1)).div(_groupSize); } + + /// @notice Checks if caller is a keep created by this factory. + modifier onlyKeep() { + require( + keepOpenedTimestamp[msg.sender] > 0, + "Caller is not a keep created by the factory" + ); + _; + } } From fecaaa3694275867e5fff86e0645c3b8551985a8 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 2 Oct 2020 12:15:17 +0200 Subject: [PATCH 3/5] Ban keep members for signature fraud in FullyBackedBondedECDSAKeep In case of a signature fraud keep will call the factory to ban keep members. --- solidity/contracts/fully-backed/FullyBackedECDSAKeep.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/solidity/contracts/fully-backed/FullyBackedECDSAKeep.sol b/solidity/contracts/fully-backed/FullyBackedECDSAKeep.sol index d06ca4332..384e9830f 100644 --- a/solidity/contracts/fully-backed/FullyBackedECDSAKeep.sol +++ b/solidity/contracts/fully-backed/FullyBackedECDSAKeep.sol @@ -52,9 +52,12 @@ contract FullyBackedECDSAKeep is AbstractBondedECDSAKeep { bonding.claimDelegatedAuthority(_keepFactory); } + /// @notice Punishes keep members after proving a signature fraud. + /// @dev Calls the keep factory to ban members of the keep. The owner of the + /// keep is able to seize the members bonds, so no action is necessary to be + /// taken from perspective of this function. function slashForSignatureFraud() internal { - // TODO: We don't need to do anything as keep owner is able to seize the bonds - // TODO: Ban members (remove from sortition pool and don't let to rejoin) + keepFactory.banKeepMembers(); } /// @notice Gets the beneficiary for the specified member address. From b4ad20dae62b115570c357b7d1152f732263de40 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 2 Oct 2020 12:20:25 +0200 Subject: [PATCH 4/5] Updated unit tests for FullyBacked ECDSA Keeps --- .../FullyBackedECDSAKeepCloneFactoryStub.sol | 6 + .../test/FullyBackedECDSAKeepStub.sol | 4 + solidity/test/BondedECDSAKeepFactoryTest.js | 2 + solidity/test/BondedECDSAKeepTest.js | 2 + .../test/FullyBackedECDSAKeepFactoryTest.js | 228 ++++++++++++++---- solidity/test/FullyBackedECDSAKeepTest.js | 86 +++---- 6 files changed, 237 insertions(+), 91 deletions(-) diff --git a/solidity/contracts/test/FullyBackedECDSAKeepCloneFactoryStub.sol b/solidity/contracts/test/FullyBackedECDSAKeepCloneFactoryStub.sol index c27e2d267..93c39932c 100644 --- a/solidity/contracts/test/FullyBackedECDSAKeepCloneFactoryStub.sol +++ b/solidity/contracts/test/FullyBackedECDSAKeepCloneFactoryStub.sol @@ -15,6 +15,8 @@ contract FullyBackedECDSAKeepCloneFactoryStub is { address public masterKeepAddress; + mapping(address => uint256) public banKeepMembersCalledCount; + constructor(address _masterKeepAddress) public { masterKeepAddress = _masterKeepAddress; } @@ -49,4 +51,8 @@ contract FullyBackedECDSAKeepCloneFactoryStub is { return true; } + + function banKeepMembers() public { + banKeepMembersCalledCount[msg.sender]++; + } } diff --git a/solidity/contracts/test/FullyBackedECDSAKeepStub.sol b/solidity/contracts/test/FullyBackedECDSAKeepStub.sol index 5116d293e..e287c0782 100644 --- a/solidity/contracts/test/FullyBackedECDSAKeepStub.sol +++ b/solidity/contracts/test/FullyBackedECDSAKeepStub.sol @@ -13,6 +13,10 @@ contract FullyBackedECDSAKeepStub is FullyBackedECDSAKeep { markAsTerminated(); } + function publicSlashForSignatureFraud() public { + slashForSignatureFraud(); + } + function isFradulentPreimageSet(bytes memory preimage) public view diff --git a/solidity/test/BondedECDSAKeepFactoryTest.js b/solidity/test/BondedECDSAKeepFactoryTest.js index cccf04edc..bf392e49e 100644 --- a/solidity/test/BondedECDSAKeepFactoryTest.js +++ b/solidity/test/BondedECDSAKeepFactoryTest.js @@ -29,6 +29,8 @@ chai.use(require("bn-chai")(BN)) const expect = chai.expect const assert = chai.assert +// TODO: Refactor tests by pulling common parts of BondedECDSAKeepFactory and +// FullyBackedBondedECDSAKeepFactory to one file. describe("BondedECDSAKeepFactory", function () { let registry let tokenStaking diff --git a/solidity/test/BondedECDSAKeepTest.js b/solidity/test/BondedECDSAKeepTest.js index 810840a76..cdf073cbc 100644 --- a/solidity/test/BondedECDSAKeepTest.js +++ b/solidity/test/BondedECDSAKeepTest.js @@ -31,6 +31,8 @@ chai.use(require("bn-chai")(BN)) const expect = chai.expect const assert = chai.assert +// TODO: Refactor tests by pulling common parts of BondedECDSAKeep and +// FullyBackedBondedECDSAKeep to one file. describe("BondedECDSAKeep", function () { const bondCreator = accounts[0] const owner = accounts[1] diff --git a/solidity/test/FullyBackedECDSAKeepFactoryTest.js b/solidity/test/FullyBackedECDSAKeepFactoryTest.js index 6e49ff0e8..216e5a871 100644 --- a/solidity/test/FullyBackedECDSAKeepFactoryTest.js +++ b/solidity/test/FullyBackedECDSAKeepFactoryTest.js @@ -20,7 +20,9 @@ const FullyBackedSortitionPoolFactory = contract.fromArtifact( "FullyBackedSortitionPoolFactory" ) const RandomBeaconStub = contract.fromArtifact("RandomBeaconStub") -const FullyBackedECDSAKeep = contract.fromArtifact("FullyBackedECDSAKeep") +const FullyBackedECDSAKeepStub = contract.fromArtifact( + "FullyBackedECDSAKeepStub" +) const BN = web3.utils.BN @@ -29,13 +31,15 @@ chai.use(require("bn-chai")(BN)) const expect = chai.expect const assert = chai.assert +// TODO: Refactor tests by pulling common parts of BondedECDSAKeepFactory and +// FullyBackedECDSAKeepFactory to one file. describe("FullyBackedECDSAKeepFactory", function () { let registry let keepFactory let sortitionPoolFactory let bonding let randomBeacon - let signerPool + let signerPoolAddress let minimumDelegationDeposit let delegationLockPeriod @@ -81,7 +85,8 @@ describe("FullyBackedECDSAKeepFactory", function () { await initializeNewFactory() await initializeMemberCandidates() - pool = await FullyBackedSortitionPool.at(signerPool) + signerPoolAddress = await keepFactory.getSortitionPool(application) + pool = await FullyBackedSortitionPool.at(signerPoolAddress) minimumBondableValue = await pool.getMinimumBondableValue() bondWeightDivisor = await keepFactory.bondWeightDivisor.call() @@ -100,7 +105,7 @@ describe("FullyBackedECDSAKeepFactory", function () { it("inserts operator with the correct unbonded value available", async () => { const unbondedValue = await bonding.unbondedValue(members[0]) - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) const expectedWeight = unbondedValue.div(bondWeightDivisor) @@ -122,7 +127,7 @@ describe("FullyBackedECDSAKeepFactory", function () { from: members[1], }) - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) assert.isTrue( await pool.isOperatorInPool(members[0]), "operator 1 is not in the pool" @@ -138,7 +143,7 @@ describe("FullyBackedECDSAKeepFactory", function () { from: members[0], }) - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) assert.isTrue( await pool.isOperatorInPool(members[0]), @@ -221,9 +226,11 @@ describe("FullyBackedECDSAKeepFactory", function () { it("reverts if delegation initialization period has not passed", async () => { await initializeNewFactory() - signerPool = await keepFactory.createSortitionPool.call(application) + signerPoolAddress = await keepFactory.createSortitionPool.call( + application + ) await keepFactory.createSortitionPool(application) - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) await delegate(members[0], members[0], authorizers[0]) @@ -247,6 +254,25 @@ describe("FullyBackedECDSAKeepFactory", function () { "operator is not in the pool after initialization period" ) }) + + it("does not let banned operator to register", async () => { + await initializeNewFactory() + await initializeMemberCandidates() + + await registerMemberCandidates(members, application) + + const keep = await openKeep() + + await keep.publicSlashForSignatureFraud() + const keepMembers = await keep.getMembers() + + await expectRevert( + keepFactory.registerMemberCandidate(application, { + from: keepMembers[1], + }), + "Operator not eligible" + ) + }) }) describe("createSortitionPool", async () => { @@ -347,7 +373,7 @@ describe("FullyBackedECDSAKeepFactory", function () { await initializeNewFactory() await initializeMemberCandidates() - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) minimumBondableValue = await pool.getMinimumBondableValue() }) @@ -451,7 +477,7 @@ describe("FullyBackedECDSAKeepFactory", function () { await initializeMemberCandidates() await registerMemberCandidates() - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) minimumBondableValue = await pool.getMinimumBondableValue() await setUnbondedValue(members[0], minimumBondableValue.muln(3)) @@ -571,7 +597,7 @@ describe("FullyBackedECDSAKeepFactory", function () { feeEstimate = await keepFactory.openKeepFeeEstimate() - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) minimumBondableValue = await pool.getMinimumBondableValue() }) @@ -828,7 +854,7 @@ describe("FullyBackedECDSAKeepFactory", function () { const availableUnbonded = await bonding.availableUnbondedValue( members[2], keepFactory.address, - signerPool + signerPoolAddress ) const withdrawValue = availableUnbonded.sub(minimumBond).add(new BN(1)) await bonding.withdraw(withdrawValue, members[2], { @@ -1079,7 +1105,7 @@ describe("FullyBackedECDSAKeepFactory", function () { } ) const recordedKeepAddress = await keepFactory.getKeepAtIndex(preKeepCount) - const keep = await FullyBackedECDSAKeep.at(keepAddress) + const keep = await FullyBackedECDSAKeepStub.at(keepAddress) const keepOpenedTime = await keep.getOpenedTimestamp() const factoryKeepOpenedTime = await keepFactory.getKeepOpenedTimestamp( keepAddress @@ -1119,7 +1145,7 @@ describe("FullyBackedECDSAKeepFactory", function () { } ) - const keep = await FullyBackedECDSAKeep.at(keepAddress) + const keep = await FullyBackedECDSAKeepStub.at(keepAddress) assert.isTrue(await keep.isActive(), "keep should be active") }) @@ -1206,15 +1232,9 @@ describe("FullyBackedECDSAKeepFactory", function () { const operators = [] for (let i = 0; i < memberCount; i++) { - const operator = await web3.eth.personal.newAccount("pass") - const authorizer = operator - await web3.eth.personal.unlockAccount(operator, "pass", 5000) // 5 sec unlock + const operator = await newAccount("21") - web3.eth.sendTransaction({ - from: accounts[0], - to: operator, - value: web3.utils.toWei("21", "ether"), - }) + const authorizer = operator await delegate(operator, beneficiary, authorizer, unbondedAmount) @@ -1229,7 +1249,7 @@ describe("FullyBackedECDSAKeepFactory", function () { }) } - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) await mineBlocks((await pool.operatorInitBlocks()).add(new BN(1))) } }) @@ -1245,7 +1265,7 @@ describe("FullyBackedECDSAKeepFactory", function () { delegationInitPeriod ) randomBeacon = accounts[1] - const keepMasterContract = await FullyBackedECDSAKeep.new() + const keepMasterContract = await FullyBackedECDSAKeepStub.new() keepFactory = await FullyBackedECDSAKeepFactoryStub.new( keepMasterContract.address, sortitionPoolFactory.address, @@ -1453,7 +1473,7 @@ describe("FullyBackedECDSAKeepFactory", function () { await initializeNewFactory() const minimumBond = await keepFactory.defaultMinimumBond.call() const memberBond = minimumBond.muln(2) // want to be able to open 2 keeps - await initializeMemberCandidates(memberBond) + await initializeMemberCandidates(members, memberBond) await registerMemberCandidates() }) @@ -1508,6 +1528,106 @@ describe("FullyBackedECDSAKeepFactory", function () { }) }) + describe("banKeepMembers", async () => { + let operators + + before(async () => { + operators = members + + // Adding more operators to check that only keep members get banned. + operators.push(await newAccount()) + operators.push(await newAccount()) + operators.push(await newAccount()) + + await initializeNewFactory() + await initializeMemberCandidates(operators) + await registerMemberCandidates(operators) + }) + + it("reverts when called by non-keep", async () => { + await expectRevert( + keepFactory.banKeepMembers(), + "Caller is not a keep created by the factory" + ) + }) + + it("bans keep members in a pool they are registered", async () => { + const keep = await openKeep() + + await keep.publicSlashForSignatureFraud() + const keepMembers = await keep.getMembers() + + const pool = await FullyBackedSortitionPool.at(signerPoolAddress) + + for (let i = 0; i < operators.length; i++) { + const operator = operators[i] + + if (keepMembers.includes(operator)) { + assert.isFalse( + await keepFactory.isOperatorRegistered(operator, application) + ) + + assert.isTrue(await pool.bannedOperators(operator)) + } else { + assert.isFalse(await pool.bannedOperators(operator)) + } + } + }) + + it("does not ban keep members in a pool they are not registered", async () => { + const keep = await openKeep() + + const application2 = "0x0000000000000000000000000000000000000002" + const pool2Address = await keepFactory.createSortitionPool.call( + application2 + ) + await keepFactory.createSortitionPool(application2) + const pool2 = await FullyBackedSortitionPool.at(pool2Address) + + await keep.publicSlashForSignatureFraud() + + for (let i = 0; i < operators.length; i++) { + const operator = operators[i] + + assert.isFalse(await pool2.bannedOperators(operator)) + } + }) + + it("emits events", async () => { + const blockNumber = await web3.eth.getBlockNumber() + + const keep = await openKeep() + + await keep.publicSlashForSignatureFraud() + const keepMembers = await keep.getMembers() + + const eventList = await keepFactory.getPastEvents("OperatorBanned", { + fromBlock: blockNumber, + toBlock: "latest", + }) + + assert.equal( + eventList.length, + keepMembers.length, + "incorrect number of emitted events" + ) + + for (let i = 0; i < eventList.length; i++) { + assert.equal( + eventList[i].returnValues.operator, + keepMembers[i], + `incorrect operator address in event ${i}` + ) + + assert.equal( + eventList[i].returnValues.application, + application, + `incorrect application address in event ${i}` + ) + } + }) + }) + describe("getSortitionPoolWeight", async () => { before(async () => { await initializeNewFactory() @@ -1521,10 +1641,10 @@ describe("FullyBackedECDSAKeepFactory", function () { const expectedPoolWeight = minimumDelegationDeposit .div(await keepFactory.bondWeightDivisor.call()) - .muln(3) - expect(poolWeight).to.eq.BN( - expectedPoolWeight, - "incorrect sortition pool weight" + .muln(members.length) + + expect(poolWeight, "incorrect sortition pool weight").to.eq.BN( + expectedPoolWeight ) }) @@ -1579,7 +1699,7 @@ describe("FullyBackedECDSAKeepFactory", function () { delegationInitPeriod ) randomBeacon = await RandomBeaconStub.new() - const keepMasterContract = await FullyBackedECDSAKeep.new() + const keepMasterContract = await FullyBackedECDSAKeepStub.new() keepFactory = await FullyBackedECDSAKeepFactoryStub.new( keepMasterContract.address, sortitionPoolFactory.address, @@ -1593,16 +1713,21 @@ describe("FullyBackedECDSAKeepFactory", function () { await registry.approveOperatorContract(keepFactory.address) } - async function initializeMemberCandidates(unbondedValue) { + async function initializeMemberCandidates( + operators = members, + unbondedValue + ) { const minimumDelegationValue = await bonding.MINIMUM_DELEGATION_DEPOSIT.call() - signerPool = await keepFactory.createSortitionPool.call(application) + const authorizers = operators + + signerPoolAddress = await keepFactory.createSortitionPool.call(application) await keepFactory.createSortitionPool(application) - for (let i = 0; i < members.length; i++) { + for (let i = 0; i < operators.length; i++) { await delegate( - members[i], - members[i], + operators[i], + operators[i], authorizers[i], unbondedValue || minimumDelegationValue ) @@ -1620,7 +1745,7 @@ describe("FullyBackedECDSAKeepFactory", function () { await bonding.authorizeOperatorContract(operator, keepFactory.address, { from: authorizer, }) - await bonding.authorizeSortitionPoolContract(operator, signerPool, { + await bonding.authorizeSortitionPoolContract(operator, signerPoolAddress, { from: authorizer, }) } @@ -1645,14 +1770,19 @@ describe("FullyBackedECDSAKeepFactory", function () { } } - async function registerMemberCandidates() { - for (let i = 0; i < members.length; i++) { - await keepFactory.registerMemberCandidate(application, { - from: members[i], + async function registerMemberCandidates( + operators = members, + app = application + ) { + for (let i = 0; i < operators.length; i++) { + await keepFactory.registerMemberCandidate(app, { + from: operators[i], }) } - const pool = await FullyBackedSortitionPool.at(signerPool) + const pool = await FullyBackedSortitionPool.at( + await keepFactory.getSortitionPool.call(app) + ) const poolInitBlocks = await pool.operatorInitBlocks() await mineBlocks(poolInitBlocks.add(new BN(1))) } @@ -1681,6 +1811,20 @@ describe("FullyBackedECDSAKeepFactory", function () { } ) - return await FullyBackedECDSAKeep.at(keepAddress) + return await FullyBackedECDSAKeepStub.at(keepAddress) + } + + async function newAccount(initialBalanceETH = "1") { + const account = await web3.eth.personal.newAccount("pass") + + await web3.eth.personal.unlockAccount(account, "pass", 5000) // 5 sec unlock + + web3.eth.sendTransaction({ + from: accounts[0], + to: account, + value: web3.utils.toWei(initialBalanceETH, "ether"), + }) + + return account } }) diff --git a/solidity/test/FullyBackedECDSAKeepTest.js b/solidity/test/FullyBackedECDSAKeepTest.js index cab27c623..799bd8a99 100644 --- a/solidity/test/FullyBackedECDSAKeepTest.js +++ b/solidity/test/FullyBackedECDSAKeepTest.js @@ -31,6 +31,8 @@ chai.use(require("bn-chai")(BN)) const expect = chai.expect const assert = chai.assert +// TODO: Refactor tests by pulling common parts of BondedECDSAKeep and +// FullyBackedBondedECDSAKeep to one file. describe("FullyBackedECDSAKeep", function () { const bondCreator = accounts[0] const owner = accounts[1] @@ -834,54 +836,40 @@ describe("FullyBackedECDSAKeep", function () { assert.isTrue(res, "incorrect returned result") - // TODO: Verify that member is banned - }) - - // TODO: Modify this case after adding banning functionality. - // it("should prevent from slashing members multiple times for the same fradulent preimage", async () => { - // await submitMembersPublicKeys(publicKey1) - - // const memberStake = web3.utils.toBN("100000000000000000000000") - // // setting a value other then the min stake for testing purposes - // await keep.setMemberStake(memberStake) - - // assert.isFalse( - // await keep.isFradulentPreimageSet(preimage1), - // "fradulent preimage should not have been set" - // ) - - // await keep.submitSignatureFraud( - // signature1.V, - // signature1.R, - // signature1.S, - // hash256Digest1, - // preimage1 - // ) - - // assert.isTrue( - // await keep.isFradulentPreimageSet(preimage1), - // "fradulent preimage should have been set" - // ) - - // await keep.submitSignatureFraud( - // signature1.V, - // signature1.R, - // signature1.S, - // hash256Digest1, - // preimage1 - // ) - - // for (let i = 0; i < members.length; i++) { - // const actualStake = await tokenStaking.eligibleStake( - // members[i], - // constants.ZERO_ADDRESS - // ) - // expect(actualStake).to.eq.BN( - // minimumStake.sub(memberStake), - // `incorrect stake for member ${i}` - // ) - // } - // }) + assert.equal(await factoryStub.banKeepMembersCalledCount(keep.address), 1) + }) + + it("should prevent from slashing members multiple times for the same fradulent preimage", async () => { + await submitMembersPublicKeys(publicKey1) + + assert.isFalse( + await keep.isFradulentPreimageSet(preimage1), + "fradulent preimage should not have been set" + ) + + await keep.submitSignatureFraud( + signature1.V, + signature1.R, + signature1.S, + hash256Digest1, + preimage1 + ) + + assert.isTrue( + await keep.isFradulentPreimageSet(preimage1), + "fradulent preimage should have been set" + ) + + await keep.submitSignatureFraud( + signature1.V, + signature1.R, + signature1.S, + hash256Digest1, + preimage1 + ) + + assert.equal(await factoryStub.banKeepMembersCalledCount(keep.address), 1) + }) it("should revert when the signature is not fraudulent", async () => { await submitMembersPublicKeys(publicKey1) @@ -899,7 +887,7 @@ describe("FullyBackedECDSAKeep", function () { "Signature is not fraudulent" ) - // TODO: Verify that member has not been banned + assert.equal(await factoryStub.banKeepMembersCalledCount(keep.address), 0) }) it("reverts if called for closed keep", async () => { From 33a47a910766a7886677ee75bb10eb178f5e498b Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Mon, 2 Nov 2020 17:44:15 +0100 Subject: [PATCH 5/5] Fixed ban members docs --- solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol b/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol index 7ab33e906..43eae2cf3 100644 --- a/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol +++ b/solidity/contracts/fully-backed/FullyBackedECDSAKeepFactory.sol @@ -246,7 +246,7 @@ contract FullyBackedECDSAKeepFactory is return bonding.isAuthorizedForOperator(_operator, address(this)); } - /// @notice Bans members of a calling keep in an sortition pools associated + /// @notice Bans members of a calling keep in a sortition pool associated /// with the application for which the keep was created. /// @dev The function can be called only by a keep created by this factory. function banKeepMembers() public onlyKeep() {