From af3d0214a140043025c388c1a4a900fcb17f20f6 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 5 Jan 2018 17:21:15 -0800 Subject: [PATCH] make the automatic disavowal actually apply as disavowal. Add a function to markets to do disavowal of crowdsourcers --- .../reporting/BaseReportingParticipant.sol | 2 +- source/contracts/reporting/Market.sol | 14 +++++-- tests/reporting/test_fee_distribution.py | 40 +++++++++++-------- tests/reporting/test_reporting.py | 25 +++++++----- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/source/contracts/reporting/BaseReportingParticipant.sol b/source/contracts/reporting/BaseReportingParticipant.sol index 209c3dad3..5c9e63307 100644 --- a/source/contracts/reporting/BaseReportingParticipant.sol +++ b/source/contracts/reporting/BaseReportingParticipant.sol @@ -72,7 +72,7 @@ contract BaseReportingParticipant is Controlled, IReportingParticipant { } function isDisavowed() public view returns (bool) { - return market == IMarket(0); + return market == IMarket(0) || !market.isContainerForReportingParticipant(this); } function getPayoutNumerator(uint8 _outcome) public view returns (uint256) { diff --git a/source/contracts/reporting/Market.sol b/source/contracts/reporting/Market.sol index 396e04988..487d5e02c 100644 --- a/source/contracts/reporting/Market.sol +++ b/source/contracts/reporting/Market.sol @@ -271,9 +271,6 @@ contract Market is DelegationTarget, Extractable, ITyped, Initializable, Ownable // reset state back to Initial Reporter feeWindow = IFeeWindow(0); IInitialReporter _initialParticipant = getInitialReporter(); - for (uint8 i = 1; i < participants.length; ++i) { - IDisputeCrowdsourcer(participants[i]).disavow(); - } delete participants; participants.push(_initialParticipant); _initialParticipant.resetReportTimestamp(); @@ -281,6 +278,17 @@ contract Market is DelegationTarget, Extractable, ITyped, Initializable, Ownable return true; } + function disavowCrowdsourcers() public onlyInGoodTimes returns (bool) { + IMarket _forkingMarket = getForkingMarket(); + require(_forkingMarket != IMarket(0)); + require(_forkingMarket != this); + IInitialReporter _initialParticipant = getInitialReporter(); + delete participants; + participants.push(_initialParticipant); + crowdsourcers = MapFactory(controller.lookup("MapFactory")).createMap(controller, this); + return true; + } + function withdrawInEmergency() public onlyInBadTimes onlyOwner returns (bool) { IReputationToken _reputationToken = getReputationToken(); uint256 _repBalance = _reputationToken.balanceOf(this); diff --git a/tests/reporting/test_fee_distribution.py b/tests/reporting/test_fee_distribution.py index 26e1d42ad..76e600096 100644 --- a/tests/reporting/test_fee_distribution.py +++ b/tests/reporting/test_fee_distribution.py @@ -79,7 +79,11 @@ def test_initial_report_and_participation_fee_collection(localFixture, universe, with EtherDelta(expectedFees, tester.a0, localFixture.chain, "Redeeming didn't increase ETH correctly"): assert categoricalInitialReport.redeem(tester.a0) -def test_failed_crowdsourcer_fees(localFixture, universe, market, cash, reputationToken): +@mark.parametrize('finalize', [ + True, + False, +]) +def test_failed_crowdsourcer_fees(finalize, localFixture, universe, market, cash, reputationToken): feeWindow = localFixture.applySignature('FeeWindow', market.getFeeWindow()) # generate some fees @@ -92,31 +96,35 @@ def test_failed_crowdsourcer_fees(localFixture, universe, market, cash, reputati generateFees(localFixture, universe, market) # We'll have testers contribute to a dispute but not reach the target - amount = market.getTotalStake() - 1 + amount = market.getTotalStake() - with TokenDelta(reputationToken, -amount, tester.a1, "Disputing did not reduce REP balance correctly"): - assert market.contribute([0, market.getNumTicks()], False, amount, sender=tester.k1, startgas=long(6.7 * 10**6)) + with TokenDelta(reputationToken, -amount + 1, tester.a1, "Disputing did not reduce REP balance correctly"): + assert market.contribute([1, market.getNumTicks()-1], False, amount - 1, sender=tester.k1, startgas=long(6.7 * 10**6)) - with TokenDelta(reputationToken, -amount, tester.a2, "Disputing did not reduce REP balance correctly"): - assert market.contribute([0, market.getNumTicks()], False, amount, sender=tester.k2, startgas=long(6.7 * 10**6)) + with TokenDelta(reputationToken, -amount + 1, tester.a2, "Disputing did not reduce REP balance correctly"): + assert market.contribute([1, market.getNumTicks()-1], False, amount - 1, sender=tester.k2, startgas=long(6.7 * 10**6)) assert market.getFeeWindow() == feeWindow.address - payoutDistributionHash = market.derivePayoutDistributionHash([0, market.getNumTicks()], False) + payoutDistributionHash = market.derivePayoutDistributionHash([1, market.getNumTicks()-1], False) failedCrowdsourcer = localFixture.applySignature("DisputeCrowdsourcer", market.getCrowdsourcer(payoutDistributionHash)) - # Fast forward time until the fee window is over and we can redeem to recieve the REP back and fees - localFixture.contracts["Time"].setTimestamp(feeWindow.getEndTime() + 1) - assert market.finalize() - - # The dispute crowdsourcer contributor locked in REP for 2 rounds, as did the Initial Reporter - expectedTotalFees = getExpectedFees(localFixture, cash, failedCrowdsourcer, 1) - - with TokenDelta(reputationToken, amount, tester.a1, "Redeeming did not refund REP"): + if finalize: + # Fast forward time until the fee window is over and we can redeem to recieve the REP back and fees + localFixture.contracts["Time"].setTimestamp(feeWindow.getEndTime() + 1) + expectedTotalFees = getExpectedFees(localFixture, cash, failedCrowdsourcer, 1) + else: + # Continue to the next round which will disavow failed crowdsourcers and let us redeem once the window is over + market.contribute([0, market.getNumTicks()], False, amount * 2, startgas=long(6.7 * 10**6)) + assert market.getFeeWindow() != feeWindow.address + localFixture.contracts["Time"].setTimestamp(feeWindow.getEndTime() + 1) + expectedTotalFees = getExpectedFees(localFixture, cash, failedCrowdsourcer, 1) + + with TokenDelta(reputationToken, amount - 1, tester.a1, "Redeeming did not refund REP"): with EtherDelta(expectedTotalFees / 2, tester.a1, localFixture.chain, "Redeeming didn't increase ETH correctly"): assert failedCrowdsourcer.redeem(tester.a1) - with TokenDelta(reputationToken, amount, tester.a2, "Redeeming did not refund REP"): + with TokenDelta(reputationToken, amount - 1, tester.a2, "Redeeming did not refund REP"): with EtherDelta(cash.balanceOf(failedCrowdsourcer.address), tester.a2, localFixture.chain, "Redeeming didn't increase ETH correctly"): assert failedCrowdsourcer.redeem(tester.a2) diff --git a/tests/reporting/test_reporting.py b/tests/reporting/test_reporting.py index 54f7aa3d0..a8a9874cf 100644 --- a/tests/reporting/test_reporting.py +++ b/tests/reporting/test_reporting.py @@ -146,11 +146,13 @@ def test_roundsOfReporting(rounds, localFixture, market, universe): feeWindow = market.getFeeWindow() assert feeWindow == universe.getCurrentFeeWindow() -@mark.parametrize('finalizeByMigration', [ - True, - False +@mark.parametrize('finalizeByMigration, manuallyDisavow', [ + (True, True), + (False, True), + (True, False), + (False, False), ]) -def test_forking(finalizeByMigration, localFixture, universe, market, categoricalMarket): +def test_forking(finalizeByMigration, manuallyDisavow, localFixture, universe, market, categoricalMarket): # Let's go into the one dispute round for the categorical market proceedToNextRound(localFixture, categoricalMarket) proceedToNextRound(localFixture, categoricalMarket) @@ -165,17 +167,21 @@ def test_forking(finalizeByMigration, localFixture, universe, market, categorica numTicks = market.getNumTicks() childUniverse = universe.createChildUniverse([numTicks/ 4, numTicks * 3 / 4], False) + # confirm that before the fork is finalized we can redeem stake in other markets crowdsourcers, which are disavowable + categoricalDisputeCrowdsourcer = localFixture.applySignature("DisputeCrowdsourcer", categoricalMarket.getReportingParticipant(1)) + + if manuallyDisavow: + assert categoricalMarket.disavowCrowdsourcers() + # We can redeem before the fork finalizes since disavowal has occured + assert categoricalDisputeCrowdsourcer.redeem(tester.a0) + # finalize the fork finalizeFork(localFixture, market, universe, finalizeByMigration) - # get the reporting participants for the categorical market before they may be disavowed - categoricalInitialReport = localFixture.applySignature("InitialReporter", categoricalMarket.getReportingParticipant(0)) - categoricalDisputeCrowdsourcer = localFixture.applySignature("DisputeCrowdsourcer", categoricalMarket.getReportingParticipant(1)) - # The categorical market can be migrated to the winning universe assert categoricalMarket.migrateThroughOneFork() - # This disavows the dispute crowdsourcer + # The dispute crowdsourcer has been disavowed newUniverse = localFixture.applySignature("Universe", categoricalMarket.getUniverse()) assert newUniverse.address != universe.address assert categoricalDisputeCrowdsourcer.isDisavowed() @@ -183,6 +189,7 @@ def test_forking(finalizeByMigration, localFixture, universe, market, categorica assert not newUniverse.isContainerForReportingParticipant(categoricalDisputeCrowdsourcer.address) # The initial report is still present however + categoricalInitialReport = localFixture.applySignature("InitialReporter", categoricalMarket.getReportingParticipant(0)) assert categoricalMarket.getReportingParticipant(0) == categoricalInitialReport.address assert not categoricalInitialReport.isDisavowed() assert not universe.isContainerForReportingParticipant(categoricalInitialReport.address)