From 9d48ae495ba58ab6a57f41973c9edf8875d46443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Tue, 15 Oct 2019 09:58:51 +0200 Subject: [PATCH 1/2] Court: Add tests to check global funds accountancy --- .travis.yml | 3 + test/accountancy/check_funds.js | 399 ++++++++++++++++++++++++++++++++ test/helpers/accounting.js | 321 +++++++++++++++++++++++++ test/helpers/court.js | 7 +- 4 files changed, 728 insertions(+), 2 deletions(-) create mode 100644 test/accountancy/check_funds.js create mode 100644 test/helpers/accounting.js diff --git a/.travis.yml b/.travis.yml index db115c96..7f594edc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,3 +40,6 @@ jobs: - stage: tests script: npm test test/controller/*.js name: "Controller" + - stage: tests + script: npm test test/accounting/*.js + name: "Accounting" diff --git a/test/accountancy/check_funds.js b/test/accountancy/check_funds.js new file mode 100644 index 00000000..d60f072f --- /dev/null +++ b/test/accountancy/check_funds.js @@ -0,0 +1,399 @@ +const { toChecksumAddress } = require('web3-utils') +const { bn, bigExp, assertBn } = require('../helpers/numbers') +const { buildHelper, PCT_BASE, DEFAULTS } = require('../helpers/court')(web3, artifacts) +const { NEXT_WEEK, ONE_DAY } = require('../helpers/time') +const { + // utils + buildOriginalFundsState, + // checks + checkEmptyBalances, + checkJurorTokenBalances, + checkAllFeeTokensBalances, + // funds state actions + fundsActions, +} = require('../helpers/accounting.js') + +const ERC20 = artifacts.require('ERC20Mock') +const Arbitrable = artifacts.require('ArbitrableMock') +const Accounting = artifacts.require('CourtAccounting') +const JurorsRegistry = artifacts.require('JurorsRegistryMock') + +contract('Court global accountancy', ( + [owner, user1, user2, juror1, juror2, juror3, juror4, juror5, juror6, juror7] +) => { + const users = [ user1, user2 ] + const jurors = [ juror1, juror2, juror3, juror4, juror5, juror6, juror7 ] + let courtHelper, court, accounting, jurorsRegistry, jurorToken, feeToken + + const termDuration = bn(ONE_DAY) + const firstTermStartTime = bn(NEXT_WEEK) + const firstRoundJurorsNumber = 3 + + const jurorFee = bigExp(10, 18) + const heartbeatFee = bigExp(20, 18) + const draftFee = bigExp(30, 18) + const settleFee = bigExp(40, 18) + + const checkFundsState = async (fundsState) => { + // Court balances should be empty + await checkEmptyBalances(court.address, fundsState.feeTokens.concat([ fundsState.jurorToken ]), 'Court', web3, ERC20) + + // Check juror's token global balances + await checkJurorTokenBalances(fundsState, jurorsRegistry, ERC20) + + // Check fee tokens global balances + await checkAllFeeTokensBalances(fundsState, accounting, ERC20) + } + + const runAndCheck = async (receiptPromise, fundsAction, originalFundsState) => { + const actionReturnValue = await receiptPromise + + const newFundsState = fundsAction.fn(originalFundsState, fundsAction.params, actionReturnValue) + await checkFundsState(newFundsState) + + return { newFundsState, actionReturnValue } + } + + beforeEach('create court', async () => { + courtHelper = buildHelper() + feeToken = await ERC20.new('Court Fee Token', 'CFT', 18) + jurorToken = await ERC20.new('Aragon Network Juror Token', 'ANJ', 18) + accounting = await Accounting.new() + jurorsRegistry = await JurorsRegistry.new() + court = await courtHelper.deploy({ + firstTermStartTime, + termDuration, + feeToken, + jurorFee, + heartbeatFee, + draftFee, + settleFee, + firstRoundJurorsNumber, + accounting, + jurorsRegistry, + jurorToken + }) + }) + + const activate = async (fundsState) => { + const jurorsWithBalances = jurors.map(juror => { + return { address: juror, initialActiveBalance: bigExp(10, 21) } + }) + + const { newFundsState } = await runAndCheck( + courtHelper.activate(jurorsWithBalances), + { + fn: fundsActions.activate, + params: jurorsWithBalances + }, + fundsState + ) + + return newFundsState + } + + const dispute = async (fundsState, draftTermId, user) => { + const disputeFeesInfo = await court.getDisputeFees(draftTermId) + const { newFundsState, actionReturnValue } = await runAndCheck( + courtHelper.dispute({ + draftTermId, + disputer: user1 + }), + { + fn: fundsActions.dispute, + params: disputeFeesInfo + }, + fundsState + ) + + return { fundsState: newFundsState, disputeId: actionReturnValue, roundId: bn(0) } + } + + const heartbeat = async (fundsState, currentTermId, desiredTermId, sender) => { + await courtHelper.increaseTime(termDuration.mul(desiredTermId.sub(currentTermId))) + const neededTransitions = await court.neededTermTransitions() + let terms = [] + for (let termId = currentTermId.toNumber(); termId < desiredTermId.toNumber(); termId++) { + const courtConfig = await court.getCourtConfig(termId) + const term = await court.getTerm(termId + 1) + terms.push({ + feeToken: courtConfig.feeToken, + heartbeatFee: courtConfig.fees[1], + dependingDrafts: term.dependingDrafts + }) + } + const { newFundsState, actionReturnValue } = await runAndCheck( + court.heartbeat(neededTransitions, { from: sender }), + { + fn: fundsActions.heartbeat, + params: { + sender, + terms + } + }, + fundsState + ) + + // advance 2 blocks to ensure we can compute term randomness + await courtHelper.advanceBlocks(2) + + return newFundsState + } + + const draft = async (fundsState, disputeId, draftTermId, sender) => { + const courtConfig = await court.getCourtConfig(draftTermId) + const { newFundsState, actionReturnValue } = await runAndCheck( + courtHelper.draft({ + disputeId, + drafter: sender + }), + { + fn: fundsActions.draft, + params: { + sender, + feeToken: courtConfig.feeToken, + draftFee: courtConfig.fees[2] + } + }, + fundsState + ) + + // sort jurors by descending weight, so we make sure that winning outcome will always be LOW when using courtHelper methods for commit and reveal + const draftedJurors = actionReturnValue.sort((a, b) => a.weight.lt(b.weight) ? 1 : -1 ).map(j => { j.address = toChecksumAddress(j.address); return j }) + + return { fundsState: newFundsState, draftedJurors } + } + + const appeal = async (fundsState, disputeId, roundId, appealMaker) => { + const nextRoundInfo = await court.getNextRoundDetails(disputeId, roundId) + + const { newFundsState } = await runAndCheck( + courtHelper.appeal({ disputeId, roundId, appealMaker }), + { + fn: fundsActions.appeal, + params: { + sender: appealMaker, + feeToken: nextRoundInfo.feeToken, + appealDeposit: nextRoundInfo.appealDeposit + } + }, + fundsState + ) + + return newFundsState + } + + const confirmAppeal = async (fundsState, disputeId, roundId, appealTaker) => { + const nextRoundInfo = await court.getNextRoundDetails(disputeId, roundId) + + const { newFundsState, actionReturnValue } = await runAndCheck( + courtHelper.confirmAppeal({ disputeId, roundId, appealTaker }), + { + fn: fundsActions.confirmAppeal, + params: { + sender: appealTaker, + feeToken: nextRoundInfo.feeToken, + confirmAppealDeposit: nextRoundInfo.confirmAppealDeposit + } + }, + fundsState + ) + + return { fundsState: newFundsState, roundId: actionReturnValue } + } + + const settleRegularRoundPenalties = async (fundsState, disputeId, roundId, jurorsToSettle, sender) => { + const { draftTerm } = await court.getRound(disputeId, bn(0)) + const config = await court.getCourtConfig(draftTerm) + + const minActiveBalance = await jurorsRegistry.minJurorsActiveBalance() + const penalty = minActiveBalance.mul(config.pcts[0]).div(PCT_BASE) + const { newFundsState } = await runAndCheck( + court.settlePenalties(disputeId, roundId, jurorsToSettle.length, { from: sender }), + { + fn: fundsActions.settleRegularRoundPenalties, + params: { + sender, + feeToken: config.feeToken, + settleFee: config.fees[3], + jurorsToSettle: jurorsToSettle, + penalty + } + }, + fundsState + ) + + return newFundsState + } + + const settlePenalties = async (fundsState, disputeId, lastRoundId, roundDraftedJurors, sender) => { + let newFundsState = fundsState + + for (let i = 0; i <= lastRoundId.toNumber(); i++) { + fundsState = await settleRegularRoundPenalties(fundsState, disputeId, i, roundDraftedJurors[i], user1); + } + + return newFundsState + } + + const settleRoundJurorReward = async (fundsState, disputeId, roundId, juror, coherentJurors) => { + const { draftTerm } = await court.getRound(disputeId, bn(0)) + const config = await court.getCourtConfig(draftTerm) + + const round = await court.getRound(disputeId, roundId) + const { newFundsState } = await runAndCheck( + court.settleReward(disputeId, roundId, juror.address), + { + fn: fundsActions.settleReward, + params: { + feeToken: config.feeToken, + jurorFees: round.jurorFees, + collectedTokens: round.collectedTokens, + juror: juror, + coherentJurors, + } + }, + fundsState + ) + + return newFundsState + } + + const settleRewards = async (fundsState, disputeId, lastRoundId, roundDraftedJurors) => { + let newFundsState = fundsState + for (let i = 0; i <= bn(lastRoundId); i++) { + // only jurors with even index are coherent with winning ruling + const coherentJurors = roundDraftedJurors[i].reduce((acc, juror, j) => { + if (j % 2 == 0) { + acc = acc.add(juror.weight) + } + return acc + }, bn(0)) + for (let j = 0; j < roundDraftedJurors[i].length; j++) { + if (j % 2 == 0) { + await settleRoundJurorReward(newFundsState, disputeId, bn(i), roundDraftedJurors[i][j], coherentJurors) + } + } + } + + return newFundsState + } + + const settleAppealDeposit = async (fundsState, disputeId, roundId, winner) => { + const nextRoundInfo = await court.getNextRoundDetails(disputeId, roundId) + + const { newFundsState } = await runAndCheck( + court.settleAppealDeposit(disputeId, roundId), + { + fn: fundsActions.settleAppealDeposit, + params: { + feeToken: nextRoundInfo.feeToken, + totalFees: nextRoundInfo.totalFees, + appealDeposit: nextRoundInfo.appealDeposit, + confirmAppealDeposit: nextRoundInfo.confirmAppealDeposit, + winner + } + }, + fundsState + ) + + return newFundsState + } + + const settleAppeals = async (fundsState, disputeId, lastRoundId, winner) => { + let newFundsState = fundsState + + for (let i = 0; i < lastRoundId.toNumber(); i++) { + fundsState = await settleAppealDeposit(fundsState, disputeId, i, winner); + } + + return newFundsState + } + + context('Main sequence', () => { + it('does stuff', async () => { + let fundsState, result + let currentTermId, draftTermId, disputeId, roundId, draftedJurors, roundDraftedJurors + + currentTermId = bn(0) + roundDraftedJurors = [] + fundsState = buildOriginalFundsState(users, jurors, jurorToken, [ feeToken ]) + + // activate + fundsState = await activate(fundsState) + + // dispute + draftTermId = bn(1); + ({ fundsState, disputeId, roundId } = await dispute(fundsState, draftTermId, user1)); + + // heartbeat + fundsState = await heartbeat(fundsState, currentTermId, draftTermId, user1); + + // draft + ({ fundsState, draftedJurors } = await draft(fundsState, disputeId, draftTermId, user1)) + roundDraftedJurors.push(draftedJurors) + + + // commit + await courtHelper.commit({ + disputeId, + roundId, + voters: draftedJurors + }) + + // reveal + await courtHelper.reveal({ + disputeId, + roundId, + voters: draftedJurors + }) + + // appeal + fundsState = await appeal(fundsState, disputeId, roundId, user2); + + // confirm appeal + ({ fundsState, roundId } = await confirmAppeal(fundsState, disputeId, roundId, user1)); + + // draft + ({ fundsState, draftedJurors } = await draft(fundsState, disputeId, draftTermId, user1)); + roundDraftedJurors.push(draftedJurors) + + + // commit + await courtHelper.commit({ + disputeId, + roundId, + voters: draftedJurors + }) + + // reveal + await courtHelper.reveal({ + disputeId, + roundId, + voters: draftedJurors + }) + + // pass appeal and confirm terms to make it final + await courtHelper.passTerms(DEFAULTS.appealTerms) + await courtHelper.passTerms(DEFAULTS.appealConfirmTerms) + + // settle penalties + fundsState = await settlePenalties(fundsState, disputeId, roundId, roundDraftedJurors, user1); + + // settle rewards + fundsState = await settleRewards(fundsState, disputeId, roundId, roundDraftedJurors); + + // settle appeal + fundsState = await settleAppeals(fundsState, disputeId, roundId, user1); + + }) + }) + // TODO: + // setConfig + // reach final round + // settle reward with no collected tokens + // settle non-confirmed appeal + // settle appeal oposed winning + // settle appeal rejected ruling + +}) diff --git a/test/helpers/accounting.js b/test/helpers/accounting.js new file mode 100644 index 00000000..a953ec68 --- /dev/null +++ b/test/helpers/accounting.js @@ -0,0 +1,321 @@ +const { bn, assertBn } = require('../helpers/numbers') + +// utils +const buildOriginalFundsState = (users, jurors, jurorToken, feeTokens) => { + const zeroFeeTokens = () => feeTokens.reduce((acc, token) => { + acc[token.address] = { + balance: bn(0), + inAccounting: bn(0) + } + return acc + }, {}) + + const fundsState = { + feeTokens: feeTokens.map(t => t.address), + jurorToken: jurorToken.address, + users: users.reduce((acc, user) => { + acc[user] = { + feeTokens: zeroFeeTokens() + } + return acc + }, {}), + jurors: jurors.reduce((acc, juror) => { + acc[juror] = { + jurorToken: { + outside: bn(0), // not in JurorsRegistry + available: bn(0), // in JurorsRegistry, but not active + active: bn(0) // in JurorsRegistry, in the tree + }, + feeTokens: zeroFeeTokens() + } + return acc + }, {}), + registry: { + jurorToken: { + remaining: bn(0) // balance in JurorsRegistry not belonging to any juror + } + }, + accounting: { + feeTokens: zeroFeeTokens() + }, + } + return fundsState +} + +// checks +const checkEmptyBalances = async (address, tokens, name, web3, ERC20) => { + const courtEthBalance = bn(await web3.eth.getBalance(address)) + assertBn(courtEthBalance, bn(0), `${name} contract should not hold ETH`) + await Promise.all(tokens.map( + async (tokenAddress) => { + const token = await ERC20.at(tokenAddress) + const tokenBalance = await token.balanceOf(address) + assertBn(tokenBalance, bn(0), `${name} contract should not hold any ${token.address}`) + } + )) +} + +const computeExpectedJurorsTotalSupply = (fundsState, balance) => { + const expectedJurorsTotalSupply = Object.values(fundsState.jurors).reduce((acc, juror) => { + return acc.add(juror.jurorToken[balance]) + }, bn(0)) + + return expectedJurorsTotalSupply +} + +const checkJurorTokenBalances = async (fundsState, jurorsRegistry, ERC20) => { + const jurorToken = await ERC20.at(fundsState.jurorToken) + const totalSupply = await jurorToken.totalSupply() + const jurorsRegistryBalance = await jurorToken.balanceOf(jurorsRegistry.address) + const treeBalance = await jurorsRegistry.totalActiveBalance() + let totalJurorBalances = bn(0) + for (const juror in fundsState.jurors) { + const jurorBalance = await jurorToken.balanceOf(juror) + assertBn(jurorBalance, fundsState.jurors[juror].jurorToken.outside, `Balance for juror ${juror} and token ${fundsState.jurorToken} doesn't match`) + totalJurorBalances = totalJurorBalances.add(jurorBalance) + } + + // expected values computed from jurors + const jurorsExpectedOutsideTotalSupply = computeExpectedJurorsTotalSupply(fundsState, 'outside') + const jurorsExpectedAvailableTotalSupply = computeExpectedJurorsTotalSupply(fundsState, 'available') + const jurorsExpectedActiveTotalSupply = computeExpectedJurorsTotalSupply(fundsState, 'active') + const jurorsRegistryExpectedRemaining = fundsState.registry.jurorToken.remaining + + // checks + assertBn(totalSupply, jurorsRegistryBalance.add(totalJurorBalances), `Total supply for juror token doesn't match`) + assertBn(totalJurorBalances, jurorsExpectedOutsideTotalSupply, `Juror balances for juror token don't macth`) + assertBn(treeBalance, jurorsExpectedActiveTotalSupply, `Juror active token balances don't macth`) + assertBn(jurorsRegistryBalance.sub(treeBalance), jurorsExpectedAvailableTotalSupply.add(jurorsRegistryExpectedRemaining), `Juror inactive token balances don't match`) +} + +// check individual balances of a fee token for a group (jurors, users) +// return total amount of feeToken hold by a group (jurors, users) +const checkFeeTokenAndComputeSum = async (group, feeToken) => { + let totalBalances = bn(0) + for (const actor in group) { + const balance = await feeToken.balanceOf(actor) + assertBn(balance, group[actor].feeTokens[feeToken.address].balance, `Balance for ${actor} and token ${feeToken.address} doesn't match`) + totalBalances = totalBalances.add(balance) + } + return totalBalances +} + +// total amount of expected feeToken hold by a group (jurors, users) +const computeExpectedFeeTokenSum = (group, feeTokenAddress, where) => { + const expectedTotalSupply = Object.values(group).reduce((acc, actor) => { + return acc.add(actor.feeTokens[feeTokenAddress][where]) + }, bn(0)) + + return expectedTotalSupply +} + +const checkAccountingFeeTokenAndComputeSum = async (group, accounting, feeTokenAddress) => { + let totalBalances = bn(0) + for (const actor in group) { + const balance = await accounting.balanceOf(feeTokenAddress, actor) + assertBn(balance, group[actor].feeTokens[feeTokenAddress].inAccounting, `Balance for ${actor} and token ${feeTokenAddress} in CourtAccounting doesn't match`) + totalBalances = totalBalances.add(balance) + } + return totalBalances +} + +const checkFeeTokenBalances = async (fundsState, accounting, feeTokenAddress, ERC20) => { + const feeToken = await ERC20.at(feeTokenAddress) + const totalSupply = await feeToken.totalSupply() + + const accountingBalance = await feeToken.balanceOf(accounting.address) + + // real values computed from users and jurors + let totalUserBalances = await checkFeeTokenAndComputeSum(fundsState.users, feeToken) + let totalUserAccountingBalances = await checkAccountingFeeTokenAndComputeSum(fundsState.users, accounting, feeTokenAddress) + let totalJurorBalances = await checkFeeTokenAndComputeSum(fundsState.jurors, feeToken) + let totalJurorAccountingBalances = await checkAccountingFeeTokenAndComputeSum(fundsState.jurors, accounting, feeTokenAddress) + + // expected values computed from users and jurors + const userExpectedFeeTokenSum = computeExpectedFeeTokenSum(fundsState.users, feeTokenAddress, 'balance') + const userExpectedAccountingFeeTokenSum = computeExpectedFeeTokenSum(fundsState.users, feeTokenAddress, 'inAccounting') + const jurorExpectedFeeTokenSum = computeExpectedFeeTokenSum(fundsState.jurors, feeTokenAddress, 'balance') + const jurorExpectedAccountingFeeTokenSum = computeExpectedFeeTokenSum(fundsState.jurors, feeTokenAddress, 'inAccounting') + + // checks + // total supply + assertBn( + totalSupply, + accountingBalance.add(totalUserBalances).add(totalJurorBalances), + `Total supply for token ${feeTokenAddress} doesn't match` + ) + assert.isTrue( + totalSupply.gte(totalUserAccountingBalances.add(totalJurorAccountingBalances)), + `Total supply in accounting for token ${feeTokenAddress} should be greater or equal than the sum of individual local balances` + ) + + // users + assertBn(totalUserBalances, userExpectedFeeTokenSum, `User balances for token ${feeTokenAddress} don't macth`) + assertBn(totalUserAccountingBalances, userExpectedAccountingFeeTokenSum, `User balances for token ${feeTokenAddress} don't macth`) + + // jurors + assertBn(totalJurorBalances, jurorExpectedFeeTokenSum, `Juror balances for token ${feeTokenAddress} don't macth`) + assertBn(totalJurorAccountingBalances, jurorExpectedAccountingFeeTokenSum, `Juror balances for token ${feeTokenAddress} don't macth`) +} + +const checkAllFeeTokensBalances = async (fundsState, accounting, ERC20) => { + await Promise.all(fundsState.feeTokens.map(token => checkFeeTokenBalances(fundsState, accounting, token, ERC20))) +} + +// funds state actions +// All these function update the testing funds state object according to the corresponding action. They take up to 3 params: +// 1 - Original funds state object +// 2 - Params corresponding to the action needed to perform the computation, known in advance +// 3 - Optiona extra params coming from the real action output. Needed when randomnes is involved. +// For instance, when drafting, we need to know which jurors were drafted. As it's not the task of accounting tests +// to check that those jurors were the correct ones, we trust the contract on this, +// and avoid having to perform that computiation here (these is already done in registry unit tests) +const activate = (originalFundsState, jurors) => { + const newFundsState = Object.assign({}, originalFundsState) + + jurors.map(juror => { + newFundsState.jurors[juror.address].jurorToken.active = + newFundsState.jurors[juror.address].jurorToken.active.add(juror.initialActiveBalance) + }) + + return newFundsState +} + +const dispute = (originalFundsState, disputeFeesInfo) => { + const newFundsState = Object.assign({}, originalFundsState) + + const { feeToken, totalFees } = disputeFeesInfo + + newFundsState.accounting.feeTokens[feeToken].balance = + newFundsState.accounting.feeTokens[feeToken].balance.add(totalFees) + + return newFundsState +} + +const heartbeat = (originalFundsState, params) => { + const newFundsState = Object.assign({}, originalFundsState) + + const { sender, terms } = params + terms.map(term => { + const { feeToken, heartbeatFee, dependingDrafts } = term + newFundsState.users[sender].feeTokens[feeToken].inAccounting = + newFundsState.users[sender].feeTokens[feeToken].inAccounting.add(heartbeatFee.mul(dependingDrafts)) + }) + + return newFundsState +} + +const draft = (originalFundsState, params, draftedJurors) => { + const newFundsState = Object.assign({}, originalFundsState) + + const jurorsNumber = Object.values(draftedJurors).reduce((acc, juror) => { + return acc.add(juror.weight) + }, bn(0)) + + const { sender, feeToken, draftFee } = params + newFundsState.users[sender].feeTokens[feeToken].inAccounting = + newFundsState.users[sender].feeTokens[feeToken].inAccounting.add(draftFee.mul(jurorsNumber)) + + return newFundsState +} + +const appeal = (originalFundsState, params) => { + const newFundsState = Object.assign({}, originalFundsState) + + const { sender, feeToken, appealDeposit } = params + // Accounting contract + newFundsState.accounting.feeTokens[feeToken].balance = + newFundsState.accounting.feeTokens[feeToken].balance.add(appealDeposit) + // Appeal maker doesn't need to be updated because tokens are newly minted in courtHelper + + return newFundsState +} + +const confirmAppeal = (originalFundsState, params) => { + const newFundsState = Object.assign({}, originalFundsState) + + const { sender, feeToken, confirmAppealDeposit } = params + // Accounting contract + newFundsState.accounting.feeTokens[feeToken].balance = + newFundsState.accounting.feeTokens[feeToken].balance.add(confirmAppealDeposit) + // Appeal taker doesn't need to be updated because tokens are newly minted in courtHelper} + + return newFundsState +} + +const settleRegularRoundPenalties = (originalFundsState, params) => { + const newFundsState = Object.assign({}, originalFundsState) + + const { sender, feeToken, settleFee, jurorsToSettle, penalty } = params + // sender fees + newFundsState.users[sender].feeTokens[feeToken].inAccounting = + newFundsState.users[sender].feeTokens[feeToken].inAccounting.add(settleFee.mul(bn(jurorsToSettle.length))) + // jurors + // even jurors (odd index, as it starts wit zero) are losing + for (let i = 1; i < jurorsToSettle.length; i += 2) { + const weightedPenalty = penalty.mul(jurorsToSettle[i].weight) + newFundsState.jurors[jurorsToSettle[i].address].jurorToken.active = + newFundsState.jurors[jurorsToSettle[i].address].jurorToken.active.sub(weightedPenalty) + // add to jurors registry remaining balance + newFundsState.registry.jurorToken.remaining = + newFundsState.registry.jurorToken.remaining.add(weightedPenalty) + } + + return newFundsState +} + +const settleReward = (originalFundsState, params) => { + const newFundsState = Object.assign({}, originalFundsState) + + const { feeToken, jurorFees, collectedTokens, juror, coherentJurors } = params + + const weightedReward = collectedTokens.mul(juror.weight).div(coherentJurors) + // juror Tokens + newFundsState.jurors[juror.address].jurorToken.available = + newFundsState.jurors[juror.address].jurorToken.available.add(weightedReward) + // subtract jurors registry remaining balance + newFundsState.registry.jurorToken.remaining = + newFundsState.registry.jurorToken.remaining.sub(weightedReward) + + // fee Tokens + const weightedFees = jurorFees.mul(juror.weight).div(coherentJurors) + newFundsState.jurors[juror.address].feeTokens[feeToken].inAccounting = + newFundsState.jurors[juror.address].feeTokens[feeToken].inAccounting.add(weightedFees) + + + return newFundsState +} + +const settleAppealDeposit = (originalFundsState, params) => { + const newFundsState = Object.assign({}, originalFundsState) + + const { feeToken, totalFees, appealDeposit, confirmAppealDeposit, winner } = params + + // fee Tokens + const reward = appealDeposit.add(confirmAppealDeposit).sub(totalFees) + newFundsState.users[winner].feeTokens[feeToken].inAccounting = + newFundsState.users[winner].feeTokens[feeToken].inAccounting.add(reward) + + return newFundsState +} + +module.exports = { + // utils + buildOriginalFundsState, + // checks + checkEmptyBalances, + checkJurorTokenBalances, + checkAllFeeTokensBalances, + // funds state actions + fundsActions: { + activate, + dispute, + heartbeat, + draft, + appeal, + confirmAppeal, + settleRegularRoundPenalties, + settleReward, + settleAppealDeposit, + }, +} diff --git a/test/helpers/court.js b/test/helpers/court.js index 026ca7d3..2e75ef24 100644 --- a/test/helpers/court.js +++ b/test/helpers/court.js @@ -345,7 +345,7 @@ module.exports = (web3, artifacts) => { maxJurorsToBeDrafted = roundJurorsNumber.toNumber() } - // mock draft if there was a jurors set to be drafted + // mock draft if there was a juror set to be drafted if (draftedJurors) { const totalWeight = draftedJurors.reduce((total, { weight }) => total + weight, 0) if (totalWeight !== maxJurorsToBeDrafted) throw Error('Given jurors to be drafted do not fit the round jurors number') @@ -429,8 +429,10 @@ module.exports = (web3, artifacts) => { } // confirm appeal and move to end of confirm appeal period - await this.court.confirmAppeal(disputeId, roundId, ruling, { from: appealTaker }) + const receipt = await this.court.confirmAppeal(disputeId, roundId, ruling, { from: appealTaker }) await this.passTerms(this.appealConfirmTerms) + + return getEventArgument(receipt, 'RulingAppealConfirmed', 'roundId') } async moveToFinalRound({ disputeId }) { @@ -500,6 +502,7 @@ module.exports = (web3, artifacts) => { } return { + PCT_BASE, DEFAULTS, DISPUTE_STATES, ROUND_STATES, From e9bea9cb64915c2e77b4d7ab745a4bd97d77ec48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Mon, 21 Oct 2019 17:17:27 +0200 Subject: [PATCH 2/2] Court: accounting tests, fixes after merge of development --- test/accountancy/check_funds.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/accountancy/check_funds.js b/test/accountancy/check_funds.js index d60f072f..6298a3ba 100644 --- a/test/accountancy/check_funds.js +++ b/test/accountancy/check_funds.js @@ -15,6 +15,7 @@ const { const ERC20 = artifacts.require('ERC20Mock') const Arbitrable = artifacts.require('ArbitrableMock') +const Controller = artifacts.require('ControllerMock') const Accounting = artifacts.require('CourtAccounting') const JurorsRegistry = artifacts.require('JurorsRegistryMock') @@ -34,6 +35,9 @@ contract('Court global accountancy', ( const draftFee = bigExp(30, 18) const settleFee = bigExp(40, 18) + const MIN_ACTIVE_AMOUNT = bigExp(1, 18) + const TOTAL_ACTIVE_BALANCE_LIMIT = bigExp(100e6, 18) + const checkFundsState = async (fundsState) => { // Court balances should be empty await checkEmptyBalances(court.address, fundsState.feeTokens.concat([ fundsState.jurorToken ]), 'Court', web3, ERC20) @@ -56,11 +60,22 @@ contract('Court global accountancy', ( beforeEach('create court', async () => { courtHelper = buildHelper() + + // tokens feeToken = await ERC20.new('Court Fee Token', 'CFT', 18) jurorToken = await ERC20.new('Aragon Network Juror Token', 'ANJ', 18) - accounting = await Accounting.new() - jurorsRegistry = await JurorsRegistry.new() + + // controller + const controller = await Controller.new() + + // accounting + accounting = await Accounting.new(controller.address) + + // registry + jurorsRegistry = await JurorsRegistry.new(controller.address, jurorToken.address, MIN_ACTIVE_AMOUNT, TOTAL_ACTIVE_BALANCE_LIMIT) + court = await courtHelper.deploy({ + controller, firstTermStartTime, termDuration, feeToken,