├── contracts ├── .npmignore ├── test │ ├── TestImports.sol │ ├── court │ │ ├── controller │ │ │ ├── ControlledRecoverableMock.sol │ │ │ └── ControlledMock.sol │ │ └── AragonCourtMock.sol │ ├── arbitration │ │ ├── FakeArbitrableMock.sol │ │ └── ArbitrableMock.sol │ ├── lib │ │ ├── CheckpointingMock.sol │ │ ├── JurorsTreeSortitionMock.sol │ │ ├── HexSumTreeGasProfiler.sol │ │ ├── HexSumTreeMock.sol │ │ └── TimeHelpersMock.sol │ ├── subscriptions │ │ └── SubscriptionsMock.sol │ ├── disputes │ │ ├── DisputeManagerMockForVoting.sol │ │ └── DisputeManagerMockForRegistry.sol │ ├── registry │ │ └── JurorsRegistryMock.sol │ └── standards │ │ └── ERC20Mock.sol ├── lib │ ├── BytesHelpers.sol │ ├── os │ │ ├── Migrations.sol │ │ ├── IsContract.sol │ │ ├── Uint256Helpers.sol │ │ ├── ERC20.sol │ │ ├── TimeHelpers.sol │ │ ├── SafeMath64.sol │ │ ├── SafeMath.sol │ │ └── SafeERC20.sol │ ├── PctHelpers.sol │ └── JurorsTreeSortition.sol ├── standards │ ├── ERC165.sol │ ├── ApproveAndCall.sol │ └── ERC900.sol ├── treasury │ ├── ITreasury.sol │ └── CourtTreasury.sol ├── subscriptions │ └── ISubscriptions.sol ├── voting │ ├── ICRVotingOwner.sol │ └── ICRVoting.sol ├── court │ ├── controller │ │ ├── ControlledRecoverable.sol │ │ └── Controlled.sol │ ├── clock │ │ └── IClock.sol │ └── config │ │ ├── CourtConfigData.sol │ │ ├── ConfigConsumer.sol │ │ └── IConfig.sol ├── arbitration │ ├── IArbitrator.sol │ └── IArbitrable.sol └── registry │ └── IJurorsRegistry.sol ├── .gitattributes ├── .soliumignore ├── docs ├── aragon-court.png ├── 2-architecture │ ├── architecture.png │ └── readme.md ├── 5-data-structures │ ├── 7-treasury.md │ ├── 1-aragon-court.md │ ├── readme.md │ ├── 5-voting.md │ ├── 4-jurors-registry.md │ ├── 6-subscriptions.md │ ├── 3-dispute-manager.md │ └── 2-controller.md ├── 8-testing-guide │ ├── images │ │ ├── faucet.png │ │ └── ipfs-output.png │ └── sample-dispute │ │ ├── evidence │ │ ├── 2.md │ │ └── 1.md │ │ ├── agreement.md │ │ └── metadata.json ├── 4-entry-points │ ├── readme.md │ ├── 5-voting.md │ ├── 7-treasury.md │ └── 1-aragon-court.md ├── 6-external-interface │ ├── readme.md │ ├── 1-aragon-court.md │ ├── 7-treasury.md │ └── 5-voting.md ├── 7-additional-documentation │ └── readme.md ├── readme.md └── 1-mechanism │ └── readme.md ├── migrations └── 1_initial_migration.js ├── test ├── helpers │ ├── asserts │ │ ├── assertBn.js │ │ ├── assertEvent.js │ │ └── assertThrow.js │ ├── lib │ │ ├── time.js │ │ ├── numbers.js │ │ ├── blocks.js │ │ ├── logging.js │ │ └── decodeEvent.js │ └── utils │ │ ├── crvoting.js │ │ ├── jurors.js │ │ ├── events.js │ │ ├── config.js │ │ └── registry.js ├── arbitration │ └── arbitrable.js ├── voting │ ├── crvoting-initialization.js │ └── crvoting-create.js ├── disputes │ └── disputes-skip.js ├── court │ └── controller │ │ ├── controlled.js │ │ └── controlled-recoverable.js ├── registry │ ├── jurors-registry-initialization.js │ └── jurors-registry-setters.js ├── subscriptions │ ├── subscriptions-donations.js │ └── subscriptions-initialization.js └── lib │ └── checkpointing.js ├── truffle-config.js ├── .solcover.js ├── .gitignore ├── .eslintrc ├── .soliumrc.json ├── .travis.yml ├── package.json ├── readme.md └── scripts └── tree-profile-gas.js /contracts/.npmignore: -------------------------------------------------------------------------------- 1 | /test 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/test 3 | -------------------------------------------------------------------------------- /docs/aragon-court.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aragon/aragon-court/HEAD/docs/aragon-court.png -------------------------------------------------------------------------------- /docs/2-architecture/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aragon/aragon-court/HEAD/docs/2-architecture/architecture.png -------------------------------------------------------------------------------- /docs/5-data-structures/7-treasury.md: -------------------------------------------------------------------------------- 1 | ## 5.7. Treasury 2 | 3 | The `Treasury` module does not rely on any custom data structure. 4 | -------------------------------------------------------------------------------- /docs/8-testing-guide/images/faucet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aragon/aragon-court/HEAD/docs/8-testing-guide/images/faucet.png -------------------------------------------------------------------------------- /docs/8-testing-guide/images/ipfs-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aragon/aragon-court/HEAD/docs/8-testing-guide/images/ipfs-output.png -------------------------------------------------------------------------------- /docs/8-testing-guide/sample-dispute/evidence/2.md: -------------------------------------------------------------------------------- 1 | # Sample evidence #2 2 | 3 | This could be one piece of evidence submitted by the other party 4 | -------------------------------------------------------------------------------- /docs/8-testing-guide/sample-dispute/evidence/1.md: -------------------------------------------------------------------------------- 1 | # Sample evidence #1 2 | 3 | This could be one piece of evidence submitted by one of the parties 4 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require('./Migrations.sol') 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations) 5 | } 6 | -------------------------------------------------------------------------------- /docs/8-testing-guide/sample-dispute/agreement.md: -------------------------------------------------------------------------------- 1 | # Sample dispute agreement 2 | 3 | This document must be the agreement that is being disputed between the parties involved, the defendant and the plaintiff. 4 | -------------------------------------------------------------------------------- /contracts/test/TestImports.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../lib/os/Migrations.sol"; 4 | 5 | 6 | contract TestImports { 7 | constructor() public { 8 | // solium-disable-previous-line no-empty-blocks 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/5-data-structures/1-aragon-court.md: -------------------------------------------------------------------------------- 1 | ## 5.1. AragonCourt 2 | 3 | `AragonCourt` does not rely on any custom data structure, but it relies on the data structures defined by the `Controller`. These can be explored in the [next section](./2-controller.md). 4 | -------------------------------------------------------------------------------- /test/helpers/asserts/assertBn.js: -------------------------------------------------------------------------------- 1 | const assertBn = (actual, expected, errorMsg) => { 2 | assert.equal(actual.toString(), expected.toString(), `${errorMsg} expected ${expected.toString()} to equal ${actual.toString()}`) 3 | } 4 | 5 | module.exports = { 6 | assertBn 7 | } 8 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | const TruffleConfig = require('@aragon/truffle-config-v5/truffle-config') 2 | 3 | TruffleConfig.compilers.solc.version = '0.5.8' 4 | TruffleConfig.compilers.solc.settings.optimizer.runs = 3000 // DisputesManager module is hitting size limit with 10k runs 5 | 6 | module.exports = TruffleConfig 7 | -------------------------------------------------------------------------------- /docs/8-testing-guide/sample-dispute/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Sample dispute for Aragon Court testing guide", 3 | "agreementTitle": "Agreement", 4 | "agreementText": "./agreement.md", 5 | "plaintiff": "0x59d0b5475AcF30F24EcA10cb66BB5Ed75d3d9016", 6 | "defendant": "0x61F73dFc8561C322171c774E5BE0D9Ae21b2da42" 7 | } 8 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | const skipFiles = [ 2 | 'lib', 3 | 'test', 4 | 'standards' 5 | ] 6 | 7 | module.exports = { 8 | norpc: true, 9 | compileCommand: '../node_modules/.bin/truffle compile', 10 | testCommand: 'node --max-old-space-size=4096 ../node_modules/.bin/truffle test --network coverage', 11 | skipFiles, 12 | } 13 | -------------------------------------------------------------------------------- /contracts/lib/BytesHelpers.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | 4 | library BytesHelpers { 5 | function toBytes4(bytes memory _self) internal pure returns (bytes4 result) { 6 | if (_self.length < 4) { 7 | return bytes4(0); 8 | } 9 | 10 | assembly { result := mload(add(_self, 0x20)) } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/helpers/lib/time.js: -------------------------------------------------------------------------------- 1 | const ONE_DAY = 60 * 60 * 24 2 | const ONE_WEEK = ONE_DAY * 7 3 | const NOW = parseInt(new Date().getTime() / 1000) // EVM timestamps are expressed in seconds 4 | const TOMORROW = NOW + ONE_DAY 5 | const NEXT_WEEK = NOW + ONE_WEEK 6 | 7 | module.exports = { 8 | NOW, 9 | TOMORROW, 10 | NEXT_WEEK, 11 | ONE_DAY, 12 | ONE_WEEK 13 | } 14 | -------------------------------------------------------------------------------- /contracts/test/court/controller/ControlledRecoverableMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../../court/controller/Controller.sol"; 4 | import "../../../court/controller/ControlledRecoverable.sol"; 5 | 6 | 7 | contract ControlledRecoverableMock is ControlledRecoverable { 8 | constructor(Controller _controller) ControlledRecoverable(_controller) public {} 9 | } 10 | -------------------------------------------------------------------------------- /contracts/test/arbitration/FakeArbitrableMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "./ArbitrableMock.sol"; 4 | 5 | 6 | contract FakeArbitrableMock is ArbitrableMock { 7 | constructor (IArbitrator _court) ArbitrableMock(_court) public {} 8 | 9 | function supportsInterface(bytes4 /* _interfaceId */) external pure returns (bool) { 10 | return false; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/test/court/controller/ControlledMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../../court/controller/Controlled.sol"; 4 | 5 | 6 | contract ControlledMock is Controlled { 7 | event OnlyConfigGovernorCalled(); 8 | 9 | constructor(Controller _controller) Controlled(_controller) public {} 10 | 11 | function onlyConfigGovernorFn() external onlyConfigGovernor { 12 | emit OnlyConfigGovernorCalled(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules/ 5 | package-lock.json 6 | 7 | # Production 8 | build/ 9 | abi/ 10 | 11 | # coverage 12 | coverage/ 13 | coverageEnv/ 14 | coverage.json 15 | allFiredEvents 16 | scTopics 17 | 18 | # Logs 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # Editors 23 | .vscode/ 24 | .idea/* 25 | 26 | # Misc 27 | .DS_Store 28 | 29 | bytecode 30 | -------------------------------------------------------------------------------- /contracts/standards/ERC165.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | 4 | interface ERC165 { 5 | /** 6 | * @dev Query if a contract implements a certain interface 7 | * @param _interfaceId The interface identifier being queried, as specified in ERC-165 8 | * @return True if the contract implements the requested interface and if its not 0xffffffff, false otherwise 9 | */ 10 | function supportsInterface(bytes4 _interfaceId) external pure returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /test/helpers/lib/numbers.js: -------------------------------------------------------------------------------- 1 | const { BN } = require('web3-utils') 2 | 3 | const bn = x => new BN(x) 4 | const bigExp = (x, y) => bn(x).mul(bn(10).pow(bn(y))) 5 | const maxUint = (e) => bn(2).pow(bn(e)).sub(bn(1)) 6 | 7 | const ONE = bigExp(1, 18) 8 | const MAX_UINT64 = maxUint(64) 9 | const MAX_UINT192 = maxUint(192) 10 | const MAX_UINT256 = maxUint(256) 11 | 12 | module.exports = { 13 | bn, 14 | bigExp, 15 | ONE, 16 | MAX_UINT64, 17 | MAX_UINT192, 18 | MAX_UINT256 19 | } 20 | -------------------------------------------------------------------------------- /docs/5-data-structures/readme.md: -------------------------------------------------------------------------------- 1 | # 5. Data structures 2 | 3 | The following sections aim to describe the different data structures used by each of the modules described in [section 4](../4-entry-points). 4 | 5 | ## Table of Contents 6 | 7 | 1. [AragonCourt](./1-aragon-court.md) 8 | 2. [Controller](./2-controller.md) 9 | 3. [Dispute Manager](./3-dispute-manager.md) 10 | 4. [Jurors Registry](./4-jurors-registry.md) 11 | 5. [Voting](./5-voting.md) 12 | 6. [Subscriptions](./6-subscriptions.md) 13 | 7. [Treasury](./7-treasury.md) 14 | -------------------------------------------------------------------------------- /docs/4-entry-points/readme.md: -------------------------------------------------------------------------------- 1 | # 4. Entry points 2 | 3 | The following sections aim to deeply describe the functionality exposed by each of the components of the Court protocol mentioned in [section 2](../2-architecture). 4 | 5 | ## Table of Contents 6 | 7 | 1. [AragonCourt](./1-aragon-court.md) 8 | 2. [Controller](./2-controller.md) 9 | 3. [Dispute Manager](./3-dispute-manager.md) 10 | 4. [Jurors Registry](./4-jurors-registry.md) 11 | 5. [Voting](./5-voting.md) 12 | 6. [Subscriptions](./6-subscriptions.md) 13 | 7. [Treasury](./7-treasury.md) 14 | -------------------------------------------------------------------------------- /test/helpers/lib/blocks.js: -------------------------------------------------------------------------------- 1 | module.exports = web3 => { 2 | const advanceBlock = async () => { 3 | return new Promise((resolve, reject) => web3.currentProvider.send({ 4 | jsonrpc: '2.0', 5 | method: 'evm_mine', 6 | id: new Date().getTime() 7 | }, (error, result) => error ? reject(error) : resolve(result))) 8 | } 9 | 10 | const advanceBlocks = async blocks => { 11 | for (let i = 0; i < blocks; i++) { 12 | await advanceBlock() 13 | } 14 | } 15 | 16 | return { 17 | advanceBlock, 18 | advanceBlocks 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/6-external-interface/readme.md: -------------------------------------------------------------------------------- 1 | # 6. External interface 2 | 3 | The following sections aim to complement [section 4](../4-entry-points)'s description of each module's external entry points with their view-only access points and emitted events. 4 | 5 | ## Table of Contents 6 | 7 | 1. [AragonCourt](./1-aragon-court.md) 8 | 2. [Controller](./2-controller.md) 9 | 3. [Dispute Manager](./3-dispute-manager.md) 10 | 4. [Jurors Registry](./4-jurors-registry.md) 11 | 5. [Voting](./5-voting.md) 12 | 6. [Subscriptions](./6-subscriptions.md) 13 | 7. [Treasury](./7-treasury.md) 14 | -------------------------------------------------------------------------------- /docs/7-additional-documentation/readme.md: -------------------------------------------------------------------------------- 1 | # 7. Additional documentation 2 | 3 | The following documents complement the technical specification: 4 | 5 | - Aragon Network [white paper](https://github.com/aragon/whitepaper) 6 | - Aragon Network [launch phases](https://forum.aragon.org/t/aragon-network-launch-phases-and-target-dates) 7 | - Court valuation model [forum post](https://forum.aragon.org/t/ant-demand-modeling-framework/1389) 8 | - Court v1 initial [forum post](https://forum.aragon.org/t/aragon-court-v1/691) 9 | - Proposal agreements description [blog post](https://blog.aragon.one/proposal-agreements-and-the-aragon-court/) 10 | -------------------------------------------------------------------------------- /docs/5-data-structures/5-voting.md: -------------------------------------------------------------------------------- 1 | ## 5.4. Voting 2 | 3 | The following objects are the data-structures used by the `Voting`: 4 | 5 | ### 5.4.1. Juror 6 | 7 | The juror object includes the following fields: 8 | 9 | - **Winning outcome:** Outcome winner of a vote instance 10 | - **Max allowed outcome:** Highest outcome allowed for the vote instance 11 | - **Cast votes:** List of cast votes indexed by voters addresses 12 | - **Outcomes tally:** Tally for each of the possible outcomes 13 | 14 | ### 5.4.2. Cast vote 15 | 16 | The cast vote object includes the following fields: 17 | 18 | - **Commitment:** Hash of the outcome casted by the voter 19 | - **Outcome:** Outcome submitted by the voter 20 | -------------------------------------------------------------------------------- /contracts/standards/ApproveAndCall.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | 4 | interface ApproveAndCallFallBack { 5 | /** 6 | * @dev This allows users to use their tokens to interact with contracts in one function call instead of two 7 | * @param _from Address of the account transferring the tokens 8 | * @param _amount The amount of tokens approved for in the transfer 9 | * @param _token Address of the token contract calling this function 10 | * @param _data Optional data that can be used to add signalling information in more complex staking applications 11 | */ 12 | function receiveApproval(address _from, uint256 _amount, address _token, bytes calldata _data) external; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/test/lib/CheckpointingMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/Checkpointing.sol"; 4 | 5 | 6 | contract CheckpointingMock { 7 | using Checkpointing for Checkpointing.History; 8 | 9 | Checkpointing.History internal history; 10 | 11 | function add(uint64 _time, uint256 _value) public { 12 | history.add(_time, _value); 13 | } 14 | 15 | function getLast() public view returns (uint256) { 16 | return history.getLast(); 17 | } 18 | 19 | function get(uint64 _time) public view returns (uint256) { 20 | return history.get(_time); 21 | } 22 | 23 | function getRecent(uint64 _time) public view returns (uint256) { 24 | return history.getRecent(_time); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | }, 6 | "globals": { 7 | "artifacts": "readonly", 8 | "assert": "readonly", 9 | "contract": "readonly", 10 | "web3": "readonly" 11 | }, 12 | "extends": ["standard"], 13 | "plugins": ["unused-imports"], 14 | "rules": { 15 | "semi": "error", 16 | "one-var": "off", 17 | "key-spacing": "off", 18 | "no-var": "error", 19 | "no-multi-spaces": "off", 20 | "no-return-assign": "off", 21 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 22 | "unused-imports/no-unused-imports": "error", 23 | "linebreak-style": ["error", "unix"], 24 | "space-before-function-paren": ["error", { "anonymous": "always", "named": "never", "asyncArrow": "always" }], 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/helpers/lib/logging.js: -------------------------------------------------------------------------------- 1 | const printTable = (title, rows) => { 2 | const header = rows[0] 3 | const columnsMaxLengths = rows.reduce((maxLengths, row) => 4 | row.map((cell, i) => Math.max(cell.length, maxLengths[i])), 5 | header.map(() => 0) 6 | ) 7 | 8 | const formattedHeader = header.map((cell, i) => cell.padEnd(columnsMaxLengths[i], ' ')) 9 | const formattedHeaderDiv = header.map((cell, i) => '-'.padEnd(columnsMaxLengths[i], '-')) 10 | const formattedBody = rows.slice(1).map(row => row.map((cell, i) => cell.padStart(columnsMaxLengths[i], ' '))) 11 | 12 | console.log(`\n${title}\n`) 13 | console.log(`| ${formattedHeader.join(' | ')} |`) 14 | console.log(`|-${formattedHeaderDiv.join('-|-')}-|`) 15 | formattedBody.forEach(row => console.log(`| ${row.join(' | ')} |`)) 16 | } 17 | 18 | module.exports = { 19 | printTable 20 | } 21 | -------------------------------------------------------------------------------- /contracts/treasury/ITreasury.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../lib/os/ERC20.sol"; 4 | 5 | 6 | interface ITreasury { 7 | /** 8 | * @dev Assign a certain amount of tokens to an account 9 | * @param _token ERC20 token to be assigned 10 | * @param _to Address of the recipient that will be assigned the tokens to 11 | * @param _amount Amount of tokens to be assigned to the recipient 12 | */ 13 | function assign(ERC20 _token, address _to, uint256 _amount) external; 14 | 15 | /** 16 | * @dev Withdraw a certain amount of tokens 17 | * @param _token ERC20 token to be withdrawn 18 | * @param _to Address of the recipient that will receive the tokens 19 | * @param _amount Amount of tokens to be withdrawn from the sender 20 | */ 21 | function withdraw(ERC20 _token, address _to, uint256 _amount) external; 22 | } 23 | -------------------------------------------------------------------------------- /contracts/lib/os/Migrations.sol: -------------------------------------------------------------------------------- 1 | // Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/lib/misc/Migrations.sol 2 | // Adapted to use pragma ^0.5.8 and satisfy our linter rules 3 | 4 | pragma solidity ^0.5.8; 5 | 6 | 7 | contract Migrations { 8 | address public owner; 9 | uint256 public lastCompletedMigration; 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) { 13 | _; 14 | } 15 | } 16 | 17 | constructor() public { 18 | owner = msg.sender; 19 | } 20 | 21 | function setCompleted(uint256 completed) public restricted { 22 | lastCompletedMigration = completed; 23 | } 24 | 25 | function upgrade(address newAddress) public restricted { 26 | Migrations upgraded = Migrations(newAddress); 27 | upgraded.setCompleted(lastCompletedMigration); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/helpers/lib/decodeEvent.js: -------------------------------------------------------------------------------- 1 | const abi = require('web3-eth-abi') 2 | const { isAddress } = require('web3-utils') 3 | 4 | function decodeEventsOfType({ receipt }, contractAbi, eventName) { 5 | const eventAbi = contractAbi.filter(abi => abi.name === eventName && abi.type === 'event')[0] 6 | const eventSignature = abi.encodeEventSignature(eventAbi) 7 | const eventLogs = receipt.rawLogs.filter(l => l.topics[0] === eventSignature) 8 | return eventLogs.map(log => { 9 | log.event = eventAbi.name 10 | log.args = abi.decodeLog(eventAbi.inputs, log.data, log.topics.slice(1)) 11 | 12 | // undo checksumed addresses 13 | Object.keys(log.args).forEach(arg => { 14 | const value = log.args[arg] 15 | if (isAddress(value)) log.args[arg] = value.toLowerCase() 16 | }) 17 | 18 | return log 19 | }) 20 | } 21 | 22 | module.exports = { 23 | decodeEventsOfType 24 | } 25 | -------------------------------------------------------------------------------- /contracts/lib/os/IsContract.sol: -------------------------------------------------------------------------------- 1 | // Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/common/IsContract.sol 2 | // Adapted to use pragma ^0.5.8 and satisfy our linter rules 3 | 4 | pragma solidity ^0.5.8; 5 | 6 | 7 | contract IsContract { 8 | /* 9 | * NOTE: this should NEVER be used for authentication 10 | * (see pitfalls: https://github.com/fergarrui/ethereum-security/tree/master/contracts/extcodesize). 11 | * 12 | * This is only intended to be used as a sanity check that an address is actually a contract, 13 | * RATHER THAN an address not being a contract. 14 | */ 15 | function isContract(address _target) internal view returns (bool) { 16 | if (_target == address(0)) { 17 | return false; 18 | } 19 | 20 | uint256 size; 21 | assembly { size := extcodesize(_target) } 22 | return size > 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/arbitration/arbitrable.js: -------------------------------------------------------------------------------- 1 | const Arbitrable = artifacts.require('ArbitrableMock') 2 | 3 | contract('Arbitrable', ([_, court]) => { 4 | let arbitrable 5 | 6 | beforeEach('create arbitrable instance', async () => { 7 | arbitrable = await Arbitrable.new(court) 8 | }) 9 | 10 | it('supports ERC165', async () => { 11 | assert.isTrue(await arbitrable.supportsInterface('0x01ffc9a7'), 'does not support ERC165') 12 | }) 13 | 14 | it('supports IArbitrable', async () => { 15 | assert.equal(await arbitrable.interfaceId(), '0x88f3ee69', 'IArbitrable interface ID does not match') 16 | assert.isTrue(await arbitrable.supportsInterface('0x88f3ee69'), 'does not support IArbitrable') 17 | }) 18 | 19 | it('supports ERC165', async () => { 20 | assert.equal(await arbitrable.interfaceID(), await arbitrable.ARBITRABLE_INTERFACE(), 'IArbitrable interface ID does not match') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /contracts/lib/os/Uint256Helpers.sol: -------------------------------------------------------------------------------- 1 | // Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/common/Uint256Helpers.sol 2 | // Adapted to use pragma ^0.5.8 and satisfy our linter rules 3 | 4 | pragma solidity ^0.5.8; 5 | 6 | 7 | library Uint256Helpers { 8 | uint256 private constant MAX_UINT8 = uint8(-1); 9 | uint256 private constant MAX_UINT64 = uint64(-1); 10 | 11 | string private constant ERROR_UINT8_NUMBER_TOO_BIG = "UINT8_NUMBER_TOO_BIG"; 12 | string private constant ERROR_UINT64_NUMBER_TOO_BIG = "UINT64_NUMBER_TOO_BIG"; 13 | 14 | function toUint8(uint256 a) internal pure returns (uint8) { 15 | require(a <= MAX_UINT8, ERROR_UINT8_NUMBER_TOO_BIG); 16 | return uint8(a); 17 | } 18 | 19 | function toUint64(uint256 a) internal pure returns (uint64) { 20 | require(a <= MAX_UINT64, ERROR_UINT64_NUMBER_TOO_BIG); 21 | return uint64(a); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/lib/PctHelpers.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../lib/os/SafeMath.sol"; 4 | 5 | 6 | library PctHelpers { 7 | using SafeMath for uint256; 8 | 9 | uint256 internal constant PCT_BASE = 10000; // ‱ (1 / 10,000) 10 | 11 | function isValid(uint16 _pct) internal pure returns (bool) { 12 | return _pct <= PCT_BASE; 13 | } 14 | 15 | function pct(uint256 self, uint16 _pct) internal pure returns (uint256) { 16 | return self.mul(uint256(_pct)) / PCT_BASE; 17 | } 18 | 19 | function pct256(uint256 self, uint256 _pct) internal pure returns (uint256) { 20 | return self.mul(_pct) / PCT_BASE; 21 | } 22 | 23 | function pctIncrease(uint256 self, uint16 _pct) internal pure returns (uint256) { 24 | // No need for SafeMath: for addition note that `PCT_BASE` is lower than (2^256 - 2^16) 25 | return self.mul(PCT_BASE + uint256(_pct)) / PCT_BASE; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/helpers/utils/crvoting.js: -------------------------------------------------------------------------------- 1 | const { bn } = require('../lib/numbers') 2 | const { soliditySha3 } = require('web3-utils') 3 | 4 | const OUTCOMES = { 5 | MISSING: bn(0), 6 | LEAKED: bn(1), 7 | REFUSED: bn(2), 8 | LOW: bn(3), 9 | HIGH: bn(4) 10 | } 11 | 12 | const SALT = soliditySha3('passw0rd') 13 | 14 | const hashVote = (outcome, salt = SALT) => { 15 | return soliditySha3({ t: 'uint8', v: outcome }, { t: 'bytes32', v: salt }) 16 | } 17 | 18 | const getVoteId = (disputeId, roundId) => { 19 | return bn(2).pow(bn(128)).mul(bn(disputeId)).add(bn(roundId)) 20 | } 21 | 22 | const outcomeFor = (n) => { 23 | return n % 2 === 0 ? OUTCOMES.LOW : OUTCOMES.HIGH 24 | } 25 | 26 | const oppositeOutcome = outcome => { 27 | return outcome.eq(OUTCOMES.LOW) ? OUTCOMES.HIGH : OUTCOMES.LOW 28 | } 29 | 30 | module.exports = { 31 | SALT, 32 | OUTCOMES, 33 | hashVote, 34 | getVoteId, 35 | outcomeFor, 36 | oppositeOutcome 37 | } 38 | -------------------------------------------------------------------------------- /docs/5-data-structures/4-jurors-registry.md: -------------------------------------------------------------------------------- 1 | ## 5.3. Jurors Registry 2 | 3 | The following objects are the data-structures used by the `JurorsRegistry`: 4 | 5 | ### 5.3.1. Juror 6 | 7 | The juror object includes the following fields: 8 | 9 | - **ID:** Identification number of each juror 10 | - **Locked balance:** Maximum amount of tokens that can be slashed based on the juror's drafts 11 | - **Active balance:** Tokens activated for the Court that can be locked in case the juror is drafted 12 | - **Available balance:** Available tokens that can be withdrawn at any time 13 | - **Withdrawals lock term ID:** Term identification number until which juror's withdrawals will be locked 14 | - **Deactivation request:** Pending deactivation request of a juror 15 | 16 | ### 5.3.2. Deactivation request 17 | 18 | The deactivation request object includes the following fields: 19 | 20 | - **Amount:** Amount requested for deactivation 21 | - **Available termId:** ID of the term when jurors can withdraw their requested deactivation tokens 22 | -------------------------------------------------------------------------------- /docs/6-external-interface/1-aragon-court.md: -------------------------------------------------------------------------------- 1 | ## 6.1. AragonCourt 2 | 3 | ### 6.1.1 Events 4 | 5 | No custom events are implemented by `AragonCourt`. 6 | 7 | ### 6.1.2. Getters 8 | 9 | The following functions are state getters provided by `AragonCourt`: 10 | 11 | #### 6.1.2.1. Dispute fees 12 | 13 | - **Inputs:** None 14 | - **Pre-flight checks:** None 15 | - **Outputs:** 16 | **Recipient:** Address where the corresponding dispute fees must be transferred to 17 | **Fee token:** ERC20 token used for the fees 18 | **Fee amount:** Total amount of fees that must be allowed to the recipient 19 | 20 | #### 6.1.2.2. Subscription fees 21 | 22 | - **Inputs:** 23 | **Subscriber:** Address of the account paying the subscription fees for 24 | - **Pre-flight checks:** None 25 | - **Outputs:** 26 | **Recipient:** Address where the corresponding subscriptions fees must be transferred to 27 | **Fee token:** ERC20 token used for the subscription fees 28 | **Fee amount:** Total amount of fees that must be allowed to the recipient 29 | -------------------------------------------------------------------------------- /contracts/subscriptions/ISubscriptions.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../lib/os/ERC20.sol"; 4 | 5 | 6 | interface ISubscriptions { 7 | /** 8 | * @dev Tell whether a certain subscriber has paid all the fees up to current period or not 9 | * @param _subscriber Address of subscriber being checked 10 | * @return True if subscriber has paid all the fees up to current period, false otherwise 11 | */ 12 | function isUpToDate(address _subscriber) external view returns (bool); 13 | 14 | /** 15 | * @dev Tell the minimum amount of fees to pay and resulting last paid period for a given subscriber in order to be up-to-date 16 | * @param _subscriber Address of the subscriber willing to pay 17 | * @return feeToken ERC20 token used for the subscription fees 18 | * @return amountToPay Amount of subscription fee tokens to be paid 19 | * @return newLastPeriodId Identification number of the resulting last paid period 20 | */ 21 | function getOwedFeesDetails(address _subscriber) external view returns (ERC20, uint256, uint256); 22 | } 23 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Aragon Court v1 - Technical spec 2 | 3 | The following documents attempt to be a high-level description of the inner implementation details of Aragon Court v1. It doesn't go deep to the point of being an exhaustive spec detailing all authentication checks and state transitions, but aims to provide enough description for a developer to deeply understand the existing codebase or guide a re-implementation. 4 | 5 | This document was written to ease the job of security auditors looking at the codebase and was written after the v1 implementation had been frozen. 6 | 7 | The core of the document is organized around the external entry points to the system across the different modules. 8 | 9 | ## Table of Contents 10 | 11 | 1. [Mechanism](./1-mechanism) 12 | 2. [Architecture](./2-architecture) 13 | 3. [Crypto-economic considerations](./3-cryptoeconomic-considerations) 14 | 4. [Entry points](./4-entry-points) 15 | 5. [Data structures](./5-data-structures) 16 | 6. [External interface](./6-external-interface) 17 | 7. [Additional documentation](./7-additional-documentation) 18 | 8. [Testing guide](./8-testing-guide) 19 | -------------------------------------------------------------------------------- /contracts/lib/os/ERC20.sol: -------------------------------------------------------------------------------- 1 | // Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/lib/token/ERC20.sol 2 | // Adapted to use pragma ^0.5.8 and satisfy our linter rules 3 | 4 | pragma solidity ^0.5.8; 5 | 6 | 7 | /** 8 | * @title ERC20 interface 9 | * @dev see https://github.com/ethereum/EIPs/issues/20 10 | */ 11 | contract ERC20 { 12 | function totalSupply() public view returns (uint256); 13 | 14 | function balanceOf(address _who) public view returns (uint256); 15 | 16 | function allowance(address _owner, address _spender) public view returns (uint256); 17 | 18 | function transfer(address _to, uint256 _value) public returns (bool); 19 | 20 | function approve(address _spender, uint256 _value) public returns (bool); 21 | 22 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool); 23 | 24 | event Transfer( 25 | address indexed from, 26 | address indexed to, 27 | uint256 value 28 | ); 29 | 30 | event Approval( 31 | address indexed owner, 32 | address indexed spender, 33 | uint256 value 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/voting/ICRVotingOwner.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | 4 | interface ICRVotingOwner { 5 | /** 6 | * @dev Ensure votes can be committed for a vote instance, revert otherwise 7 | * @param _voteId ID of the vote instance to request the weight of a voter for 8 | */ 9 | function ensureCanCommit(uint256 _voteId) external; 10 | 11 | /** 12 | * @dev Ensure a certain voter can commit votes for a vote instance, revert otherwise 13 | * @param _voteId ID of the vote instance to request the weight of a voter for 14 | * @param _voter Address of the voter querying the weight of 15 | */ 16 | function ensureCanCommit(uint256 _voteId, address _voter) external; 17 | 18 | /** 19 | * @dev Ensure a certain voter can reveal votes for vote instance, revert otherwise 20 | * @param _voteId ID of the vote instance to request the weight of a voter for 21 | * @param _voter Address of the voter querying the weight of 22 | * @return Weight of the requested juror for the requested vote instance 23 | */ 24 | function ensureCanReveal(uint256 _voteId, address _voter) external returns (uint64); 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/subscriptions/SubscriptionsMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../subscriptions/ISubscriptions.sol"; 4 | import "../../subscriptions/CourtSubscriptions.sol"; 5 | 6 | 7 | contract SubscriptionsMock is CourtSubscriptions { 8 | bool internal upToDate; 9 | 10 | constructor( 11 | Controller _controller, 12 | uint64 _periodDuration, 13 | ERC20 _feeToken, 14 | uint256 _feeAmount, 15 | uint256 _prePaymentPeriods, 16 | uint256 _resumePrePaidPeriods, 17 | uint16 _latePaymentPenaltyPct, 18 | uint16 _governorSharePct 19 | ) 20 | CourtSubscriptions( 21 | _controller, 22 | _periodDuration, 23 | _feeToken, 24 | _feeAmount, 25 | _prePaymentPeriods, 26 | _resumePrePaidPeriods, 27 | _latePaymentPenaltyPct, 28 | _governorSharePct 29 | ) 30 | public 31 | {} 32 | 33 | function mockUpToDate(bool _upToDate) external { 34 | upToDate = _upToDate; 35 | } 36 | 37 | function isUpToDate(address) external view returns (bool) { 38 | return upToDate; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/5-data-structures/6-subscriptions.md: -------------------------------------------------------------------------------- 1 | ## 5.5. Subscriptions 2 | 3 | The following objects are the data-structures used by the `Subscriptions`: 4 | 5 | ### 5.5.1. Period 6 | 7 | The period object includes the following fields: 8 | 9 | - **Fee token:** Fee token corresponding to a certain subscription period 10 | - **Fee amount:** Amount of fees paid for a certain subscription period 11 | - **Claimed fees:** List of jurors that have claimed fees during a period, indexed by juror address 12 | - **Collected fees:** Total amount of subscription fees collected during a period 13 | - **Balance checkpoint:** Term identification number of a period used to fetch the total active balance of the jurors registry 14 | - **Total active balance:** Total amount of juror tokens active in the Court at the corresponding period checkpoint 15 | 16 | ### 5.5.2. Subscriber 17 | 18 | The subscriber object includes the following fields: 19 | 20 | - **Subscribed:** Whether or not a user has been subscribed to the Court 21 | - **Paused:** Whether or not a user has paused the Court subscriptions 22 | - **Last payment period ID:** Identification number of the last period paid by a subscriber 23 | - **Previous delayed periods:** Number of delayed periods before pausing 24 | 25 | -------------------------------------------------------------------------------- /docs/6-external-interface/7-treasury.md: -------------------------------------------------------------------------------- 1 | ## 6.7. Treasury 2 | 3 | ### 6.7.1 Events 4 | 5 | The following events are emitted by the `Treasury`: 6 | 7 | #### 6.7.1.1. Assign 8 | 9 | - **Name:** `Assign` 10 | - **Args:** 11 | - **Token:** Address of the ERC20 token assigned 12 | - **From:** Address of the account that has deposited the tokens 13 | - **To:** Address of the account that has received the tokens 14 | - **Amount:** Number of tokens assigned to the recipient account 15 | 16 | #### 6.7.1.2. Withdraw 17 | 18 | - **Name:** `Withdraw` 19 | - **Args:** 20 | - **Token:** Address of the ERC20 token withdrawn 21 | - **From:** Address of the account that has withdrawn the tokens 22 | - **To:** Address of the account that has received the tokens 23 | - **Amount:** Number of tokens withdrawn to the recipient account 24 | 25 | ### 6.7.2. Getters 26 | 27 | The following functions are state getters provided by the `Treasury`: 28 | 29 | #### 6.7.2.1. Balance of 30 | 31 | - **Inputs:** 32 | - **Token:** Address of the ERC20 token querying a holder's the balance of 33 | - **Holder:** Address of account querying the balance of 34 | - **Pre-flight checks:** None 35 | - **Outputs:** 36 | - **Balance:** Amount of tokens the holder owns 37 | -------------------------------------------------------------------------------- /test/helpers/utils/jurors.js: -------------------------------------------------------------------------------- 1 | const { sha3, toChecksumAddress } = require('web3-utils') 2 | 3 | const ACTIVATE_DATA = sha3('activate(uint256)').slice(0, 10) 4 | 5 | const filterJurors = (jurorsList, jurorsToFiler) => { 6 | const addressesToFiler = jurorsToFiler.map(j => toChecksumAddress(j.address)) 7 | return jurorsList.filter(juror => !addressesToFiler.includes(toChecksumAddress(juror.address))) 8 | } 9 | 10 | const filterWinningJurors = (votersList, winningRuling) => { 11 | const winners = votersList.filter(({ outcome }) => outcome === winningRuling) 12 | const losers = filterJurors(votersList, winners) 13 | return [winners, losers] 14 | } 15 | 16 | const countJuror = (list, jurorAddress) => { 17 | const equalJurors = list.filter(address => address === jurorAddress) 18 | return equalJurors.length 19 | } 20 | 21 | const countEqualJurors = addresses => { 22 | return addresses.reduce((totals, address) => { 23 | const index = totals.map(juror => juror.address).indexOf(address) 24 | if (index >= 0) totals[index].count++ 25 | else totals.push({ address, count: 1 }) 26 | return totals 27 | }, []) 28 | } 29 | 30 | module.exports = { 31 | ACTIVATE_DATA, 32 | countJuror, 33 | countEqualJurors, 34 | filterJurors, 35 | filterWinningJurors 36 | } 37 | -------------------------------------------------------------------------------- /test/helpers/asserts/assertEvent.js: -------------------------------------------------------------------------------- 1 | const { getEventAt, getEvents } = require('@aragon/test-helpers/events') 2 | const { isAddress, isBN, toChecksumAddress } = require('web3-utils') 3 | 4 | const assertEvent = (receipt, eventName, expectedArgs = {}, index = 0) => { 5 | const event = getEventAt(receipt, eventName, index) 6 | 7 | assert(typeof event === 'object', `could not find an emitted ${eventName} event ${index === 0 ? '' : `at index ${index}`}`) 8 | 9 | for (const arg of Object.keys(expectedArgs)) { 10 | let foundArg = event.args[arg] 11 | if (isBN(foundArg)) foundArg = foundArg.toString() 12 | if (isAddress(foundArg)) foundArg = toChecksumAddress(foundArg) 13 | 14 | let expectedArg = expectedArgs[arg] 15 | if (isBN(expectedArg)) expectedArg = expectedArg.toString() 16 | if (isAddress(foundArg)) expectedArg = toChecksumAddress(expectedArg) 17 | 18 | assert.equal(foundArg, expectedArg, `${eventName} event ${arg} value does not match`) 19 | } 20 | } 21 | 22 | const assertAmountOfEvents = (receipt, eventName, expectedAmount = 1) => { 23 | const events = getEvents(receipt, eventName) 24 | assert.equal(events.length, expectedAmount, `number of ${eventName} events does not match`) 25 | } 26 | 27 | module.exports = { 28 | assertEvent, 29 | assertAmountOfEvents 30 | } 31 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:all", 3 | "plugins": ["security"], 4 | "rules": { 5 | "security/no-low-level-calls": "off", 6 | "security/no-inline-assembly": "off", 7 | "imports-on-top": "error", 8 | "variable-declarations": "error", 9 | "array-declarations": "error", 10 | "operator-whitespace": "error", 11 | "conditionals-whitespace": "error", 12 | "comma-whitespace": "error", 13 | "semicolon-whitespace": "error", 14 | "function-whitespace": "error", 15 | "lbrace": "error", 16 | "mixedcase": "error", 17 | "camelcase": "error", 18 | "uppercase": "error", 19 | "no-empty-blocks": "error", 20 | "no-unused-vars": "error", 21 | "quotes": ["error", "double"], 22 | "blank-lines": "error", 23 | "indentation": ["error", 4], 24 | "arg-overflow": ["error", 8], 25 | "whitespace": "error", 26 | "deprecated-suicide": "error", 27 | "pragma-on-top": "error", 28 | "function-order": "error", 29 | "emit": "error", 30 | "no-constant": "error", 31 | "value-in-payable": "error", 32 | "no-experimental": "off", 33 | "max-len": ["error", 145], 34 | "error-reason": "error", 35 | "visibility-first": "error", 36 | "linebreak-style": "error", 37 | "constructor": "error", 38 | "no-trailing-whitespace": "error" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | notifications: 6 | email: false 7 | node_js: 8 | - '10' 9 | before_script: 10 | - npm prune 11 | jobs: 12 | include: 13 | - stage: tests 14 | script: npm run lint 15 | name: "Lint" 16 | - stage: tests 17 | script: npm test test/court/**/*.js test/court/*.js test/arbitration/*.js 18 | name: "Court Controller" 19 | - stage: tests 20 | script: npm test test/disputes/disputes-{create,evidence,draft}.js 21 | name: "Court Disputes" 22 | - stage: tests 23 | script: npm test test/disputes/disputes-{voting,appeal,confirm-appeal}.js 24 | name: "Court Appeals" 25 | - stage: tests 26 | script: npm test test/disputes/disputes-settle-round.js 27 | name: "Court Settle Rounds" 28 | - stage: tests 29 | script: npm test test/disputes/disputes-settle-appeal.js 30 | name: "Court Settle Appeals" 31 | - stage: tests 32 | script: npm test test/registry/*.js 33 | name: "Registry" 34 | - stage: tests 35 | script: npm test test/lib/*.js 36 | name: "Lib" 37 | - stage: tests 38 | script: npm test test/voting/*.js 39 | name: "Voting" 40 | - stage: tests 41 | script: npm test test/subscriptions/*.js 42 | name: "Subscriptions" 43 | - stage: tests 44 | script: npm test test/treasury/court-treasury.js 45 | name: "Treasury" 46 | -------------------------------------------------------------------------------- /test/voting/crvoting-initialization.js: -------------------------------------------------------------------------------- 1 | const { buildHelper } = require('../helpers/wrappers/court')(web3, artifacts) 2 | const { assertRevert } = require('../helpers/asserts/assertThrow') 3 | const { CONTROLLED_ERRORS } = require('../helpers/utils/errors') 4 | 5 | const CRVoting = artifacts.require('CRVoting') 6 | 7 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 8 | 9 | contract('CRVoting', ([_, someone]) => { 10 | let controller 11 | 12 | beforeEach('create base contracts', async () => { 13 | controller = await buildHelper().deploy() 14 | }) 15 | 16 | describe('constructor', () => { 17 | context('when the initialization succeeds', () => { 18 | it('initializes voting correctly', async () => { 19 | const voting = await CRVoting.new(controller.address) 20 | 21 | assert.equal(await voting.getController(), controller.address, 'subscriptions controller does not match') 22 | }) 23 | }) 24 | 25 | context('initialization fails', () => { 26 | context('when the given controller is the zero address', () => { 27 | const controllerAddress = ZERO_ADDRESS 28 | 29 | it('reverts', async () => { 30 | await assertRevert(CRVoting.new(controllerAddress), CONTROLLED_ERRORS.CONTROLLER_NOT_CONTRACT) 31 | }) 32 | }) 33 | 34 | context('when the given controller is not a contract address', () => { 35 | const controllerAddress = someone 36 | 37 | it('reverts', async () => { 38 | await assertRevert(CRVoting.new(controllerAddress), CONTROLLED_ERRORS.CONTROLLER_NOT_CONTRACT) 39 | }) 40 | }) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/helpers/asserts/assertThrow.js: -------------------------------------------------------------------------------- 1 | const REVERT_CODE = 'revert' 2 | const THROW_ERROR_PREFIX = 'Returned error: VM Exception while processing transaction:' 3 | 4 | function assertError(error, expectedErrorCode) { 5 | assert(error.message.search(expectedErrorCode) > -1, `Expected error code "${expectedErrorCode}" but failed with "${error}" instead.`) 6 | } 7 | 8 | async function assertThrows(blockOrPromise, expectedErrorCode, expectedReason) { 9 | try { 10 | (typeof blockOrPromise === 'function') ? await blockOrPromise() : await blockOrPromise 11 | } catch (error) { 12 | assertError(error, expectedErrorCode) 13 | return error 14 | } 15 | // assert.fail() for some reason does not have its error string printed 🤷 16 | assert(0, `Expected "${expectedErrorCode}"${expectedReason ? ` (with reason: "${expectedReason}")` : ''} but it did not fail`) 17 | } 18 | 19 | async function assertRevert(blockOrPromise, reason) { 20 | const error = await assertThrows(blockOrPromise, REVERT_CODE, reason) 21 | const errorPrefix = `${THROW_ERROR_PREFIX} ${REVERT_CODE}` 22 | 23 | if (error.message.includes(errorPrefix)) { 24 | error.reason = error.message.replace(errorPrefix, '') 25 | // Truffle 5 sometimes add an extra ' -- Reason given: reason.' to the error message 🤷 26 | error.reason = error.reason.replace(` -- Reason given: ${reason}.`, '').trim() 27 | } 28 | 29 | if (process.env.SOLIDITY_COVERAGE !== 'true' && reason) { 30 | assert.equal(error.reason, reason, `Expected revert reason "${reason}" but failed with "${error.reason || 'no reason'}" instead.`) 31 | } 32 | } 33 | 34 | module.exports = { 35 | assertRevert 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aragon/court", 3 | "version": "1.1.3", 4 | "description": "Aragon Court", 5 | "author": "Aragon Association", 6 | "license": "GPL-3.0", 7 | "files": [ 8 | "/abi", 9 | "/build", 10 | "/contracts" 11 | ], 12 | "scripts": { 13 | "compile": "truffle compile", 14 | "lint": "npm run lint:solidity && npm run lint:js", 15 | "lint:js": "eslint ./test ./scripts ./migrations", 16 | "lint:solidity": "solium --dir ./contracts", 17 | "test": "npm run ganache-cli:test", 18 | "test:gas": "GAS_REPORTER=true npm test", 19 | "coverage": "SOLIDITY_COVERAGE=true npm run ganache-cli:test", 20 | "ganache-cli:test": "./node_modules/@aragon/test-helpers/ganache-cli.sh", 21 | "abi:extract": "truffle-extract --output abi/ --keys abi", 22 | "prepublishOnly": "truffle compile --all && npm run abi:extract -- --no-compile" 23 | }, 24 | "pre-push": [ 25 | "lint" 26 | ], 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "@aragon/test-helpers": "^2.1.0", 30 | "@aragon/truffle-config-v5": "^1.0.0", 31 | "eslint": "^5.6.0", 32 | "eslint-config-standard": "^12.0.0", 33 | "eslint-plugin-import": "^2.8.0", 34 | "eslint-plugin-node": "^7.0.1", 35 | "eslint-plugin-promise": "^4.0.1", 36 | "eslint-plugin-standard": "^4.0.0", 37 | "eslint-plugin-unused-imports": "^0.1.0", 38 | "eth-gas-reporter": "^0.2.9", 39 | "ganache-cli": "^6.9.0", 40 | "pre-push": "^0.1.1", 41 | "solidity-coverage": "0.5.8", 42 | "solium": "^1.2.3", 43 | "truffle": "^5.0.34", 44 | "truffle-extract": "^1.2.1", 45 | "web3": "^1.2.1", 46 | "web3-eth-abi": "1.0.0-beta.33" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/test/arbitration/ArbitrableMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../arbitration/IArbitrable.sol"; 4 | import "../../arbitration/IArbitrator.sol"; 5 | 6 | 7 | contract ArbitrableMock is IArbitrable { 8 | bytes4 public constant ERC165_INTERFACE = ERC165_INTERFACE_ID; 9 | bytes4 public constant ARBITRABLE_INTERFACE = ARBITRABLE_INTERFACE_ID; 10 | 11 | IArbitrator internal arbitrator; 12 | 13 | constructor (IArbitrator _arbitrator) public { 14 | arbitrator = _arbitrator; 15 | } 16 | 17 | function interfaceId() external pure returns (bytes4) { 18 | IArbitrable iArbitrable; 19 | return iArbitrable.submitEvidence.selector ^ iArbitrable.rule.selector; 20 | } 21 | 22 | function createDispute(uint8 _possibleRulings, bytes calldata _metadata) external { 23 | (address recipient, ERC20 feeToken, uint256 disputeFees) = arbitrator.getDisputeFees(); 24 | feeToken.approve(recipient, disputeFees); 25 | arbitrator.createDispute(_possibleRulings, _metadata); 26 | } 27 | 28 | function submitEvidence(uint256 _disputeId, bytes calldata _evidence, bool _finished) external { 29 | emit EvidenceSubmitted(arbitrator, _disputeId, msg.sender, _evidence, _finished); 30 | if (_finished) arbitrator.closeEvidencePeriod(_disputeId); 31 | } 32 | 33 | function rule(uint256 _disputeId, uint256 _ruling) external { 34 | emit Ruled(IArbitrator(msg.sender), _disputeId, _ruling); 35 | } 36 | 37 | function interfaceID() external pure returns (bytes4) { 38 | IArbitrable arbitrable; 39 | return arbitrable.submitEvidence.selector ^ arbitrable.rule.selector; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/lib/os/TimeHelpers.sol: -------------------------------------------------------------------------------- 1 | // Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/common/TimeHelpers.sol 2 | // Adapted to use pragma ^0.5.8 and satisfy our linter rules 3 | 4 | pragma solidity ^0.5.8; 5 | 6 | import "./Uint256Helpers.sol"; 7 | 8 | 9 | contract TimeHelpers { 10 | using Uint256Helpers for uint256; 11 | 12 | /** 13 | * @dev Returns the current block number. 14 | * Using a function rather than `block.number` allows us to easily mock the block number in 15 | * tests. 16 | */ 17 | function getBlockNumber() internal view returns (uint256) { 18 | return block.number; 19 | } 20 | 21 | /** 22 | * @dev Returns the current block number, converted to uint64. 23 | * Using a function rather than `block.number` allows us to easily mock the block number in 24 | * tests. 25 | */ 26 | function getBlockNumber64() internal view returns (uint64) { 27 | return getBlockNumber().toUint64(); 28 | } 29 | 30 | /** 31 | * @dev Returns the current timestamp. 32 | * Using a function rather than `block.timestamp` allows us to easily mock it in 33 | * tests. 34 | */ 35 | function getTimestamp() internal view returns (uint256) { 36 | return block.timestamp; // solium-disable-line security/no-block-members 37 | } 38 | 39 | /** 40 | * @dev Returns the current timestamp, converted to uint64. 41 | * Using a function rather than `block.timestamp` allows us to easily mock it in 42 | * tests. 43 | */ 44 | function getTimestamp64() internal view returns (uint64) { 45 | return getTimestamp().toUint64(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/court/controller/ControlledRecoverable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/os/ERC20.sol"; 4 | import "../../lib/os/SafeERC20.sol"; 5 | 6 | import "./Controlled.sol"; 7 | 8 | 9 | contract ControlledRecoverable is Controlled { 10 | using SafeERC20 for ERC20; 11 | 12 | string private constant ERROR_SENDER_NOT_FUNDS_GOVERNOR = "CTD_SENDER_NOT_FUNDS_GOVERNOR"; 13 | string private constant ERROR_INSUFFICIENT_RECOVER_FUNDS = "CTD_INSUFFICIENT_RECOVER_FUNDS"; 14 | string private constant ERROR_RECOVER_TOKEN_FUNDS_FAILED = "CTD_RECOVER_TOKEN_FUNDS_FAILED"; 15 | 16 | event RecoverFunds(ERC20 token, address recipient, uint256 balance); 17 | 18 | /** 19 | * @dev Ensure the msg.sender is the controller's funds governor 20 | */ 21 | modifier onlyFundsGovernor { 22 | require(msg.sender == controller.getFundsGovernor(), ERROR_SENDER_NOT_FUNDS_GOVERNOR); 23 | _; 24 | } 25 | 26 | /** 27 | * @dev Constructor function 28 | * @param _controller Address of the controller 29 | */ 30 | constructor(Controller _controller) Controlled(_controller) public { 31 | // solium-disable-previous-line no-empty-blocks 32 | } 33 | 34 | /** 35 | * @notice Transfer all `_token` tokens to `_to` 36 | * @param _token ERC20 token to be recovered 37 | * @param _to Address of the recipient that will be receive all the funds of the requested token 38 | */ 39 | function recoverFunds(ERC20 _token, address _to) external onlyFundsGovernor { 40 | uint256 balance = _token.balanceOf(address(this)); 41 | require(balance > 0, ERROR_INSUFFICIENT_RECOVER_FUNDS); 42 | require(_token.safeTransfer(_to, balance), ERROR_RECOVER_TOKEN_FUNDS_FAILED); 43 | emit RecoverFunds(_token, _to, balance); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/test/disputes/DisputeManagerMockForVoting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../voting/ICRVoting.sol"; 4 | import "../../voting/ICRVotingOwner.sol"; 5 | import "../../court/controller/Controller.sol"; 6 | import "../../court/controller/Controlled.sol"; 7 | 8 | 9 | contract DisputeManagerMockForVoting is ICRVotingOwner, Controlled { 10 | string private constant ERROR_VOTER_WEIGHT_ZERO = "DM_VOTER_WEIGHT_ZERO"; 11 | string private constant ERROR_OWNER_MOCK_COMMIT_CHECK_REVERTED = "CRV_OWNER_MOCK_COMMIT_CHECK_REVERTED"; 12 | string private constant ERROR_OWNER_MOCK_REVEAL_CHECK_REVERTED = "CRV_OWNER_MOCK_REVEAL_CHECK_REVERTED"; 13 | 14 | bool internal failing; 15 | mapping (address => uint64) internal weights; 16 | 17 | constructor(Controller _controller) Controlled(_controller) public {} 18 | 19 | function mockChecksFailing(bool _failing) external { 20 | failing = _failing; 21 | } 22 | 23 | function mockVoterWeight(address _voter, uint64 _weight) external { 24 | weights[_voter] = _weight; 25 | } 26 | 27 | function create(uint256 _voteId, uint8 _ruling) external { 28 | _voting().create(_voteId, _ruling); 29 | } 30 | 31 | function ensureCanCommit(uint256 /* _voteId */) external { 32 | require(!failing, ERROR_OWNER_MOCK_COMMIT_CHECK_REVERTED); 33 | } 34 | 35 | function ensureCanCommit(uint256 /* _voteId */, address _voter) external { 36 | require(!failing, ERROR_OWNER_MOCK_COMMIT_CHECK_REVERTED); 37 | require(weights[_voter] > 0, ERROR_VOTER_WEIGHT_ZERO); 38 | } 39 | 40 | function ensureCanReveal(uint256 /* _voteId */, address _voter) external returns (uint64) { 41 | require(!failing, ERROR_OWNER_MOCK_REVEAL_CHECK_REVERTED); 42 | return weights[_voter]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/test/lib/JurorsTreeSortitionMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "./HexSumTreeMock.sol"; 4 | import "../../lib/JurorsTreeSortition.sol"; 5 | 6 | 7 | contract JurorsTreeSortitionMock is HexSumTreeMock { 8 | using JurorsTreeSortition for HexSumTree.Tree; 9 | 10 | function batchedRandomSearch( 11 | bytes32 _termRandomness, 12 | uint256 _disputeId, 13 | uint64 _termId, 14 | uint256 _selectedJurors, 15 | uint256 _batchRequestedJurors, 16 | uint256 _roundRequestedJurors, 17 | uint256 _sortitionIteration 18 | ) 19 | public 20 | view 21 | returns (uint256[] memory jurorsIds, uint256[] memory activeBalances) 22 | { 23 | return tree.batchedRandomSearch(_termRandomness, _disputeId, _termId, _selectedJurors, _batchRequestedJurors, _roundRequestedJurors, _sortitionIteration); 24 | } 25 | 26 | function getSearchBatchBounds(uint64 _termId, uint256 _selectedJurors, uint256 _batchRequestedJurors, uint256 _roundRequestedJurors) 27 | public 28 | view 29 | returns (uint256 low, uint256 high) 30 | { 31 | return tree.getSearchBatchBounds(_termId, _selectedJurors, _batchRequestedJurors, _roundRequestedJurors); 32 | } 33 | 34 | function computeSearchRandomBalances( 35 | bytes32 _termRandomness, 36 | uint256 _disputeId, 37 | uint256 _sortitionIteration, 38 | uint256 _batchRequestedJurors, 39 | uint256 _lowActiveBalanceBatchBound, 40 | uint256 _highActiveBalanceBatchBound 41 | ) 42 | public 43 | pure 44 | returns (uint256[] memory) 45 | { 46 | return JurorsTreeSortition._computeSearchRandomBalances(_termRandomness, _disputeId, _sortitionIteration, _batchRequestedJurors, _lowActiveBalanceBatchBound, _highActiveBalanceBatchBound); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/test/lib/HexSumTreeGasProfiler.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/HexSumTree.sol"; 4 | 5 | 6 | contract HexSumTreeGasProfiler { 7 | using HexSumTree for HexSumTree.Tree; 8 | using Checkpointing for Checkpointing.History; 9 | 10 | uint256 private constant BITS_IN_NIBBLE = 4; 11 | 12 | HexSumTree.Tree internal tree; 13 | 14 | event GasConsumed(uint256 gas); 15 | 16 | modifier profileGas { 17 | uint256 initialGas = gasleft(); 18 | _; 19 | emit GasConsumed(initialGas - gasleft()); 20 | } 21 | 22 | function init() external { 23 | tree.init(); 24 | } 25 | 26 | function insert(uint64 _time, uint256 _value) external profileGas { 27 | tree.insert(_time, _value); 28 | } 29 | 30 | function set(uint256 _key, uint64 _time, uint256 _value) external profileGas { 31 | tree.set(_key, _time, _value); 32 | } 33 | 34 | function update(uint256 _key, uint64 _time, uint256 _delta, bool _positive) external profileGas { 35 | tree.update(_key, _time, _delta, _positive); 36 | } 37 | 38 | function search(uint256[] calldata _values, uint64 _time) external profileGas { 39 | tree.search(_values, _time); 40 | } 41 | 42 | function mockNextKey(uint64 _time, uint256 _nextKey) external { 43 | // Compute new height 44 | uint256 newHeight = 0; 45 | for (uint256 key = _nextKey; key > 0; newHeight++) { 46 | key = key >> BITS_IN_NIBBLE; 47 | } 48 | 49 | // Update fake values 50 | tree.nextKey = _nextKey; 51 | tree.height.add(_time, newHeight); 52 | } 53 | 54 | function nextKey() external view returns (uint256) { 55 | return tree.nextKey; 56 | } 57 | 58 | function height() external view returns (uint256) { 59 | return tree.getHeight(); 60 | } 61 | 62 | function total() public view returns (uint256) { 63 | return tree.getTotal(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/test/disputes/DisputeManagerMockForRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../court/controller/Controlled.sol"; 4 | import "../../court/controller/Controller.sol"; 5 | 6 | 7 | contract DisputeManagerMockForRegistry is Controlled { 8 | event Slashed(uint256 collected); 9 | event Collected(bool collected); 10 | event Drafted(address[] addresses, uint256 length); 11 | 12 | constructor(Controller _controller) Controlled(_controller) public {} 13 | 14 | function assignTokens(address _juror, uint256 _amount) external { 15 | _jurorsRegistry().assignTokens(_juror, _amount); 16 | } 17 | 18 | function burnTokens(uint256 _amount) external { 19 | _jurorsRegistry().burnTokens(_amount); 20 | } 21 | 22 | function slashOrUnlock(address[] calldata _jurors, uint256[] calldata _lockedAmounts, bool[] calldata _rewardedJurors) external { 23 | uint256 collectedTokens = _jurorsRegistry().slashOrUnlock(_getLastEnsuredTermId(), _jurors, _lockedAmounts, _rewardedJurors); 24 | emit Slashed(collectedTokens); 25 | } 26 | 27 | function collect(address _juror, uint256 _amount) external { 28 | bool collected = _jurorsRegistry().collectTokens(_juror, _amount, _getLastEnsuredTermId()); 29 | emit Collected(collected); 30 | } 31 | 32 | function draft( 33 | bytes32 _termRandomness, 34 | uint256 _disputeId, 35 | uint256 _selectedJurors, 36 | uint256 _batchRequestedJurors, 37 | uint64 _roundRequestedJurors, 38 | uint16 _lockPct 39 | ) 40 | external 41 | { 42 | uint256[7] memory draftParams = [ 43 | uint256(_termRandomness), 44 | _disputeId, 45 | _getLastEnsuredTermId(), 46 | _selectedJurors, 47 | _batchRequestedJurors, 48 | _roundRequestedJurors, 49 | _lockPct 50 | ]; 51 | 52 | (address[] memory jurors, uint256 length) = _jurorsRegistry().draft(draftParams); 53 | emit Drafted(jurors, length); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/arbitration/IArbitrator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../lib/os/ERC20.sol"; 4 | 5 | 6 | interface IArbitrator { 7 | /** 8 | * @dev Create a dispute over the Arbitrable sender with a number of possible rulings 9 | * @param _possibleRulings Number of possible rulings allowed for the dispute 10 | * @param _metadata Optional metadata that can be used to provide additional information on the dispute to be created 11 | * @return Dispute identification number 12 | */ 13 | function createDispute(uint256 _possibleRulings, bytes calldata _metadata) external returns (uint256); 14 | 15 | /** 16 | * @dev Close the evidence period of a dispute 17 | * @param _disputeId Identification number of the dispute to close its evidence submitting period 18 | */ 19 | function closeEvidencePeriod(uint256 _disputeId) external; 20 | 21 | /** 22 | * @dev Execute the Arbitrable associated to a dispute based on its final ruling 23 | * @param _disputeId Identification number of the dispute to be executed 24 | */ 25 | function executeRuling(uint256 _disputeId) external; 26 | 27 | /** 28 | * @dev Tell the dispute fees information to create a dispute 29 | * @return recipient Address where the corresponding dispute fees must be transferred to 30 | * @return feeToken ERC20 token used for the fees 31 | * @return feeAmount Total amount of fees that must be allowed to the recipient 32 | */ 33 | function getDisputeFees() external view returns (address recipient, ERC20 feeToken, uint256 feeAmount); 34 | 35 | /** 36 | * @dev Tell the subscription fees information for a subscriber to be up-to-date 37 | * @param _subscriber Address of the account paying the subscription fees for 38 | * @return recipient Address where the corresponding subscriptions fees must be transferred to 39 | * @return feeToken ERC20 token used for the subscription fees 40 | * @return feeAmount Total amount of fees that must be allowed to the recipient 41 | */ 42 | function getSubscriptionFees(address _subscriber) external view returns (address recipient, ERC20 feeToken, uint256 feeAmount); 43 | } 44 | -------------------------------------------------------------------------------- /contracts/test/lib/HexSumTreeMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/HexSumTree.sol"; 4 | 5 | 6 | contract HexSumTreeMock { 7 | using HexSumTree for HexSumTree.Tree; 8 | 9 | HexSumTree.Tree internal tree; 10 | 11 | function init() external { 12 | tree.init(); 13 | } 14 | 15 | function insert(uint64 _time, uint256 _value) external returns (uint256) { 16 | return tree.insert(_time, _value); 17 | } 18 | 19 | function set(uint256 _key, uint64 _time, uint256 _value) external { 20 | tree.set(_key, _time, _value); 21 | } 22 | 23 | function update(uint256 _key, uint64 _time, uint256 _delta, bool _positive) external { 24 | tree.update(_key, _time, _delta, _positive); 25 | } 26 | 27 | function nextKey() external view returns (uint256) { 28 | return tree.nextKey; 29 | } 30 | 31 | function total() external view returns (uint256) { 32 | return tree.getTotal(); 33 | } 34 | 35 | function totalAt(uint64 _time) external view returns (uint256) { 36 | return tree.getTotalAt(_time); 37 | } 38 | 39 | function node(uint256 _level, uint256 _key) external view returns (uint256) { 40 | return tree.getNode(_level, _key); 41 | } 42 | 43 | function nodeAt(uint256 _level, uint256 _key, uint64 _time) external view returns (uint256) { 44 | return tree.getNodeAt(_level, _key, _time); 45 | } 46 | 47 | function item(uint256 _key) external view returns (uint256) { 48 | return tree.getItem(_key); 49 | } 50 | 51 | function itemAt(uint256 _key, uint64 _time) external view returns (uint256) { 52 | return tree.getItemAt(_key, _time); 53 | } 54 | 55 | function height() external view returns (uint256) { 56 | return tree.getHeight(); 57 | } 58 | 59 | function heightAt(uint64 _time) external view returns (uint256) { 60 | return tree.getRecentHeightAt(_time); 61 | } 62 | 63 | function search(uint256[] calldata _values, uint64 _time) external view returns (uint256[] memory keys, uint256[] memory values) { 64 | return tree.search(_values, _time); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docs/5-data-structures/3-dispute-manager.md: -------------------------------------------------------------------------------- 1 | ## 5.3. Dispute Manager 2 | 3 | The following objects are the data-structures used by the `DisputeManager`: 4 | 5 | ### 5.3.1. Dispute 6 | 7 | The dispute object includes the following fields: 8 | 9 | - **Subject:** Arbitrable instance associated to a dispute 10 | - **Possible rulings:** Number of possible rulings jurors can vote for each dispute 11 | - **Creation term ID:** Identification number when the dispute was created 12 | - **Final ruling:** Winning ruling of a dispute 13 | - **Dispute state:** State of a dispute: pre-draft, adjudicating, or ruled 14 | - **Adjudication rounds:** List of adjudication rounds for each dispute 15 | 16 | ### 5.3.2. Adjudication round 17 | 18 | The adjudication round object includes the following fields: 19 | 20 | - **Draft term ID:** Term from which the jurors of a round can be drafted 21 | - **Jurors number:** Number of jurors drafted for a round 22 | - **Settled penalties:** Whether or not penalties have been settled for a round 23 | - **Juror fees:** Total amount of fees to be distributed between the winning jurors of a round 24 | - **Jurors:** List of jurors drafted for a round 25 | - **Jurors states:** List of states for each drafted juror indexed by address 26 | - **Delayed terms:** Number of terms a round was delayed based on its requested draft term id 27 | - **Selected jurors:** Number of jurors selected for a round, to allow drafts to be batched 28 | - **Coherent jurors:** Number of drafted jurors that voted in favor of the dispute final ruling 29 | - **Settled jurors:** Number of jurors whose rewards were already settled 30 | - **Collected tokens:** Total amount of tokens collected from losing jurors 31 | - **Appeal:** Appeal-related information of a round 32 | 33 | ### 5.3.3. Juror state 34 | 35 | The juror state object includes the following fields: 36 | 37 | - **Weight:** Weight computed for a juror on a round 38 | - **Rewarded:** Whether or not a drafted juror was rewarded 39 | 40 | ### 5.3.4. Appeal 41 | 42 | The appeal object includes the following fields: 43 | 44 | - **Maker:** Address of the appealer 45 | - **Appealed ruling:** Ruling appealing in favor of 46 | - **Taker:** Address of the one confirming an appeal 47 | - **Opposed ruling:** Ruling opposed to an appeal 48 | - **Settled:** Whether or not an appeal has been settled 49 | -------------------------------------------------------------------------------- /test/disputes/disputes-skip.js: -------------------------------------------------------------------------------- 1 | const { assertBn } = require('../helpers/asserts/assertBn') 2 | const { assertRevert } = require('../helpers/asserts/assertThrow') 3 | const { DISPUTE_MANAGER_ERRORS } = require('../helpers/utils/errors') 4 | const { buildHelper, DISPUTE_STATES } = require('../helpers/wrappers/court')(web3, artifacts) 5 | 6 | contract('DisputeManager', () => { 7 | let courtHelper, disputeManager 8 | 9 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 10 | 11 | const setup = skippedDisputes => { 12 | beforeEach('setup court', async () => { 13 | courtHelper = buildHelper() 14 | await courtHelper.deploy({ skippedDisputes }) 15 | disputeManager = courtHelper.disputeManager 16 | }) 17 | } 18 | 19 | const itSkipsTheNumberOfRequestedDisputes = skippedDisputes => { 20 | let disputeId 21 | 22 | beforeEach('create first dispute', async () => { 23 | disputeId = await courtHelper.dispute() 24 | }) 25 | 26 | it('skips the number of requested rounds', async () => { 27 | assertBn(disputeId, skippedDisputes, 'dispute ID does not match') 28 | }) 29 | 30 | it('ignores the previous disputes', async () => { 31 | const { subject, possibleRulings: rulings, state, finalRuling, createTermId } = await courtHelper.getDispute(0) 32 | 33 | assert.equal(subject, ZERO_ADDRESS, 'dispute subject does not match') 34 | assertBn(state, DISPUTE_STATES.PRE_DRAFT, 'dispute state does not match') 35 | assertBn(rulings, 0, 'dispute possible rulings do not match') 36 | assertBn(finalRuling, 0, 'dispute final ruling does not match') 37 | assertBn(createTermId, 0, 'dispute create term ID does not match') 38 | }) 39 | 40 | it('does not create rounds for the skipped disputes', async () => { 41 | for (let id = 0; id < disputeId; id++) { 42 | await assertRevert(disputeManager.getRound(id, 0), DISPUTE_MANAGER_ERRORS.ROUND_DOES_NOT_EXIST) 43 | } 44 | }) 45 | } 46 | 47 | context('when skipping one dispute', () => { 48 | const skippedDisputes = 1 49 | setup(skippedDisputes) 50 | itSkipsTheNumberOfRequestedDisputes(skippedDisputes) 51 | }) 52 | 53 | context('when skipping many disputes', () => { 54 | const skippedDisputes = 10 55 | setup(skippedDisputes) 56 | itSkipsTheNumberOfRequestedDisputes(skippedDisputes) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /contracts/standards/ERC900.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | 4 | // Interface for ERC900: https://eips.ethereum.org/EIPS/eip-900 5 | interface ERC900 { 6 | event Staked(address indexed user, uint256 amount, uint256 total, bytes data); 7 | event Unstaked(address indexed user, uint256 amount, uint256 total, bytes data); 8 | 9 | /** 10 | * @dev Stake a certain amount of tokens 11 | * @param _amount Amount of tokens to be staked 12 | * @param _data Optional data that can be used to add signalling information in more complex staking applications 13 | */ 14 | function stake(uint256 _amount, bytes calldata _data) external; 15 | 16 | /** 17 | * @dev Stake a certain amount of tokens in favor of someone 18 | * @param _user Address to stake an amount of tokens to 19 | * @param _amount Amount of tokens to be staked 20 | * @param _data Optional data that can be used to add signalling information in more complex staking applications 21 | */ 22 | function stakeFor(address _user, uint256 _amount, bytes calldata _data) external; 23 | 24 | /** 25 | * @dev Unstake a certain amount of tokens 26 | * @param _amount Amount of tokens to be unstaked 27 | * @param _data Optional data that can be used to add signalling information in more complex staking applications 28 | */ 29 | function unstake(uint256 _amount, bytes calldata _data) external; 30 | 31 | /** 32 | * @dev Tell the total amount of tokens staked for an address 33 | * @param _addr Address querying the total amount of tokens staked for 34 | * @return Total amount of tokens staked for an address 35 | */ 36 | function totalStakedFor(address _addr) external view returns (uint256); 37 | 38 | /** 39 | * @dev Tell the total amount of tokens staked 40 | * @return Total amount of tokens staked 41 | */ 42 | function totalStaked() external view returns (uint256); 43 | 44 | /** 45 | * @dev Tell the address of the token used for staking 46 | * @return Address of the token used for staking 47 | */ 48 | function token() external view returns (address); 49 | 50 | /* 51 | * @dev Tell if the current registry supports historic information or not 52 | * @return True if the optional history functions are implemented, false otherwise 53 | */ 54 | function supportsHistory() external pure returns (bool); 55 | } 56 | -------------------------------------------------------------------------------- /contracts/lib/os/SafeMath64.sol: -------------------------------------------------------------------------------- 1 | // Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/lib/math/SafeMath64.sol 2 | // Adapted to use pragma ^0.5.8 and satisfy our linter rules 3 | 4 | pragma solidity ^0.5.8; 5 | 6 | 7 | /** 8 | * @title SafeMath64 9 | * @dev Math operations for uint64 with safety checks that revert on error 10 | */ 11 | library SafeMath64 { 12 | string private constant ERROR_ADD_OVERFLOW = "MATH64_ADD_OVERFLOW"; 13 | string private constant ERROR_SUB_UNDERFLOW = "MATH64_SUB_UNDERFLOW"; 14 | string private constant ERROR_MUL_OVERFLOW = "MATH64_MUL_OVERFLOW"; 15 | string private constant ERROR_DIV_ZERO = "MATH64_DIV_ZERO"; 16 | 17 | /** 18 | * @dev Multiplies two numbers, reverts on overflow. 19 | */ 20 | function mul(uint64 _a, uint64 _b) internal pure returns (uint64) { 21 | uint256 c = uint256(_a) * uint256(_b); 22 | require(c < 0x010000000000000000, ERROR_MUL_OVERFLOW); // 2**64 (less gas this way) 23 | 24 | return uint64(c); 25 | } 26 | 27 | /** 28 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 29 | */ 30 | function div(uint64 _a, uint64 _b) internal pure returns (uint64) { 31 | require(_b > 0, ERROR_DIV_ZERO); // Solidity only automatically asserts when dividing by 0 32 | uint64 c = _a / _b; 33 | // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold 34 | 35 | return c; 36 | } 37 | 38 | /** 39 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 40 | */ 41 | function sub(uint64 _a, uint64 _b) internal pure returns (uint64) { 42 | require(_b <= _a, ERROR_SUB_UNDERFLOW); 43 | uint64 c = _a - _b; 44 | 45 | return c; 46 | } 47 | 48 | /** 49 | * @dev Adds two numbers, reverts on overflow. 50 | */ 51 | function add(uint64 _a, uint64 _b) internal pure returns (uint64) { 52 | uint64 c = _a + _b; 53 | require(c >= _a, ERROR_ADD_OVERFLOW); 54 | 55 | return c; 56 | } 57 | 58 | /** 59 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 60 | * reverts when dividing by zero. 61 | */ 62 | function mod(uint64 a, uint64 b) internal pure returns (uint64) { 63 | require(b != 0, ERROR_DIV_ZERO); 64 | return a % b; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/court/clock/IClock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | 4 | interface IClock { 5 | /** 6 | * @dev Ensure that the current term of the clock is up-to-date 7 | * @return Identification number of the current term 8 | */ 9 | function ensureCurrentTerm() external returns (uint64); 10 | 11 | /** 12 | * @dev Transition up to a certain number of terms to leave the clock up-to-date 13 | * @param _maxRequestedTransitions Max number of term transitions allowed by the sender 14 | * @return Identification number of the term ID after executing the heartbeat transitions 15 | */ 16 | function heartbeat(uint64 _maxRequestedTransitions) external returns (uint64); 17 | 18 | /** 19 | * @dev Ensure that a certain term has its randomness set 20 | * @return Randomness of the current term 21 | */ 22 | function ensureCurrentTermRandomness() external returns (bytes32); 23 | 24 | /** 25 | * @dev Tell the last ensured term identification number 26 | * @return Identification number of the last ensured term 27 | */ 28 | function getLastEnsuredTermId() external view returns (uint64); 29 | 30 | /** 31 | * @dev Tell the current term identification number. Note that there may be pending term transitions. 32 | * @return Identification number of the current term 33 | */ 34 | function getCurrentTermId() external view returns (uint64); 35 | 36 | /** 37 | * @dev Tell the number of terms the clock should transition to be up-to-date 38 | * @return Number of terms the clock should transition to be up-to-date 39 | */ 40 | function getNeededTermTransitions() external view returns (uint64); 41 | 42 | /** 43 | * @dev Tell the information related to a term based on its ID 44 | * @param _termId ID of the term being queried 45 | * @return startTime Term start time 46 | * @return randomnessBN Block number used for randomness in the requested term 47 | * @return randomness Randomness computed for the requested term 48 | */ 49 | function getTerm(uint64 _termId) external view returns (uint64 startTime, uint64 randomnessBN, bytes32 randomness); 50 | 51 | /** 52 | * @dev Tell the randomness of a term even if it wasn't computed yet 53 | * @param _termId Identification number of the term being queried 54 | * @return Randomness of the requested term 55 | */ 56 | function getTermRandomness(uint64 _termId) external view returns (bytes32); 57 | } 58 | -------------------------------------------------------------------------------- /test/court/controller/controlled.js: -------------------------------------------------------------------------------- 1 | const { buildHelper } = require('../../helpers/wrappers/court')(web3, artifacts) 2 | const { assertRevert } = require('../../helpers/asserts/assertThrow') 3 | const { CONTROLLED_ERRORS } = require('../../helpers/utils/errors') 4 | const { assertAmountOfEvents } = require('../../helpers/asserts/assertEvent') 5 | 6 | const Controlled = artifacts.require('ControlledMock') 7 | 8 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 9 | 10 | contract('Controlled', ([_, fundsGovernor, configGovernor, modulesGovernor, someone]) => { 11 | let controller, controlled 12 | 13 | beforeEach('create controlled', async () => { 14 | controller = await buildHelper().deploy({ fundsGovernor, configGovernor, modulesGovernor }) 15 | controlled = await Controlled.new(controller.address) 16 | }) 17 | 18 | describe('constructor', () => { 19 | context('when the initialization succeeds', () => { 20 | it('initializes the controlled', async () => { 21 | controlled = await Controlled.new(controller.address) 22 | 23 | assert.equal(await controlled.getController(), controller.address, 'controller does not match') 24 | }) 25 | }) 26 | 27 | context('when the initialization fails', () => { 28 | context('when the given controller is not a contract', () => { 29 | const controllerAddress = someone 30 | 31 | it('reverts', async () => { 32 | await assertRevert(Controlled.new(controllerAddress), CONTROLLED_ERRORS.CONTROLLER_NOT_CONTRACT) 33 | }) 34 | }) 35 | 36 | context('when the given controller is the zero address', () => { 37 | const controllerAddress = ZERO_ADDRESS 38 | 39 | it('reverts', async () => { 40 | await assertRevert(Controlled.new(controllerAddress), CONTROLLED_ERRORS.CONTROLLER_NOT_CONTRACT) 41 | }) 42 | }) 43 | }) 44 | }) 45 | 46 | describe('onlyConfigGovernor', () => { 47 | context('when the sender is the governor', () => { 48 | const from = configGovernor 49 | 50 | it('executes call', async () => { 51 | const receipt = await controlled.onlyConfigGovernorFn({ from }) 52 | 53 | assertAmountOfEvents(receipt, 'OnlyConfigGovernorCalled') 54 | }) 55 | }) 56 | 57 | context('when the sender is not the governor', () => { 58 | const from = someone 59 | 60 | it('reverts', async () => { 61 | await assertRevert(controlled.onlyConfigGovernorFn({ from }), CONTROLLED_ERRORS.SENDER_NOT_CONFIG_GOVERNOR) 62 | }) 63 | }) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /contracts/test/registry/JurorsRegistryMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../registry/JurorsRegistry.sol"; 4 | 5 | 6 | contract JurorsRegistryMock is JurorsRegistry { 7 | string private constant ERROR_INVALID_MOCK_LOCK_AMOUNT = 'JR_INVALID_MOCK_LOCK_AMOUNT'; 8 | 9 | bool internal nextDraftMocked; 10 | address[] internal mockedSelectedJurors; 11 | 12 | constructor (Controller _controller, ERC20 _jurorToken, uint256 _totalActiveBalanceLimit) 13 | public 14 | JurorsRegistry(_controller, _jurorToken, _totalActiveBalanceLimit) 15 | {} 16 | 17 | function mockLock(address _juror, uint256 _leftUnlockedAmount) external { 18 | Juror storage juror = jurorsByAddress[_juror]; 19 | uint256 active = _existsJuror(juror) ? tree.getItem(juror.id) : 0; 20 | require(_leftUnlockedAmount < active, ERROR_INVALID_MOCK_LOCK_AMOUNT); 21 | juror.lockedBalance = active - _leftUnlockedAmount; 22 | } 23 | 24 | function collect(address _juror, uint256 _amount) external { 25 | Juror storage juror = jurorsByAddress[_juror]; 26 | uint64 nextTermId = _getLastEnsuredTermId() + 1; 27 | tree.update(juror.id, nextTermId, _amount, false); 28 | } 29 | 30 | function mockNextDraft(address[] calldata _selectedJurors, uint256[] calldata _weights) external { 31 | nextDraftMocked = true; 32 | 33 | delete mockedSelectedJurors; 34 | for (uint256 i = 0; i < _selectedJurors.length; i++) { 35 | for (uint256 j = 0; j < _weights[i]; j++) { 36 | mockedSelectedJurors.push(_selectedJurors[i]); 37 | } 38 | } 39 | } 40 | 41 | function _treeSearch(DraftParams memory _params) internal view returns (uint256[] memory, uint256[] memory) { 42 | if (nextDraftMocked) { 43 | return _runMockedSearch(_params); 44 | } 45 | return super._treeSearch(_params); 46 | } 47 | 48 | function _runMockedSearch(DraftParams memory _params) internal view returns (uint256[] memory ids, uint256[] memory activeBalances) { 49 | uint256 length = mockedSelectedJurors.length; 50 | ids = new uint256[](length); 51 | activeBalances = new uint256[](length); 52 | 53 | for (uint256 i = 0; i < mockedSelectedJurors.length; i++) { 54 | address juror = mockedSelectedJurors[i]; 55 | uint256 id = jurorsByAddress[juror].id; 56 | uint256 activeBalance = tree.getItemAt(id, _params.termId); 57 | 58 | ids[i] = id; 59 | activeBalances[i] = activeBalance; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/lib/os/SafeMath.sol: -------------------------------------------------------------------------------- 1 | // Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/lib/math/SafeMath.sol 2 | // Adapted to use pragma ^0.5.8 and satisfy our linter rules 3 | 4 | pragma solidity >=0.4.24 <0.6.0; 5 | 6 | 7 | /** 8 | * @title SafeMath 9 | * @dev Math operations with safety checks that revert on error 10 | */ 11 | library SafeMath { 12 | string private constant ERROR_ADD_OVERFLOW = "MATH_ADD_OVERFLOW"; 13 | string private constant ERROR_SUB_UNDERFLOW = "MATH_SUB_UNDERFLOW"; 14 | string private constant ERROR_MUL_OVERFLOW = "MATH_MUL_OVERFLOW"; 15 | string private constant ERROR_DIV_ZERO = "MATH_DIV_ZERO"; 16 | 17 | /** 18 | * @dev Multiplies two numbers, reverts on overflow. 19 | */ 20 | function mul(uint256 _a, uint256 _b) internal pure returns (uint256) { 21 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 22 | // benefit is lost if 'b' is also tested. 23 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 24 | if (_a == 0) { 25 | return 0; 26 | } 27 | 28 | uint256 c = _a * _b; 29 | require(c / _a == _b, ERROR_MUL_OVERFLOW); 30 | 31 | return c; 32 | } 33 | 34 | /** 35 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 36 | */ 37 | function div(uint256 _a, uint256 _b) internal pure returns (uint256) { 38 | require(_b > 0, ERROR_DIV_ZERO); // Solidity only automatically asserts when dividing by 0 39 | uint256 c = _a / _b; 40 | // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold 41 | 42 | return c; 43 | } 44 | 45 | /** 46 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 47 | */ 48 | function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { 49 | require(_b <= _a, ERROR_SUB_UNDERFLOW); 50 | uint256 c = _a - _b; 51 | 52 | return c; 53 | } 54 | 55 | /** 56 | * @dev Adds two numbers, reverts on overflow. 57 | */ 58 | function add(uint256 _a, uint256 _b) internal pure returns (uint256) { 59 | uint256 c = _a + _b; 60 | require(c >= _a, ERROR_ADD_OVERFLOW); 61 | 62 | return c; 63 | } 64 | 65 | /** 66 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 67 | * reverts when dividing by zero. 68 | */ 69 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 70 | require(b != 0, ERROR_DIV_ZERO); 71 | return a % b; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contracts/arbitration/IArbitrable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "./IArbitrator.sol"; 4 | import "../standards/ERC165.sol"; 5 | 6 | 7 | contract IArbitrable is ERC165 { 8 | bytes4 internal constant ERC165_INTERFACE_ID = bytes4(0x01ffc9a7); 9 | bytes4 internal constant ARBITRABLE_INTERFACE_ID = bytes4(0x88f3ee69); 10 | 11 | /** 12 | * @dev Emitted when an IArbitrable instance's dispute is ruled by an IArbitrator 13 | * @param arbitrator IArbitrator instance ruling the dispute 14 | * @param disputeId Identification number of the dispute being ruled by the arbitrator 15 | * @param ruling Ruling given by the arbitrator 16 | */ 17 | event Ruled(IArbitrator indexed arbitrator, uint256 indexed disputeId, uint256 ruling); 18 | 19 | /** 20 | * @dev Emitted when new evidence is submitted for the IArbitrable instance's dispute 21 | * @param arbitrator IArbitrator submitting the evidence for 22 | * @param disputeId Identification number of the dispute receiving new evidence 23 | * @param submitter Address of the account submitting the evidence 24 | * @param evidence Data submitted for the evidence of the dispute 25 | * @param finished Whether or not the submitter has finished submitting evidence 26 | */ 27 | event EvidenceSubmitted(IArbitrator indexed arbitrator, uint256 indexed disputeId, address indexed submitter, bytes evidence, bool finished); 28 | 29 | /** 30 | * @dev Submit evidence for a dispute 31 | * @param _disputeId Id of the dispute in the Court 32 | * @param _evidence Data submitted for the evidence related to the dispute 33 | * @param _finished Whether or not the submitter has finished submitting evidence 34 | */ 35 | function submitEvidence(uint256 _disputeId, bytes calldata _evidence, bool _finished) external; 36 | 37 | /** 38 | * @dev Give a ruling for a certain dispute, the account calling it must have rights to rule on the contract 39 | * @param _disputeId Identification number of the dispute to be ruled 40 | * @param _ruling Ruling given by the arbitrator, where 0 is reserved for "refused to make a decision" 41 | */ 42 | function rule(uint256 _disputeId, uint256 _ruling) external; 43 | 44 | /** 45 | * @dev ERC165 - Query if a contract implements a certain interface 46 | * @param _interfaceId The interface identifier being queried, as specified in ERC-165 47 | * @return True if this contract supports the given interface, false otherwise 48 | */ 49 | function supportsInterface(bytes4 _interfaceId) external pure returns (bool) { 50 | return _interfaceId == ARBITRABLE_INTERFACE_ID || _interfaceId == ERC165_INTERFACE_ID; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/5-data-structures/2-controller.md: -------------------------------------------------------------------------------- 1 | ## 5.2. Controller 2 | 3 | The following objects are the data-structures used by the `Controller`: 4 | 5 | ### 5.2.1. Governor 6 | 7 | The governor object includes the following fields: 8 | 9 | - **Funds:** Address allowed to recover funds from the ERC20-Recoverable modules 10 | - **Config:** Address allowed to change the different configurations of the whole system 11 | - **Modules:** Address allowed to plug/unplug modules from the system 12 | 13 | ### 5.2.2. Config 14 | 15 | The config object includes the following fields: 16 | 17 | - **Fees config:** Fees config object 18 | - **Disputes config:** Disputes config object 19 | - **Min active balance:** Minimum amount of tokens jurors have to activate to participate in the Court 20 | 21 | ### 5.2.3. Fees config 22 | 23 | The fees config object includes the following fields: 24 | 25 | - **Token:** ERC20 token to be used for the fees of the Court 26 | - **Final round reduction:** Permyriad of fees reduction applied for final appeal round (1/10,000) 27 | - **Juror fee:** Amount of tokens paid to draft a juror to adjudicate a dispute 28 | - **Draft fee:** Amount of tokens paid per round to cover the costs of drafting jurors 29 | - **Settle fee:** Amount of tokens paid per round to cover the costs of slashing jurors 30 | 31 | ### 5.2.4. Disputes config 32 | 33 | The disputes config object includes the following fields: 34 | 35 | - **Evidence terms:** Max submitting evidence period duration in Court terms 36 | - **Commit terms:** Committing period duration in terms 37 | - **Reveal terms:** Revealing period duration in terms 38 | - **Appeal terms:** Appealing period duration in terms 39 | - **Appeal confirmation terms:** Confirmation appeal period duration in terms 40 | - **Penalty permyriad:** ‱ of min active tokens balance to be locked for each drafted juror (1/10,000) 41 | - **First round jurors number:** Number of jurors drafted on first round 42 | - **Appeal step factor:** Factor in which the jurors number is increased on each appeal 43 | - **Final round lock terms:** Period a coherent juror in the final round will remain locked 44 | - **Max regular appeal rounds:** Before the final appeal 45 | - **Appeal collateral factor:** Permyriad multiple of dispute fees (jurors, draft, and settlements) required to appeal a preliminary ruling (1/10,000) 46 | - **Appeal confirmation collateral factor:** Permyriad multiple of dispute fees (jurors, draft, and settlements) required to confirm appeal (1/10,000) 47 | 48 | ### 5.2.5. Term 49 | 50 | The term object includes the following fields: 51 | 52 | - **Start time:** Timestamp when the term started 53 | - **Randomness block number:** Block number for entropy 54 | - **Randomness:** Entropy from randomness block number's hash 55 | -------------------------------------------------------------------------------- /contracts/court/config/CourtConfigData.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/os/ERC20.sol"; 4 | 5 | 6 | contract CourtConfigData { 7 | struct Config { 8 | FeesConfig fees; // Full fees-related config 9 | DisputesConfig disputes; // Full disputes-related config 10 | uint256 minActiveBalance; // Minimum amount of tokens jurors have to activate to participate in the Court 11 | } 12 | 13 | struct FeesConfig { 14 | ERC20 token; // ERC20 token to be used for the fees of the Court 15 | uint16 finalRoundReduction; // Permyriad of fees reduction applied for final appeal round (‱ - 1/10,000) 16 | uint256 jurorFee; // Amount of tokens paid to draft a juror to adjudicate a dispute 17 | uint256 draftFee; // Amount of tokens paid per round to cover the costs of drafting jurors 18 | uint256 settleFee; // Amount of tokens paid per round to cover the costs of slashing jurors 19 | } 20 | 21 | struct DisputesConfig { 22 | uint64 evidenceTerms; // Max submitting evidence period duration in terms 23 | uint64 commitTerms; // Committing period duration in terms 24 | uint64 revealTerms; // Revealing period duration in terms 25 | uint64 appealTerms; // Appealing period duration in terms 26 | uint64 appealConfirmTerms; // Confirmation appeal period duration in terms 27 | uint16 penaltyPct; // Permyriad of min active tokens balance to be locked for each drafted juror (‱ - 1/10,000) 28 | uint64 firstRoundJurorsNumber; // Number of jurors drafted on first round 29 | uint64 appealStepFactor; // Factor in which the jurors number is increased on each appeal 30 | uint64 finalRoundLockTerms; // Period a coherent juror in the final round will remain locked 31 | uint256 maxRegularAppealRounds; // Before the final appeal 32 | uint256 appealCollateralFactor; // Permyriad multiple of dispute fees required to appeal a preliminary ruling (‱ - 1/10,000) 33 | uint256 appealConfirmCollateralFactor; // Permyriad multiple of dispute fees required to confirm appeal (‱ - 1/10,000) 34 | } 35 | 36 | struct DraftConfig { 37 | ERC20 feeToken; // ERC20 token to be used for the fees of the Court 38 | uint16 penaltyPct; // Permyriad of min active tokens balance to be locked for each drafted juror (‱ - 1/10,000) 39 | uint256 draftFee; // Amount of tokens paid per round to cover the costs of drafting jurors 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/test/lib/TimeHelpersMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/os/SafeMath.sol"; 4 | import "../../lib/os/SafeMath64.sol"; 5 | import "../../lib/os/TimeHelpers.sol"; 6 | 7 | 8 | contract TimeHelpersMock is TimeHelpers { 9 | using SafeMath for uint256; 10 | using SafeMath64 for uint64; 11 | 12 | uint256 private mockedTimestamp; 13 | uint256 private mockedSetBlockNumber; 14 | uint256 private mockedAdvancedBlockNumber; 15 | 16 | /** 17 | * @dev Tells the mocked block number in uint256, or the real block number if it wasn't mocked 18 | */ 19 | function getBlockNumberExt() external view returns (uint256) { 20 | return getBlockNumber(); 21 | } 22 | 23 | /** 24 | * @dev Tells the mocked timestamp value in uint256, or the real timestamp if it wasn't mocked 25 | */ 26 | function getTimestampExt() external view returns (uint256) { 27 | return getTimestamp(); 28 | } 29 | 30 | /** 31 | * @dev Sets a mocked block number value, used only for testing purposes 32 | */ 33 | function mockSetBlockNumber(uint256 _number) external { 34 | mockedSetBlockNumber = _number; 35 | } 36 | 37 | /** 38 | * @dev Advances the mocked block number value, used only for testing purposes 39 | */ 40 | function mockAdvanceBlocks(uint256 _number) external { 41 | if (mockedSetBlockNumber != 0) { 42 | mockedAdvancedBlockNumber = mockedSetBlockNumber.add(_number); 43 | mockedSetBlockNumber = 0; 44 | } 45 | else if (mockedAdvancedBlockNumber != 0) mockedAdvancedBlockNumber = mockedAdvancedBlockNumber.add(_number); 46 | else mockedAdvancedBlockNumber = block.number.add(_number); 47 | } 48 | 49 | /** 50 | * @dev Sets a mocked timestamp value, used only for testing purposes 51 | */ 52 | function mockSetTimestamp(uint256 _timestamp) external { 53 | mockedTimestamp = _timestamp; 54 | } 55 | 56 | /** 57 | * @dev Increases the mocked timestamp value, used only for testing purposes 58 | */ 59 | function mockIncreaseTime(uint256 _seconds) external { 60 | if (mockedTimestamp != 0) mockedTimestamp = mockedTimestamp.add(_seconds); 61 | else mockedTimestamp = block.timestamp.add(_seconds); 62 | } 63 | 64 | /** 65 | * @dev Internal function to get the mocked block number if it was set, or current `block.number` 66 | */ 67 | function getBlockNumber() internal view returns (uint256) { 68 | if (mockedSetBlockNumber != 0) return mockedSetBlockNumber; 69 | uint256 realBlockNumber = super.getBlockNumber(); 70 | return (mockedAdvancedBlockNumber > realBlockNumber) ? mockedAdvancedBlockNumber : realBlockNumber; 71 | } 72 | 73 | /** 74 | * @dev Internal function to get the mocked timestamp if it was set, or current `block.timestamp` 75 | */ 76 | function getTimestamp() internal view returns (uint256) { 77 | if (mockedTimestamp != 0) return mockedTimestamp; 78 | return super.getTimestamp(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/helpers/utils/events.js: -------------------------------------------------------------------------------- 1 | const ARBITRABLE_EVENTS = { 2 | RULED: 'Ruled' 3 | } 4 | 5 | const DISPUTE_MANAGER_EVENTS = { 6 | DISPUTE_STATE_CHANGED: 'DisputeStateChanged', 7 | NEW_DISPUTE: 'NewDispute', 8 | JUROR_DRAFTED: 'JurorDrafted', 9 | EVIDENCE_PERIOD_CLOSED: 'EvidencePeriodClosed', 10 | RULING_APPEALED: 'RulingAppealed', 11 | RULING_APPEAL_CONFIRMED: 'RulingAppealConfirmed', 12 | RULING_COMPUTED: 'RulingComputed', 13 | PENALTIES_SETTLED: 'PenaltiesSettled', 14 | REWARD_SETTLED: 'RewardSettled', 15 | APPEAL_DEPOSIT_SETTLED: 'AppealDepositSettled', 16 | MAX_JURORS_PER_DRAFT_BATCH_CHANGED: 'MaxJurorsPerDraftBatchChanged' 17 | } 18 | 19 | const VOTING_EVENTS = { 20 | VOTING_CREATED: 'VotingCreated', 21 | VOTE_COMMITTED: 'VoteCommitted', 22 | VOTE_REVEALED: 'VoteRevealed', 23 | VOTE_LEAKED: 'VoteLeaked' 24 | } 25 | 26 | const REGISTRY_EVENTS = { 27 | STAKED: 'Staked', 28 | UNSTAKED: 'Unstaked', 29 | SLASHED: 'Slashed', 30 | COLLECTED: 'Collected', 31 | JUROR_ACTIVATED: 'JurorActivated', 32 | JUROR_DEACTIVATION_REQUESTED: 'JurorDeactivationRequested', 33 | JUROR_DEACTIVATION_PROCESSED: 'JurorDeactivationProcessed', 34 | JUROR_DEACTIVATION_UPDATED: 'JurorDeactivationUpdated', 35 | JUROR_BALANCE_LOCKED: 'JurorBalanceLocked', 36 | JUROR_BALANCE_UNLOCKED: 'JurorBalanceUnlocked', 37 | JUROR_SLASHED: 'JurorSlashed', 38 | JUROR_TOKENS_BURNED: 'JurorTokensBurned', 39 | JUROR_TOKENS_ASSIGNED: 'JurorTokensAssigned', 40 | JUROR_TOKENS_COLLECTED: 'JurorTokensCollected', 41 | TOTAL_ACTIVE_BALANCE_LIMIT_CHANGED: 'TotalActiveBalanceLimitChanged' 42 | } 43 | 44 | const TREASURY_EVENTS = { 45 | ASSIGN: 'Assign', 46 | WITHDRAW: 'Withdraw' 47 | } 48 | 49 | const SUBSCRIPTIONS_EVENTS = { 50 | FEES_PAID: 'FeesPaid', 51 | FEES_DONATED: 'FeesDonated', 52 | FEES_CLAIMED: 'FeesClaimed', 53 | GOVERNOR_FEES_TRANSFERRED: 'GovernorFeesTransferred', 54 | FEE_TOKEN_CHANGED: 'FeeTokenChanged', 55 | FEE_AMOUNT_CHANGED: 'FeeAmountChanged', 56 | PRE_PAYMENT_PERIODS_CHANGED: 'PrePaymentPeriodsChanged', 57 | GOVERNOR_SHARE_PCT_CHANGED: 'GovernorSharePctChanged', 58 | LATE_PAYMENT_PENALTY_CHANGED: 'LatePaymentPenaltyPctChanged', 59 | RESUME_PENALTIES_CHANGED: 'ResumePenaltiesChanged' 60 | } 61 | 62 | const CONTROLLER_EVENTS = { 63 | MODULE_SET: 'ModuleSet', 64 | FUNDS_GOVERNOR_CHANGED: 'FundsGovernorChanged', 65 | CONFIG_GOVERNOR_CHANGED: 'ConfigGovernorChanged', 66 | MODULES_GOVERNOR_CHANGED: 'ModulesGovernorChanged' 67 | } 68 | 69 | const CONTROLLED_EVENTS = { 70 | RECOVER_FUNDS: 'RecoverFunds' 71 | } 72 | 73 | const CONFIG_EVENTS = { 74 | CONFIG_CHANGED: 'NewConfig', 75 | AUTOMATIC_WITHDRAWALS_ALLOWED_CHANGED: 'AutomaticWithdrawalsAllowedChanged' 76 | } 77 | 78 | const CLOCK_EVENTS = { 79 | HEARTBEAT: 'Heartbeat', 80 | START_TIME_DELAYED: 'StartTimeDelayed' 81 | } 82 | 83 | module.exports = { 84 | DISPUTE_MANAGER_EVENTS, 85 | VOTING_EVENTS, 86 | REGISTRY_EVENTS, 87 | TREASURY_EVENTS, 88 | SUBSCRIPTIONS_EVENTS, 89 | CONTROLLER_EVENTS, 90 | CONTROLLED_EVENTS, 91 | CONFIG_EVENTS, 92 | CLOCK_EVENTS, 93 | ARBITRABLE_EVENTS 94 | } 95 | -------------------------------------------------------------------------------- /contracts/court/config/ConfigConsumer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/os/ERC20.sol"; 4 | 5 | import "./IConfig.sol"; 6 | import "./CourtConfigData.sol"; 7 | 8 | 9 | contract ConfigConsumer is CourtConfigData { 10 | /** 11 | * @dev Internal function to fetch the address of the Config module from the controller 12 | * @return Address of the Config module 13 | */ 14 | function _courtConfig() internal view returns (IConfig); 15 | 16 | /** 17 | * @dev Internal function to get the Court config for a certain term 18 | * @param _termId Identification number of the term querying the Court config of 19 | * @return Court config for the given term 20 | */ 21 | function _getConfigAt(uint64 _termId) internal view returns (Config memory) { 22 | (ERC20 _feeToken, 23 | uint256[3] memory _fees, 24 | uint64[5] memory _roundStateDurations, 25 | uint16[2] memory _pcts, 26 | uint64[4] memory _roundParams, 27 | uint256[2] memory _appealCollateralParams, 28 | uint256 _minActiveBalance) = _courtConfig().getConfig(_termId); 29 | 30 | Config memory config; 31 | 32 | config.fees = FeesConfig({ 33 | token: _feeToken, 34 | jurorFee: _fees[0], 35 | draftFee: _fees[1], 36 | settleFee: _fees[2], 37 | finalRoundReduction: _pcts[1] 38 | }); 39 | 40 | config.disputes = DisputesConfig({ 41 | evidenceTerms: _roundStateDurations[0], 42 | commitTerms: _roundStateDurations[1], 43 | revealTerms: _roundStateDurations[2], 44 | appealTerms: _roundStateDurations[3], 45 | appealConfirmTerms: _roundStateDurations[4], 46 | penaltyPct: _pcts[0], 47 | firstRoundJurorsNumber: _roundParams[0], 48 | appealStepFactor: _roundParams[1], 49 | maxRegularAppealRounds: _roundParams[2], 50 | finalRoundLockTerms: _roundParams[3], 51 | appealCollateralFactor: _appealCollateralParams[0], 52 | appealConfirmCollateralFactor: _appealCollateralParams[1] 53 | }); 54 | 55 | config.minActiveBalance = _minActiveBalance; 56 | 57 | return config; 58 | } 59 | 60 | /** 61 | * @dev Internal function to get the draft config for a given term 62 | * @param _termId Identification number of the term querying the draft config of 63 | * @return Draft config for the given term 64 | */ 65 | function _getDraftConfig(uint64 _termId) internal view returns (DraftConfig memory) { 66 | (ERC20 feeToken, uint256 draftFee, uint16 penaltyPct) = _courtConfig().getDraftConfig(_termId); 67 | return DraftConfig({ feeToken: feeToken, draftFee: draftFee, penaltyPct: penaltyPct }); 68 | } 69 | 70 | /** 71 | * @dev Internal function to get the min active balance config for a given term 72 | * @param _termId Identification number of the term querying the min active balance config of 73 | * @return Minimum amount of juror tokens that can be activated 74 | */ 75 | function _getMinActiveBalance(uint64 _termId) internal view returns (uint256) { 76 | return _courtConfig().getMinActiveBalance(_termId); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/voting/ICRVoting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "./ICRVotingOwner.sol"; 4 | 5 | 6 | interface ICRVoting { 7 | /** 8 | * @dev Create a new vote instance 9 | * @dev This function can only be called by the CRVoting owner 10 | * @param _voteId ID of the new vote instance to be created 11 | * @param _possibleOutcomes Number of possible outcomes for the new vote instance to be created 12 | */ 13 | function create(uint256 _voteId, uint8 _possibleOutcomes) external; 14 | 15 | /** 16 | * @dev Get the winning outcome of a vote instance 17 | * @param _voteId ID of the vote instance querying the winning outcome of 18 | * @return Winning outcome of the given vote instance or refused in case it's missing 19 | */ 20 | function getWinningOutcome(uint256 _voteId) external view returns (uint8); 21 | 22 | /** 23 | * @dev Get the tally of an outcome for a certain vote instance 24 | * @param _voteId ID of the vote instance querying the tally of 25 | * @param _outcome Outcome querying the tally of 26 | * @return Tally of the outcome being queried for the given vote instance 27 | */ 28 | function getOutcomeTally(uint256 _voteId, uint8 _outcome) external view returns (uint256); 29 | 30 | /** 31 | * @dev Tell whether an outcome is valid for a given vote instance or not 32 | * @param _voteId ID of the vote instance to check the outcome of 33 | * @param _outcome Outcome to check if valid or not 34 | * @return True if the given outcome is valid for the requested vote instance, false otherwise 35 | */ 36 | function isValidOutcome(uint256 _voteId, uint8 _outcome) external view returns (bool); 37 | 38 | /** 39 | * @dev Get the outcome voted by a voter for a certain vote instance 40 | * @param _voteId ID of the vote instance querying the outcome of 41 | * @param _voter Address of the voter querying the outcome of 42 | * @return Outcome of the voter for the given vote instance 43 | */ 44 | function getVoterOutcome(uint256 _voteId, address _voter) external view returns (uint8); 45 | 46 | /** 47 | * @dev Tell whether a voter voted in favor of a certain outcome in a vote instance or not 48 | * @param _voteId ID of the vote instance to query if a voter voted in favor of a certain outcome 49 | * @param _outcome Outcome to query if the given voter voted in favor of 50 | * @param _voter Address of the voter to query if voted in favor of the given outcome 51 | * @return True if the given voter voted in favor of the given outcome, false otherwise 52 | */ 53 | function hasVotedInFavorOf(uint256 _voteId, uint8 _outcome, address _voter) external view returns (bool); 54 | 55 | /** 56 | * @dev Filter a list of voters based on whether they voted in favor of a certain outcome in a vote instance or not 57 | * @param _voteId ID of the vote instance to be checked 58 | * @param _outcome Outcome to filter the list of voters of 59 | * @param _voters List of addresses of the voters to be filtered 60 | * @return List of results to tell whether a voter voted in favor of the given outcome or not 61 | */ 62 | function getVotersInFavorOf(uint256 _voteId, uint8 _outcome, address[] calldata _voters) external view returns (bool[] memory); 63 | } 64 | -------------------------------------------------------------------------------- /docs/4-entry-points/5-voting.md: -------------------------------------------------------------------------------- 1 | ## 4.5. Voting 2 | 3 | The `Voting` module is in charge of handling all the votes submitted by the drafted jurors and computing the tallies to ensure the final ruling of a dispute once finished. 4 | In particular, the first version of the Court protocol uses a commit-reveal mechanism. Therefore, the `Voting` module allows jurors to commit and reveal their votes, and leaked other jurors votes. 5 | 6 | ### 4.5.1. Constructor 7 | 8 | - **Actor:** Deployer account 9 | - **Inputs:** 10 | - **Controller:** Address of the `Controller` contract that centralizes all the modules being used 11 | - **Authentication:** Open 12 | - **Pre-flight checks:** 13 | - Ensure that the controller address is a contract 14 | - **State transitions:** 15 | - Save the controller address 16 | 17 | ### 4.5.2. Create 18 | 19 | - **Actor:** `DisputeManager` module 20 | - **Inputs:** 21 | - **Vote ID:** Vote identification number 22 | - **Authentication:** Only `DisputeManager` module 23 | - **Pre-flight checks:** 24 | - Ensure there is no other existing vote for the given vote ID 25 | - **State transitions:** 26 | - Create a new vote object 27 | 28 | ### 4.5.3. Commit 29 | 30 | - **Actor:** Juror drafted for an adjudication round 31 | - **Inputs:** 32 | - **Vote ID:** Vote identification number 33 | - **Commitment:** Hashed outcome to be stored for future reveal 34 | - **Authentication:** Open. Implicitly, only jurors that were drafted for the corresponding adjudication round can call this function 35 | - **Pre-flight checks:** 36 | - Ensure a vote object with that ID exists 37 | - Ensure that the sender was drafted for the corresponding dispute's adjudication round 38 | - Ensure that the sender has not committed a vote before 39 | - Ensure that votes can still be committed for the adjudication round 40 | - **State transitions:** 41 | - Create a cast vote object for the sender voter 42 | 43 | ### 4.5.4. Leak 44 | 45 | - **Actor:** External entity incentivized to slash a juror 46 | - **Inputs:** 47 | - **Vote ID:** Vote identification number 48 | - **Voter:** Address of the voter to leak a vote of 49 | - **Outcome:** Outcome leaked for the voter 50 | - **Salt:** Salt to decrypt and validate the committed vote of the voter 51 | - **Authentication:** Open 52 | - **Pre-flight checks:** 53 | - Ensure the voter commitment can be decrypted with the provided outcome and salt values 54 | - Ensure that votes can still be committed for the adjudication round 55 | - **State transitions:** 56 | - Update the voter's cast vote object marking it as leaked 57 | 58 | ### 4.5.5. Reveal 59 | 60 | - **Actor:** Juror drafted for an adjudication round 61 | - **Inputs:** 62 | - **Vote ID:** Vote identification number 63 | - **Voter:** Address of the voter revealing a vote for 64 | - **Outcome:** Outcome leaked for the voter 65 | - **Salt:** Salt to decrypt and validate the committed vote of the voter 66 | - **Authentication:** Open 67 | - **Pre-flight checks:** 68 | - Ensure the voter commitment can be decrypted with the provided outcome and salt values 69 | - Ensure the resultant outcome is valid 70 | - Ensure that votes can still be revealed for the adjudication round 71 | - **State transitions:** 72 | - Update the voter's cast vote object saving the corresponding outcome 73 | - Update the vote object tally 74 | -------------------------------------------------------------------------------- /test/registry/jurors-registry-initialization.js: -------------------------------------------------------------------------------- 1 | const { bigExp } = require('../helpers/lib/numbers') 2 | const { assertBn } = require('../helpers/asserts/assertBn') 3 | const { buildHelper } = require('../helpers/wrappers/court')(web3, artifacts) 4 | const { assertRevert } = require('../helpers/asserts/assertThrow') 5 | const { CONTROLLED_ERRORS, REGISTRY_ERRORS } = require('../helpers/utils/errors') 6 | 7 | const JurorsRegistry = artifacts.require('JurorsRegistry') 8 | const ERC20 = artifacts.require('ERC20Mock') 9 | 10 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 11 | 12 | contract('JurorsRegistry', ([_, something]) => { 13 | let controller, ANJ 14 | 15 | const TOTAL_ACTIVE_BALANCE_LIMIT = bigExp(100e6, 18) 16 | 17 | beforeEach('create base contracts', async () => { 18 | controller = await buildHelper().deploy() 19 | ANJ = await ERC20.new('ANJ Token', 'ANJ', 18) 20 | }) 21 | 22 | describe('initialize', () => { 23 | context('when the initialization succeeds', () => { 24 | it('sets initial config correctly', async () => { 25 | const registry = await JurorsRegistry.new(controller.address, ANJ.address, TOTAL_ACTIVE_BALANCE_LIMIT) 26 | 27 | assert.isFalse(await registry.supportsHistory()) 28 | assert.equal(await registry.getController(), controller.address, 'registry controller does not match') 29 | assert.equal(await registry.token(), ANJ.address, 'token address does not match') 30 | assertBn((await registry.totalJurorsActiveBalanceLimit()), TOTAL_ACTIVE_BALANCE_LIMIT, 'total active balance limit does not match') 31 | }) 32 | }) 33 | 34 | context('initialization fails', () => { 35 | context('when the given token address is the zero address', () => { 36 | const token = ZERO_ADDRESS 37 | 38 | it('reverts', async () => { 39 | await assertRevert(JurorsRegistry.new(controller.address, token, TOTAL_ACTIVE_BALANCE_LIMIT), REGISTRY_ERRORS.NOT_CONTRACT) 40 | }) 41 | }) 42 | 43 | context('when the given token address is not a contract address', () => { 44 | const token = something 45 | 46 | it('reverts', async () => { 47 | await assertRevert(JurorsRegistry.new(controller.address, token, TOTAL_ACTIVE_BALANCE_LIMIT), REGISTRY_ERRORS.NOT_CONTRACT) 48 | }) 49 | }) 50 | 51 | context('when the given total active balance limit is zero', () => { 52 | const totalActiveBalanceLimit = 0 53 | 54 | it('reverts', async () => { 55 | await assertRevert(JurorsRegistry.new(controller.address, ANJ.address, totalActiveBalanceLimit), REGISTRY_ERRORS.BAD_TOTAL_ACTIVE_BAL_LIMIT) 56 | }) 57 | }) 58 | 59 | context('when the given controller is the zero address', () => { 60 | const controllerAddress = ZERO_ADDRESS 61 | 62 | it('reverts', async () => { 63 | await assertRevert(JurorsRegistry.new(controllerAddress, ANJ.address, TOTAL_ACTIVE_BALANCE_LIMIT), CONTROLLED_ERRORS.CONTROLLER_NOT_CONTRACT) 64 | }) 65 | }) 66 | 67 | context('when the given controller is not a contract address', () => { 68 | const controllerAddress = something 69 | 70 | it('reverts', async () => { 71 | await assertRevert(JurorsRegistry.new(controllerAddress, ANJ.address, TOTAL_ACTIVE_BALANCE_LIMIT), CONTROLLED_ERRORS.CONTROLLER_NOT_CONTRACT) 72 | }) 73 | }) 74 | }) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /docs/4-entry-points/7-treasury.md: -------------------------------------------------------------------------------- 1 | ## 4.7. Treasury 2 | 3 | The `Treasury` module is in charge of handling the token assets related to the disputes process. 4 | The ANJ of the jurors and the subscription fees of the users are the only assets excluded from the `Treasury`. 5 | Except from those, the rest of the fees, deposits, and collaterals required to back the different adjudication rounds of a dispute, are stored in the `Treasury`. 6 | 7 | ### 4.7.1. Constructor 8 | 9 | - **Actor:** Deployer account 10 | - **Inputs:** 11 | - **Controller:** Address of the `Controller` contract that centralizes all the modules being used 12 | - **Authentication:** Open 13 | - **Pre-flight checks:** 14 | - Ensure that the controller address is a contract 15 | - **State transitions:** 16 | - Save the controller address 17 | 18 | ### 4.7.2. Assign 19 | 20 | - **Actor:** `DisputeManager` module 21 | - **Inputs:** 22 | - **Token:** Address of the ERC20-compatible token to be withdrawn 23 | - **Recipient:** Address that will receive the funds being withdrawn 24 | - **Amount:** Amount of tokens to be transferred to the recipient 25 | - **Authentication:** Only `DisputeManager` module 26 | - **Pre-flight checks:** 27 | - Ensure that the requested amount is greater than zero 28 | - **State transitions:** 29 | - Increase the token balance of the recipient based on the requested amount 30 | 31 | ### 4.7.3. Withdraw 32 | 33 | - **Actor:** External entity owning a certain amount of tokens of the `Treasury` module 34 | - **Inputs:** 35 | - **Token:** Address of the ERC20-compatible token to be withdrawn 36 | - **Recipient:** Address that will receive the funds being withdrawn 37 | - **Amount:** Amount of tokens to be transferred to the recipient 38 | - **Authentication:** Open. Implicitly, only addresses that have some balance assigned in the `Treasury` module 39 | - **Pre-flight checks:** 40 | - Ensure that the token balance of the caller is greater than zero 41 | - Ensure that the token balance of the caller is greater than or equal to the requested amount 42 | - **State transitions:** 43 | - Reduce the token balance of the caller based on the requested amount 44 | - Transfer the requested token amount to the recipient address, revert if the ERC20-transfer wasn't successful 45 | 46 | ### 4.7.4. Withdraw all 47 | 48 | - **Actor:** External entity incentivized in transfer the funds of a certain address 49 | - **Inputs:** 50 | - **Token:** Address of the ERC20-compatible token to be withdrawn 51 | - **Recipient:** Address whose funds will be transferred 52 | - **Authentication:** Open 53 | - **Pre-flight checks:** 54 | - Ensure that the token balance of the recipient address is greater than zero 55 | - **State transitions:** 56 | - Set the token balance of the recipient to zero 57 | - Transfer the whole balance of the recipient address to it, revert if the ERC20-transfer wasn't successful 58 | 59 | ### 4.7.5. Recover funds 60 | 61 | - **Actor:** External entity in charge of maintaining the Court protocol 62 | - **Inputs:** 63 | - **Token:** Address of the ERC20-compatible token to be recovered from the `Treasury` module 64 | - **Recipient:** Address that will receive the funds of the `Treasury` module 65 | - **Authentication:** Only funds governor 66 | - **Pre-flight checks:** 67 | - Ensure that the balance of the `Treasury` module is greater than zero 68 | - **State transitions:** 69 | - Transfer the whole balance of the `Treasury` module to the recipient address, revert if the ERC20-transfer wasn't successful 70 | -------------------------------------------------------------------------------- /test/court/controller/controlled-recoverable.js: -------------------------------------------------------------------------------- 1 | const { bigExp } = require('../../helpers/lib/numbers') 2 | const { assertBn } = require('../../helpers/asserts/assertBn') 3 | const { buildHelper } = require('../../helpers/wrappers/court')(web3, artifacts) 4 | const { assertRevert } = require('../../helpers/asserts/assertThrow') 5 | const { CONTROLLED_EVENTS } = require('../../helpers/utils/events') 6 | const { CONTROLLED_ERRORS } = require('../../helpers/utils/errors') 7 | const { assertAmountOfEvents, assertEvent } = require('../../helpers/asserts/assertEvent') 8 | 9 | const ERC20 = artifacts.require('ERC20Mock') 10 | const ControlledRecoverable = artifacts.require('ControlledRecoverable') 11 | 12 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 13 | 14 | contract('ControlledRecoverable', ([_, fundsGovernor, configGovernor, modulesGovernor, someone, recipient]) => { 15 | let recoverable, controller 16 | 17 | describe('recoverFunds', () => { 18 | let token 19 | const amount = bigExp(10, 18) 20 | 21 | beforeEach('create token and recoverable instance', async () => { 22 | token = await ERC20.new('DAI', 'DAI', 18) 23 | controller = await buildHelper().deploy({ fundsGovernor, configGovernor, modulesGovernor }) 24 | recoverable = await ControlledRecoverable.new(controller.address) 25 | }) 26 | 27 | context('when the sender is the governor', () => { 28 | const from = fundsGovernor 29 | 30 | context('when the governed has some funds', () => { 31 | beforeEach('mint some tokens', async () => { 32 | await token.generateTokens(recoverable.address, amount) 33 | }) 34 | 35 | it('transfers the requested amount to the given recipient', async () => { 36 | const previousGovernorBalance = await token.balanceOf(configGovernor) 37 | const previousGovernedBalance = await token.balanceOf(recoverable.address) 38 | const previousRecipientBalance = await token.balanceOf(recipient) 39 | 40 | await recoverable.recoverFunds(token.address, recipient, { from }) 41 | 42 | const currentGovernorBalance = await token.balanceOf(configGovernor) 43 | assertBn(previousGovernorBalance, currentGovernorBalance, 'governor balances do not match') 44 | 45 | const currentGovernedBalance = await token.balanceOf(recoverable.address) 46 | assertBn(previousGovernedBalance.sub(amount), currentGovernedBalance, 'governed balances do not match') 47 | 48 | const currentRecipientBalance = await token.balanceOf(recipient) 49 | assertBn(previousRecipientBalance.add(amount), currentRecipientBalance, 'recipient balances do not match') 50 | }) 51 | 52 | it('emits an event', async () => { 53 | const receipt = await recoverable.recoverFunds(token.address, recipient, { from }) 54 | 55 | assertAmountOfEvents(receipt, CONTROLLED_EVENTS.RECOVER_FUNDS) 56 | assertEvent(receipt, CONTROLLED_EVENTS.RECOVER_FUNDS, { token: token.address, recipient, balance: amount }) 57 | }) 58 | }) 59 | 60 | context('when the governed does not have funds', () => { 61 | it('reverts', async () => { 62 | await assertRevert(recoverable.recoverFunds(token.address, recipient, { from }), CONTROLLED_ERRORS.INSUFFICIENT_RECOVER_FUNDS) 63 | }) 64 | }) 65 | }) 66 | }) 67 | 68 | context('when the sender is not the governor', () => { 69 | const from = someone 70 | 71 | it('reverts', async () => { 72 | await assertRevert(recoverable.recoverFunds(ZERO_ADDRESS, recipient, { from }), CONTROLLED_ERRORS.SENDER_NOT_FUNDS_GOVERNOR) 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /contracts/lib/os/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | // Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/common/SafeERC20.sol 2 | // Adapted to use pragma ^0.5.8 and satisfy our linter rules 3 | 4 | pragma solidity ^0.5.8; 5 | 6 | import "./ERC20.sol"; 7 | 8 | 9 | library SafeERC20 { 10 | // Before 0.5, solidity has a mismatch between `address.transfer()` and `token.transfer()`: 11 | // https://github.com/ethereum/solidity/issues/3544 12 | bytes4 private constant TRANSFER_SELECTOR = 0xa9059cbb; 13 | 14 | /** 15 | * @dev Same as a standards-compliant ERC20.transfer() that never reverts (returns false). 16 | * Note that this makes an external call to the token. 17 | */ 18 | function safeTransfer(ERC20 _token, address _to, uint256 _amount) internal returns (bool) { 19 | bytes memory transferCallData = abi.encodeWithSelector( 20 | TRANSFER_SELECTOR, 21 | _to, 22 | _amount 23 | ); 24 | return invokeAndCheckSuccess(address(_token), transferCallData); 25 | } 26 | 27 | /** 28 | * @dev Same as a standards-compliant ERC20.transferFrom() that never reverts (returns false). 29 | * Note that this makes an external call to the token. 30 | */ 31 | function safeTransferFrom(ERC20 _token, address _from, address _to, uint256 _amount) internal returns (bool) { 32 | bytes memory transferFromCallData = abi.encodeWithSelector( 33 | _token.transferFrom.selector, 34 | _from, 35 | _to, 36 | _amount 37 | ); 38 | return invokeAndCheckSuccess(address(_token), transferFromCallData); 39 | } 40 | 41 | /** 42 | * @dev Same as a standards-compliant ERC20.approve() that never reverts (returns false). 43 | * Note that this makes an external call to the token. 44 | */ 45 | function safeApprove(ERC20 _token, address _spender, uint256 _amount) internal returns (bool) { 46 | bytes memory approveCallData = abi.encodeWithSelector( 47 | _token.approve.selector, 48 | _spender, 49 | _amount 50 | ); 51 | return invokeAndCheckSuccess(address(_token), approveCallData); 52 | } 53 | 54 | function invokeAndCheckSuccess(address _addr, bytes memory _calldata) private returns (bool) { 55 | bool ret; 56 | assembly { 57 | let ptr := mload(0x40) // free memory pointer 58 | 59 | let success := call( 60 | gas, // forward all gas 61 | _addr, // address 62 | 0, // no value 63 | add(_calldata, 0x20), // calldata start 64 | mload(_calldata), // calldata length 65 | ptr, // write output over free memory 66 | 0x20 // uint256 return 67 | ) 68 | 69 | if gt(success, 0) { 70 | // Check number of bytes returned from last function call 71 | switch returndatasize 72 | 73 | // No bytes returned: assume success 74 | case 0 { 75 | ret := 1 76 | } 77 | 78 | // 32 bytes returned: check if non-zero 79 | case 0x20 { 80 | // Only return success if returned data was true 81 | // Already have output in ptr 82 | ret := eq(mload(ptr), 1) 83 | } 84 | 85 | // Not sure what was returned: don't mark as success 86 | default { } 87 | } 88 | } 89 | return ret; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /contracts/test/court/AragonCourtMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../court/AragonCourt.sol"; 4 | import "../lib/TimeHelpersMock.sol"; 5 | 6 | 7 | contract AragonCourtMock is AragonCourt, TimeHelpersMock { 8 | uint64 internal mockedTermId; 9 | bytes32 internal mockedTermRandomness; 10 | 11 | constructor( 12 | uint64[2] memory _termParams, 13 | address[3] memory _governors, 14 | ERC20 _feeToken, 15 | uint256[3] memory _fees, 16 | uint64[5] memory _roundStateDurations, 17 | uint16[2] memory _pcts, 18 | uint64[4] memory _roundParams, 19 | uint256[2] memory _appealCollateralParams, 20 | uint256 _minActiveBalance 21 | ) 22 | AragonCourt( 23 | _termParams, 24 | _governors, 25 | _feeToken, 26 | _fees, 27 | _roundStateDurations, 28 | _pcts, 29 | _roundParams, 30 | _appealCollateralParams, 31 | _minActiveBalance 32 | ) 33 | public 34 | {} 35 | 36 | function setDisputeManager(address _addr) external { 37 | _setModule(DISPUTE_MANAGER, _addr); 38 | } 39 | 40 | function setDisputeManagerMock(address _addr) external { 41 | // This function allows setting any address as the DisputeManager module 42 | modules[DISPUTE_MANAGER] = _addr; 43 | emit ModuleSet(DISPUTE_MANAGER, _addr); 44 | } 45 | 46 | function setTreasury(address _addr) external { 47 | _setModule(TREASURY, _addr); 48 | } 49 | 50 | function setVoting(address _addr) external { 51 | _setModule(VOTING, _addr); 52 | } 53 | 54 | function setJurorsRegistry(address _addr) external { 55 | _setModule(JURORS_REGISTRY, _addr); 56 | } 57 | 58 | function setSubscriptions(address _addr) external { 59 | _setModule(SUBSCRIPTIONS, _addr); 60 | } 61 | 62 | function mockIncreaseTerm() external { 63 | if (mockedTermId != 0) mockedTermId = mockedTermId + 1; 64 | else mockedTermId = _lastEnsuredTermId() + 1; 65 | } 66 | 67 | function mockIncreaseTerms(uint64 _terms) external { 68 | if (mockedTermId != 0) mockedTermId = mockedTermId + _terms; 69 | else mockedTermId = _lastEnsuredTermId() + _terms; 70 | } 71 | 72 | function mockSetTerm(uint64 _termId) external { 73 | mockedTermId = _termId; 74 | } 75 | 76 | function mockSetTermRandomness(bytes32 _termRandomness) external { 77 | mockedTermRandomness = _termRandomness; 78 | } 79 | 80 | function ensureCurrentTerm() external returns (uint64) { 81 | if (mockedTermId != 0) return mockedTermId; 82 | return super._ensureCurrentTerm(); 83 | } 84 | 85 | function getCurrentTermId() external view returns (uint64) { 86 | if (mockedTermId != 0) return mockedTermId; 87 | return super._currentTermId(); 88 | } 89 | 90 | function getLastEnsuredTermId() external view returns (uint64) { 91 | if (mockedTermId != 0) return mockedTermId; 92 | return super._lastEnsuredTermId(); 93 | } 94 | 95 | function getTermRandomness(uint64 _termId) external view returns (bytes32) { 96 | if (mockedTermRandomness != bytes32(0)) return mockedTermRandomness; 97 | return super._computeTermRandomness(_termId); 98 | } 99 | 100 | function _computeTermRandomness(uint64 _termId) internal view returns (bytes32) { 101 | if (mockedTermRandomness != bytes32(0)) return mockedTermRandomness; 102 | return super._computeTermRandomness(_termId); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /contracts/registry/IJurorsRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../lib/os/ERC20.sol"; 4 | 5 | 6 | interface IJurorsRegistry { 7 | 8 | /** 9 | * @dev Assign a requested amount of juror tokens to a juror 10 | * @param _juror Juror to add an amount of tokens to 11 | * @param _amount Amount of tokens to be added to the available balance of a juror 12 | */ 13 | function assignTokens(address _juror, uint256 _amount) external; 14 | 15 | /** 16 | * @dev Burn a requested amount of juror tokens 17 | * @param _amount Amount of tokens to be burned 18 | */ 19 | function burnTokens(uint256 _amount) external; 20 | 21 | /** 22 | * @dev Draft a set of jurors based on given requirements for a term id 23 | * @param _params Array containing draft requirements: 24 | * 0. bytes32 Term randomness 25 | * 1. uint256 Dispute id 26 | * 2. uint64 Current term id 27 | * 3. uint256 Number of seats already filled 28 | * 4. uint256 Number of seats left to be filled 29 | * 5. uint64 Number of jurors required for the draft 30 | * 6. uint16 Permyriad of the minimum active balance to be locked for the draft 31 | * 32 | * @return jurors List of jurors selected for the draft 33 | * @return length Size of the list of the draft result 34 | */ 35 | function draft(uint256[7] calldata _params) external returns (address[] memory jurors, uint256 length); 36 | 37 | /** 38 | * @dev Slash a set of jurors based on their votes compared to the winning ruling 39 | * @param _termId Current term id 40 | * @param _jurors List of juror addresses to be slashed 41 | * @param _lockedAmounts List of amounts locked for each corresponding juror that will be either slashed or returned 42 | * @param _rewardedJurors List of booleans to tell whether a juror's active balance has to be slashed or not 43 | * @return Total amount of slashed tokens 44 | */ 45 | function slashOrUnlock(uint64 _termId, address[] calldata _jurors, uint256[] calldata _lockedAmounts, bool[] calldata _rewardedJurors) 46 | external 47 | returns (uint256 collectedTokens); 48 | 49 | /** 50 | * @dev Try to collect a certain amount of tokens from a juror for the next term 51 | * @param _juror Juror to collect the tokens from 52 | * @param _amount Amount of tokens to be collected from the given juror and for the requested term id 53 | * @param _termId Current term id 54 | * @return True if the juror has enough unlocked tokens to be collected for the requested term, false otherwise 55 | */ 56 | function collectTokens(address _juror, uint256 _amount, uint64 _termId) external returns (bool); 57 | 58 | /** 59 | * @dev Lock a juror's withdrawals until a certain term ID 60 | * @param _juror Address of the juror to be locked 61 | * @param _termId Term ID until which the juror's withdrawals will be locked 62 | */ 63 | function lockWithdrawals(address _juror, uint64 _termId) external; 64 | 65 | /** 66 | * @dev Tell the active balance of a juror for a given term id 67 | * @param _juror Address of the juror querying the active balance of 68 | * @param _termId Term ID querying the active balance for 69 | * @return Amount of active tokens for juror in the requested past term id 70 | */ 71 | function activeBalanceOfAt(address _juror, uint64 _termId) external view returns (uint256); 72 | 73 | /** 74 | * @dev Tell the total amount of active juror tokens at the given term id 75 | * @param _termId Term ID querying the total active balance for 76 | * @return Total amount of active juror tokens at the given term id 77 | */ 78 | function totalActiveBalanceAt(uint64 _termId) external view returns (uint256); 79 | } 80 | -------------------------------------------------------------------------------- /test/helpers/utils/config.js: -------------------------------------------------------------------------------- 1 | const { assertBn } = require('../asserts/assertBn') 2 | const { bn, bigExp } = require('../lib/numbers') 3 | 4 | const PCT_BASE = bn(10000) 5 | 6 | module.exports = artifacts => { 7 | const buildNewConfig = async (config, iteration = 1) => { 8 | return { 9 | feeToken: await artifacts.require('ERC20Mock').new('Court Fee Token', 'CFT', 18), 10 | jurorFee: config.jurorFee.add(bigExp(iteration * 10, 18)), 11 | draftFee: config.draftFee.add(bigExp(iteration * 10, 18)), 12 | settleFee: config.settleFee.add(bigExp(iteration * 10, 18)), 13 | evidenceTerms: config.evidenceTerms.add(bn(iteration)), 14 | commitTerms: config.commitTerms.add(bn(iteration)), 15 | revealTerms: config.revealTerms.add(bn(iteration)), 16 | appealTerms: config.appealTerms.add(bn(iteration)), 17 | appealConfirmTerms: config.appealConfirmTerms.add(bn(iteration)), 18 | penaltyPct: config.penaltyPct.add(bn(iteration * 100)), 19 | finalRoundReduction: config.finalRoundReduction.add(bn(iteration * 100)), 20 | firstRoundJurorsNumber: config.firstRoundJurorsNumber.add(bn(iteration)), 21 | appealStepFactor: config.appealStepFactor.add(bn(iteration)), 22 | maxRegularAppealRounds: config.maxRegularAppealRounds.add(bn(iteration)), 23 | finalRoundLockTerms: config.finalRoundLockTerms.add(bn(1)), 24 | appealCollateralFactor: config.appealCollateralFactor.add(bn(iteration * PCT_BASE)), 25 | appealConfirmCollateralFactor: config.appealConfirmCollateralFactor.add(bn(iteration * PCT_BASE)), 26 | minActiveBalance: config.minActiveBalance.add(bigExp(iteration * 100, 18)) 27 | } 28 | } 29 | 30 | const assertConfig = async (actualConfig, expectedConfig) => { 31 | assert.equal(actualConfig.feeToken.address, expectedConfig.feeToken.address, 'fee token does not match') 32 | assertBn(actualConfig.jurorFee, expectedConfig.jurorFee, 'juror fee does not match') 33 | assertBn(actualConfig.draftFee, expectedConfig.draftFee, 'draft fee does not match') 34 | assertBn(actualConfig.settleFee, expectedConfig.settleFee, 'settle fee does not match') 35 | assertBn(actualConfig.commitTerms, expectedConfig.commitTerms, 'commit terms number does not match') 36 | assertBn(actualConfig.revealTerms, expectedConfig.revealTerms, 'reveal terms number does not match') 37 | assertBn(actualConfig.appealTerms, expectedConfig.appealTerms, 'appeal terms number does not match') 38 | assertBn(actualConfig.appealConfirmTerms, expectedConfig.appealConfirmTerms, 'appeal confirmation terms number does not match') 39 | assertBn(actualConfig.penaltyPct, expectedConfig.penaltyPct, 'penalty permyriad does not match') 40 | assertBn(actualConfig.finalRoundReduction, expectedConfig.finalRoundReduction, 'final round reduction does not match') 41 | assertBn(actualConfig.firstRoundJurorsNumber, expectedConfig.firstRoundJurorsNumber, 'first round jurors number does not match') 42 | assertBn(actualConfig.appealStepFactor, expectedConfig.appealStepFactor, 'appeal step factor does not match') 43 | assertBn(actualConfig.maxRegularAppealRounds, expectedConfig.maxRegularAppealRounds, 'number of max regular appeal rounds does not match') 44 | assertBn(actualConfig.finalRoundLockTerms, expectedConfig.finalRoundLockTerms, 'number of final round lock terms does not match') 45 | assertBn(actualConfig.appealCollateralFactor, expectedConfig.appealCollateralFactor, 'appeal collateral factor does not match') 46 | assertBn(actualConfig.appealConfirmCollateralFactor, expectedConfig.appealConfirmCollateralFactor, 'appeal confirmation collateral factor does not match') 47 | assertBn(actualConfig.minActiveBalance, expectedConfig.minActiveBalance, 'min active balance does not match') 48 | } 49 | 50 | return { 51 | buildNewConfig, 52 | assertConfig 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/court/config/IConfig.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/os/ERC20.sol"; 4 | 5 | 6 | interface IConfig { 7 | 8 | /** 9 | * @dev Tell the full Court configuration parameters at a certain term 10 | * @param _termId Identification number of the term querying the Court config of 11 | * @return token Address of the token used to pay for fees 12 | * @return fees Array containing: 13 | * 0. jurorFee Amount of fee tokens that is paid per juror per dispute 14 | * 1. draftFee Amount of fee tokens per juror to cover the drafting cost 15 | * 2. settleFee Amount of fee tokens per juror to cover round settlement cost 16 | * @return roundStateDurations Array containing the durations in terms of the different phases of a dispute: 17 | * 0. evidenceTerms Max submitting evidence period duration in terms 18 | * 1. commitTerms Commit period duration in terms 19 | * 2. revealTerms Reveal period duration in terms 20 | * 3. appealTerms Appeal period duration in terms 21 | * 4. appealConfirmationTerms Appeal confirmation period duration in terms 22 | * @return pcts Array containing: 23 | * 0. penaltyPct Permyriad of min active tokens balance to be locked for each drafted juror (‱ - 1/10,000) 24 | * 1. finalRoundReduction Permyriad of fee reduction for the last appeal round (‱ - 1/10,000) 25 | * @return roundParams Array containing params for rounds: 26 | * 0. firstRoundJurorsNumber Number of jurors to be drafted for the first round of disputes 27 | * 1. appealStepFactor Increasing factor for the number of jurors of each round of a dispute 28 | * 2. maxRegularAppealRounds Number of regular appeal rounds before the final round is triggered 29 | * @return appealCollateralParams Array containing params for appeal collateral: 30 | * 0. appealCollateralFactor Multiple of dispute fees required to appeal a preliminary ruling 31 | * 1. appealConfirmCollateralFactor Multiple of dispute fees required to confirm appeal 32 | * @return minActiveBalance Minimum amount of tokens jurors have to activate to participate in the Court 33 | */ 34 | function getConfig(uint64 _termId) external view 35 | returns ( 36 | ERC20 feeToken, 37 | uint256[3] memory fees, 38 | uint64[5] memory roundStateDurations, 39 | uint16[2] memory pcts, 40 | uint64[4] memory roundParams, 41 | uint256[2] memory appealCollateralParams, 42 | uint256 minActiveBalance 43 | ); 44 | 45 | /** 46 | * @dev Tell the draft config at a certain term 47 | * @param _termId Identification number of the term querying the draft config of 48 | * @return feeToken Address of the token used to pay for fees 49 | * @return draftFee Amount of fee tokens per juror to cover the drafting cost 50 | * @return penaltyPct Permyriad of min active tokens balance to be locked for each drafted juror (‱ - 1/10,000) 51 | */ 52 | function getDraftConfig(uint64 _termId) external view returns (ERC20 feeToken, uint256 draftFee, uint16 penaltyPct); 53 | 54 | /** 55 | * @dev Tell the min active balance config at a certain term 56 | * @param _termId Term querying the min active balance config of 57 | * @return Minimum amount of tokens jurors have to activate to participate in the Court 58 | */ 59 | function getMinActiveBalance(uint64 _termId) external view returns (uint256); 60 | 61 | /** 62 | * @dev Tell whether a certain holder accepts automatic withdrawals of tokens or not 63 | * @return True if the given holder accepts automatic withdrawals of their tokens, false otherwise 64 | */ 65 | function areWithdrawalsAllowedFor(address _holder) external view returns (bool); 66 | } 67 | -------------------------------------------------------------------------------- /test/registry/jurors-registry-setters.js: -------------------------------------------------------------------------------- 1 | const { assertBn } = require('../helpers/asserts/assertBn') 2 | const { bn, bigExp } = require('../helpers/lib/numbers') 3 | const { buildHelper } = require('../helpers/wrappers/court')(web3, artifacts) 4 | const { assertRevert } = require('../helpers/asserts/assertThrow') 5 | const { REGISTRY_EVENTS } = require('../helpers/utils/events') 6 | const { assertEvent, assertAmountOfEvents } = require('../helpers/asserts/assertEvent') 7 | const { CONTROLLED_ERRORS, REGISTRY_ERRORS } = require('../helpers/utils/errors') 8 | 9 | const JurorsRegistry = artifacts.require('JurorsRegistry') 10 | const ERC20 = artifacts.require('ERC20Mock') 11 | 12 | contract('JurorsRegistry', ([_, governor, someone]) => { 13 | let controller, registry, ANJ 14 | 15 | const MIN_ACTIVE_BALANCE = bigExp(100, 18) 16 | const TOTAL_ACTIVE_BALANCE_LIMIT = bigExp(100e6, 18) 17 | 18 | before('create base contracts', async () => { 19 | controller = await buildHelper().deploy({ configGovernor: governor, minActiveBalance: MIN_ACTIVE_BALANCE }) 20 | ANJ = await ERC20.new('ANJ Token', 'ANJ', 18) 21 | }) 22 | 23 | beforeEach('create jurors registry module', async () => { 24 | registry = await JurorsRegistry.new(controller.address, ANJ.address, TOTAL_ACTIVE_BALANCE_LIMIT) 25 | await controller.setJurorsRegistry(registry.address) 26 | }) 27 | 28 | describe('setTotalActiveBalanceLimit', () => { 29 | context('when the sender is the governor', () => { 30 | const from = governor 31 | 32 | context('when the given limit is greater than zero', () => { 33 | const itUpdatesTheTotalActiveBalanceLimit = newTotalActiveBalanceLimit => { 34 | it('updates the current total active balance limit', async () => { 35 | await registry.setTotalActiveBalanceLimit(newTotalActiveBalanceLimit, { from }) 36 | 37 | const currentTotalActiveBalanceLimit = await registry.totalJurorsActiveBalanceLimit() 38 | assertBn(currentTotalActiveBalanceLimit, newTotalActiveBalanceLimit, 'total active balance limit does not match') 39 | }) 40 | 41 | it('emits an event', async () => { 42 | const previousTotalActiveBalanceLimit = await registry.totalJurorsActiveBalanceLimit() 43 | 44 | const receipt = await registry.setTotalActiveBalanceLimit(newTotalActiveBalanceLimit, { from }) 45 | 46 | assertAmountOfEvents(receipt, REGISTRY_EVENTS.TOTAL_ACTIVE_BALANCE_LIMIT_CHANGED) 47 | assertEvent(receipt, REGISTRY_EVENTS.TOTAL_ACTIVE_BALANCE_LIMIT_CHANGED, { previousTotalActiveBalanceLimit, currentTotalActiveBalanceLimit: newTotalActiveBalanceLimit }) 48 | }) 49 | } 50 | 51 | context('when the given limit is below the minimum active balance', () => { 52 | const newTotalActiveBalanceLimit = MIN_ACTIVE_BALANCE.sub(bn(1)) 53 | 54 | itUpdatesTheTotalActiveBalanceLimit(newTotalActiveBalanceLimit) 55 | }) 56 | 57 | context('when the given limit is above the minimum active balance', () => { 58 | const newTotalActiveBalanceLimit = MIN_ACTIVE_BALANCE.add(bn(1)) 59 | 60 | itUpdatesTheTotalActiveBalanceLimit(newTotalActiveBalanceLimit) 61 | }) 62 | }) 63 | 64 | context('when the given limit is zero', () => { 65 | const newTotalActiveBalanceLimit = bn(0) 66 | 67 | it('reverts', async () => { 68 | await assertRevert(registry.setTotalActiveBalanceLimit(newTotalActiveBalanceLimit, { from }), REGISTRY_ERRORS.BAD_TOTAL_ACTIVE_BALANCE_LIMIT) 69 | }) 70 | }) 71 | }) 72 | 73 | context('when the sender is not the governor', () => { 74 | const from = someone 75 | 76 | it('reverts', async () => { 77 | await assertRevert(registry.setTotalActiveBalanceLimit(TOTAL_ACTIVE_BALANCE_LIMIT, { from }), CONTROLLED_ERRORS.SENDER_NOT_CONFIG_GOVERNOR) 78 | }) 79 | }) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![Aragon Court](./docs/aragon-court.png) 2 | 3 | 4 | 5 | 6 | 7 | ## Project 8 | 9 | #### 👩‍️ [Become an Aragon Court juror](https://anj.aragon.org) 10 | Aragon Court is now live on Ethereum mainnet. You can become a juror by staking 10,000 ANJ. 11 | 12 | #### ⚖ [Check out the Aragon Court Dashboard](https://court.aragon.org) 13 | The Aragon Court Dashboard is the central app where all dispute-related information is available for jurors. 14 | 15 | #### 📚 [Read the User Guide](https://help.aragon.org/category/47-aragoncourt) 16 | Read the user guide if you have any doubts about the Aragon Court protocol, Court Dashboard, or related tools. 17 | 18 | ## Protocol 19 | 20 | #### 📓 [Read the full documentation](/docs) 21 | Aragon Court is a dispute resolution protocol that runs on Ethereum. It's one of the core components of the [Aragon Network](https://aragon.org/network/). 22 | 23 | #### 🚧 Project stage: v1 implementation 24 | After a long research and development phase, Aragon Court's v1 implementation has been [released](https://www.npmjs.com/package/@aragon/court) and [deployed](https://etherscan.io/address/0xee4650cBe7a2B23701D416f58b41D8B76b617797#code). 25 | 26 | #### ✅ Security review status: audited 27 | Aragon Court v1 has already been audited by an independent security professional. You can read the audit report [here](https://github.com/gakonst/publications/blob/master/aragon_court_audit.pdf). 28 | 29 | #### 👋 Get started contributing with a [good first issue](https://github.com/aragon/aragon-court/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) 30 | Don't be shy to contribute even the smallest tweak. Everyone will be especially nice and helpful to beginners to help you get started! 31 | 32 | ## How does it work 33 | 34 | **Full description of the mechanism: [Mechanism documentation](/docs/1-mechanism)** 35 | 36 | Aragon Court handles subjective disputes that cannot be solved by smart contracts. For this, it employs jurors that need to stake a token to the Court which allows them to get drafted to adjudicate disputes, that can earn them fees. The more tokens a juror has activated, the higher the chance to get drafted and earn more fees. 37 | 38 | Aragon Court attempts to find what the subjective truth is with a [Schelling game](https://en.wikipedia.org/wiki/Focal_point_(game_theory)). Jurors are asked to vote on the ruling that they think their fellow jurors are more likely to vote on. To incentivize consensus, jurors that don't vote on the consensus ruling have some tokens slashed. Jurors that vote with the consensus ruling are rewarded with ruling fees and juror tokens from the jurors that voted for a minority ruling. 39 | 40 | A design goal of the mechanism is to require very few jurors to adjudicate a dispute and produce a ruling. A small number of jurors is adjudicated by default to a dispute, and their ruling can be appealed in multiple rounds of appeals. 41 | 42 | Even though Aragon Court could theoretically resolve any type of binary dispute, in its first deployments it will be used to arbitrate **Proposal Agreements.** These agreements require entities creating a proposal in an organization to agree to its specific rules around proposal creation, putting some collateral at stake that could be lost if the Court finds the proposal invalid. 43 | 44 | ## Deployed instances 45 | 46 | #### Mainnet 47 | 48 | The mainnet instance of Aragon Court is deployed at [`0xee4650cBe7a2B23701D416f58b41D8B76b617797`](https://etherscan.io/address/0xee4650cBe7a2B23701D416f58b41D8B76b617797#code) 49 | 50 | #### Testing 51 | 52 | There are a few testing instances deployed of Aragon Court, please refer to the [testing guide](/docs/8-testing-guide) to have a better understanding on using these. 53 | 54 | ## Help shape Aragon Court 55 | - Discuss in [Aragon Forum](https://forum.aragon.org/tags/dispute-resolution) 56 | - Join the [Aragon Court channel](https://discord.gg/nxMejdG) on Discord. 57 | -------------------------------------------------------------------------------- /test/helpers/utils/registry.js: -------------------------------------------------------------------------------- 1 | const { bn } = require('../lib/numbers') 2 | const { soliditySha3, toBN } = require('web3-utils') 3 | 4 | const expectedBounds = ({ selectedJurors, batchRequestedJurors, balances, totalRequestedJurors }) => { 5 | const totalBalance = balances.reduce((total, balance) => total.add(balance), bn(0)) 6 | 7 | const expectedLowBound = bn(selectedJurors).mul(bn(totalBalance)).div(bn(totalRequestedJurors)) 8 | const expectedHighBound = bn(selectedJurors).add(bn(batchRequestedJurors)).mul(bn(totalBalance)).div(bn(totalRequestedJurors)) 9 | return { expectedLowBound, expectedHighBound } 10 | } 11 | 12 | const simulateComputeSearchRandomBalances = ({ 13 | termRandomness, 14 | disputeId, 15 | sortitionIteration, 16 | batchRequestedJurors, 17 | lowActiveBalanceBatchBound, 18 | highActiveBalanceBatchBound 19 | }) => { 20 | let expectedSumTreeBalances = [] 21 | const interval = highActiveBalanceBatchBound.sub(lowActiveBalanceBatchBound) 22 | for (let i = 0; i < batchRequestedJurors; i++) { 23 | if (interval.eq(bn(0))) expectedSumTreeBalances.push(lowActiveBalanceBatchBound) 24 | else { 25 | const seed = soliditySha3(termRandomness, disputeId, sortitionIteration, i) 26 | const balance = bn(lowActiveBalanceBatchBound).add(toBN(seed).mod(interval)) 27 | expectedSumTreeBalances.push(balance) 28 | } 29 | } 30 | 31 | return expectedSumTreeBalances.sort((x, y) => x.lt(y) ? -1 : 1) 32 | } 33 | 34 | const simulateBatchedRandomSearch = ({ 35 | termRandomness, 36 | disputeId, 37 | selectedJurors, 38 | batchRequestedJurors, 39 | roundRequestedJurors, 40 | sortitionIteration, 41 | balances, 42 | getTreeKey 43 | }) => { 44 | const { expectedLowBound, expectedHighBound } = expectedBounds({ 45 | selectedJurors, 46 | batchRequestedJurors, 47 | balances, 48 | totalRequestedJurors: roundRequestedJurors 49 | }) 50 | 51 | const expectedSumTreeBalances = simulateComputeSearchRandomBalances({ 52 | termRandomness, 53 | disputeId, 54 | sortitionIteration, 55 | batchRequestedJurors, 56 | lowActiveBalanceBatchBound: expectedLowBound, 57 | highActiveBalanceBatchBound: expectedHighBound 58 | }) 59 | 60 | // as jurors balances are sequential 0 to n, ids and values are the same 61 | return expectedSumTreeBalances 62 | .map(balance => getTreeKey(balances, balance)) 63 | .filter(key => key !== undefined) 64 | } 65 | 66 | const simulateDraft = ({ 67 | termRandomness, 68 | disputeId, 69 | selectedJurors, 70 | batchRequestedJurors, 71 | roundRequestedJurors, 72 | sortitionIteration, 73 | jurors, 74 | draftLockAmount, 75 | getTreeKey 76 | }) => { 77 | const balances = jurors.map(juror => juror.activeBalance) 78 | 79 | const MAX_ITERATIONS = 10 80 | let draftedKeys = [] 81 | let iteration = sortitionIteration 82 | let jurorsLeft = batchRequestedJurors 83 | 84 | while (jurorsLeft > 0 && iteration < MAX_ITERATIONS) { 85 | const iterationDraftedKeys = simulateBatchedRandomSearch({ 86 | termRandomness, 87 | disputeId, 88 | selectedJurors, 89 | batchRequestedJurors, 90 | roundRequestedJurors, 91 | sortitionIteration: iteration, 92 | balances, 93 | getTreeKey 94 | }) 95 | 96 | // remove locked jurors 97 | const filteredIterationDraftedKeys = iterationDraftedKeys 98 | .filter(key => { 99 | const { unlockedActiveBalance } = jurors[key] 100 | const enoughBalance = unlockedActiveBalance.gte(draftLockAmount) 101 | if (enoughBalance) jurors[key].unlockedActiveBalance = unlockedActiveBalance.sub(draftLockAmount) 102 | return enoughBalance 103 | }) 104 | .slice(0, jurorsLeft) 105 | 106 | iteration++ 107 | jurorsLeft -= filteredIterationDraftedKeys.length 108 | draftedKeys = draftedKeys.concat(filteredIterationDraftedKeys) 109 | } 110 | 111 | return draftedKeys.map(key => jurors[key].address) 112 | } 113 | 114 | module.exports = { 115 | expectedBounds, 116 | simulateComputeSearchRandomBalances, 117 | simulateBatchedRandomSearch, 118 | simulateDraft 119 | } 120 | -------------------------------------------------------------------------------- /test/subscriptions/subscriptions-donations.js: -------------------------------------------------------------------------------- 1 | const { assertBn } = require('../helpers/asserts/assertBn') 2 | const { bn, bigExp } = require('../helpers/lib/numbers') 3 | const { buildHelper } = require('../helpers/wrappers/court')(web3, artifacts) 4 | const { assertRevert } = require('../helpers/asserts/assertThrow') 5 | const { assertAmountOfEvents } = require('../helpers/asserts/assertEvent') 6 | const { SUBSCRIPTIONS_ERRORS } = require('../helpers/utils/errors') 7 | const { SUBSCRIPTIONS_EVENTS } = require('../helpers/utils/events') 8 | 9 | const CourtSubscriptions = artifacts.require('CourtSubscriptions') 10 | const ERC20 = artifacts.require('ERC20Mock') 11 | 12 | contract('CourtSubscriptions', ([_, payer]) => { 13 | let controller, subscriptions, feeToken 14 | 15 | const FEE_AMOUNT = bigExp(10, 18) 16 | const PREPAYMENT_PERIODS = 5 17 | const RESUME_PRE_PAID_PERIODS = 1 18 | const PERIOD_DURATION = 24 * 30 // 30 days, assuming terms are 1h 19 | const GOVERNOR_SHARE_PCT = bn(100) // 100‱ = 1% 20 | const LATE_PAYMENT_PENALTY_PCT = bn(1000) // 1000‱ = 10% 21 | 22 | before('create base contracts', async () => { 23 | controller = await buildHelper().deploy() 24 | feeToken = await ERC20.new('Subscriptions Fee Token', 'SFT', 18) 25 | }) 26 | 27 | beforeEach('create subscriptions module', async () => { 28 | subscriptions = await CourtSubscriptions.new(controller.address, PERIOD_DURATION, feeToken.address, FEE_AMOUNT, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT) 29 | await controller.setSubscriptions(subscriptions.address) 30 | }) 31 | 32 | describe('donate', () => { 33 | context('when the amount is greater than zero', () => { 34 | const amount = bn(10) 35 | 36 | context('when the court has not started yet', () => { 37 | it('reverts', async () => { 38 | await assertRevert(subscriptions.donate(amount, { from: payer }), SUBSCRIPTIONS_ERRORS.COURT_HAS_NOT_STARTED) 39 | }) 40 | }) 41 | 42 | context('when the court has already started', () => { 43 | beforeEach('move terms to reach period #0', async () => { 44 | await controller.mockSetTerm(PERIOD_DURATION) 45 | }) 46 | 47 | context('when the sender has enough balance', () => { 48 | const from = payer 49 | 50 | beforeEach('mint fee tokens', async () => { 51 | const balance = FEE_AMOUNT.mul(bn(10000)) 52 | await feeToken.generateTokens(from, balance) 53 | await feeToken.approve(subscriptions.address, balance, { from }) 54 | }) 55 | 56 | it('pays the requested periods subscriptions', async () => { 57 | const previousPayerBalance = await feeToken.balanceOf(from) 58 | const previousSubscriptionsBalance = await feeToken.balanceOf(subscriptions.address) 59 | 60 | const { collectedFees } = await subscriptions.getCurrentPeriod() 61 | 62 | const receipt = await subscriptions.donate(amount, { from }) 63 | assertAmountOfEvents(receipt, SUBSCRIPTIONS_EVENTS.FEES_DONATED) 64 | 65 | const currentSubscriptionsBalance = await feeToken.balanceOf(subscriptions.address) 66 | assertBn(currentSubscriptionsBalance, previousSubscriptionsBalance.add(amount), 'subscriptions balances do not match') 67 | 68 | const currentPayerBalance = await feeToken.balanceOf(from) 69 | assertBn(currentPayerBalance, previousPayerBalance.sub(amount), 'payer balances do not match') 70 | 71 | const { collectedFees: newCollectedFees } = await subscriptions.getCurrentPeriod() 72 | assertBn(newCollectedFees, collectedFees.add(amount), 'period collected fees do not match') 73 | }) 74 | }) 75 | 76 | context('when the sender does not have enough balance', () => { 77 | it('reverts', async () => { 78 | await assertRevert(subscriptions.donate(1), SUBSCRIPTIONS_ERRORS.TOKEN_TRANSFER_FAILED) 79 | }) 80 | }) 81 | }) 82 | }) 83 | 84 | context('when the amount is zero', () => { 85 | const amount = bn(0) 86 | 87 | it('reverts', async () => { 88 | await assertRevert(subscriptions.donate(amount), SUBSCRIPTIONS_ERRORS.DONATION_AMOUNT_ZERO) 89 | }) 90 | }) 91 | }) 92 | }) 93 | -------------------------------------------------------------------------------- /docs/6-external-interface/5-voting.md: -------------------------------------------------------------------------------- 1 | ## 6.5. Voting 2 | 3 | ### 6.5.1 Events 4 | 5 | The following events are emitted by the `Voting`: 6 | 7 | #### 6.5.1.1. Voting created 8 | 9 | - **Name:** `VotingCreated` 10 | - **Args:** 11 | - **Vote ID:** Identification number of the new vote instance that has been created 12 | - **Possible outcomes:** Number of possible outcomes of the new vote instance that has been created 13 | 14 | #### 6.5.1.2. Vote committed 15 | 16 | - **Name:** `VoteCommitted` 17 | - **Args:** 18 | - **Vote ID:** Identification number of the vote instance where a vote has been committed 19 | - **Voter:** Address of the voter that has committed the vote 20 | - **Commitment:** Hashed outcome of the committed vote 21 | 22 | #### 6.5.1.3. Vote revealed 23 | 24 | - **Name:** `VoteRevealed` 25 | - **Args:** 26 | - **Vote ID:** Identification number of the vote instance where a vote has been revealed 27 | - **Voter:** Address of the voter whose vote has been revealed 28 | - **Outcome:** Outcome of the vote that has been revealed 29 | - **Revealer:** Address of the account that has revealed the vote 30 | 31 | #### 6.5.1.4. Vote leaked 32 | 33 | - **Name:** `VoteLeaked` 34 | - **Args:** 35 | - **Vote ID:** Identification number of the vote instance where a vote has been leaked 36 | - **Voter:** Address of the voter whose vote has been leaked 37 | - **Outcome:** Outcome of the vote that has been leaked 38 | - **Leaker:** Address of the account that has leaked the vote 39 | 40 | ### 6.5.2. Getters 41 | 42 | The following functions are state getters provided by the `Voting`: 43 | 44 | #### 6.5.2.1. Max allowed outcome 45 | 46 | - **Inputs:** 47 | - **Vote ID:** Vote identification number 48 | - **Pre-flight checks:** 49 | - Ensure a vote object with that ID exists 50 | - **Outputs:** 51 | - **Max outcome:** Max allowed outcome for the given vote instance 52 | 53 | #### 6.5.2.2. Winning outcome 54 | 55 | - **Inputs:** 56 | - **Vote ID:** Vote identification number 57 | - **Pre-flight checks:** 58 | - Ensure a vote object with that ID exists 59 | - **Outputs:** 60 | - **Winning outcome:** Winning outcome of the given vote instance or refused in case it's missing 61 | 62 | #### 6.5.2.3. Outcome tally 63 | 64 | - **Inputs:** 65 | - **Vote ID:** Vote identification number 66 | - **Outcome:** Outcome querying the tally of 67 | - **Pre-flight checks:** 68 | - Ensure a vote object with that ID exists 69 | - **Outputs:** 70 | - **Tally:** Tally of the outcome being queried for the given vote instance 71 | 72 | #### 6.5.2.4. Is valid outcome 73 | 74 | - **Inputs:** 75 | - **Vote ID:** Vote identification number 76 | - **Outcome:** Outcome to check if valid or not 77 | - **Pre-flight checks:** 78 | - Ensure a vote object with that ID exists 79 | - **Outputs:** 80 | - **Valid:** True if the given outcome is valid for the requested vote instance, false otherwise 81 | 82 | #### 6.5.2.5. Voter outcome 83 | 84 | - **Inputs:** 85 | - **Vote ID:** Vote identification number querying the outcome of 86 | - **Voter:** Address of the voter querying the outcome of 87 | - **Pre-flight checks:** 88 | - Ensure a vote object with that ID exists 89 | - **Outputs:** 90 | - **Outcome:** Outcome of the voter for the given vote instance 91 | 92 | #### 6.5.2.6. Has voted in favor of 93 | 94 | - **Inputs:** 95 | - **Vote ID:** Vote identification number querying if a voter voted in favor of a certain outcome 96 | - **Outcome:** Outcome to query if the given voter voted in favor of 97 | - **Voter:** Address of the voter to query if voted in favor of the given outcome 98 | - **Pre-flight checks:** 99 | - Ensure a vote object with that ID exists 100 | - **Outputs:** 101 | - **In favor:** True if the given voter voted in favor of the given outcome, false otherwise 102 | 103 | #### 6.5.2.7. Voters in favor of 104 | 105 | - **Inputs:** 106 | - **Vote ID:** Vote identification number querying if a voter voted in favor of a certain outcome 107 | - **Outcome:** Outcome to query if the given voter voted in favor of 108 | - **Voters:** List of addresses of the voters to be filtered 109 | - **Pre-flight checks:** 110 | - Ensure a vote object with that ID exists 111 | - **Outputs:** 112 | - **In favor:** List of results to tell whether a voter voted in favor of the given outcome or not 113 | -------------------------------------------------------------------------------- /docs/1-mechanism/readme.md: -------------------------------------------------------------------------------- 1 | # 1. Mechanism 2 | 3 | ### 1.1. Description 4 | 5 | The Aragon Court is a dispute resolution protocol designed to handle subjective disputes which cannot be arbitrated by smart contracts. At a high level, this is achieved by drafting a random set of jurors for each dispute over which a ruling is voted over. Aragon Court is one of the core components of the [Aragon Network](https://aragon.org/network/). 6 | 7 | A separate ANJ token will be created for the canonical Aragon Court, and Ethereum accounts (including contracts) will sign up to be jurors by staking this ANJ token to the Court. The more tokens a juror has staked and activated, the higher their chance of being drafted. ANJ will be directly convertable to ANT via a bonding curve, through a [Aragon Fundraising](https://blog.aragon.org/introducing-aragon-fundraising/) organization deployed alongside the Court. 8 | 9 | Based on the concept of a [Schelling point](https://en.wikipedia.org/wiki/Focal_point_(game_theory)), jurors are asked to vote on the ruling they think their fellow jurors are most likely to vote on. Every time a juror is drafted for a dispute, a portion of their staked (TODO: should this be activated?) tokens are locked until the dispute is finalized. To incentivize consensus, jurors that don’t vote in favor of the consensus ruling have their locked tokens slashed. Jurors that vote in favor of the consensus ruling are rewarded with ruling fees and a portion of the tokens slashed from the minority-voting jurors. 10 | 11 | Once a ruling has been decided for a dispute, there is a time period where anyone is allowed to appeal said ruling by putting some collateral at stake to initiate a new dispute round. If this occurs, a new set of jurors will be drafted and a new ruling will be voted on. Rulings can be appealed multiple times until the final round is reached. To mitigate 51% attacks, all active juror accounts can opt into voting during the final round. For future versions of the Court, we are considering using [futarchy decision markets](https://blog.aragon.one/futarchy-courts/) for the final dispute round instead. 12 | 13 | The Court uses an inherent time unit, called a "term," to determine how long certain actions or phases last. The length of a "term" is guaranteed to be constant, although each phase in a dispute can have its length configured by setting how many "terms" the phase will last for. Terms are advanced via "heartbeat" transactions and most functionality will only execute if the Court has been updated to its latest term. 14 | 15 | Even though the Aragon Court could theoretically resolve any type of binary dispute, we intend for its first version to be primarily used for arbitrating [Proposal Agreements](https://blog.aragon.one/proposal-agreements-and-the-aragon-court/). These agreements require entities to first agree upon a set of rules and processes for creating proposals in an organization, each forcing proposal creators to stake collateral that may be forfeit if the proposal is deemed invalid by the Court. However, how disputes arrive at the Aragon Court is outside of the scope of this protocol. The Court relies on a small external interface to link corresponding disputes and execute them once a ruling has been decided. 16 | 17 | ### 1.2. High-level flow 18 | 19 | - Jurors deposit ANT into a bonding curve to generate ANJ tokens. 20 | - Jurors stake ANJ to the Court contract and schedule their activation and deactivation for the time period in which they can be drafted to rule on disputes. 21 | - Court fees and configuration parameters are controlled by a governor (eventually the Aragon Network), but can only be modified for future terms to ensure that parameters can’t change for ongoing disputes. 22 | - The creator of a dispute must pay fees to cover the maintenance gas costs of the Court and the jurors that will adjudicate their dispute. The governor of the Court receives a share of all fees paid to the Court. 23 | - Jurors are randomly drafted to adjudicate disputes, where the chance to be drafted is proportional to the amount of ANJ they have activated. 24 | - When drafted, each juror must commit and reveal their vote for the ruling. Failure to commit or reveal results in a penalty for the juror. 25 | - After a ruling is decided, it can be appealed by anyone a certain number of times, after which all active jurors will vote on the last appeal (an unappealable ruling). 26 | - When the final ruling is decided, all the adjudication rounds for the dispute can be settled, taking into account the final ruling for rewards and penalties. 27 | -------------------------------------------------------------------------------- /docs/2-architecture/readme.md: -------------------------------------------------------------------------------- 1 | # 2. Architecture 2 | 3 | ![Architecture diagram](./architecture.png) 4 | 5 | ## 2.1. Controller 6 | 7 | The `Controller` has four main responsibilities: 8 | - Permissions management 9 | - Modules management 10 | - Court terms ("clock") management 11 | - Court configuration management 12 | 13 | The Court protocol relies on five main modules: `DisputeManager`, `Voting`, `JurorsRegistry`, `Treasury`, and `Subscriptions`. 14 | Each of these modules are only referenced by the `Controller`; centralizing them allows us to be able to plug or unplug modules easily. 15 | 16 | The Court terms management and reference is held in `Clock`. Almost every functionality of the protocol needs to ensure the current Court term is up-to-date. 17 | 18 | Every protocol configuration variable that needs to be check-pointed based on the different terms of the Court is referenced in `Config`. 19 | Note that this is important to be able to guarantee to our users that certain actions they committed to the Court will rely always on the same configurations of the protocol that were there at the moment said actions were requested. 20 | On the other hand, there are some configuration variables that are related to instantaneous actions. In this case, since we don't need to ensure historic information, these are held on its corresponding module for gas-optimization reasons. 21 | 22 | ## 2.2. Modules 23 | 24 | All the modules of the protocol can be switched by new deployed ones. This allows us to have recoverability in case there is a failure in any of the modules. 25 | Each module has a reference to the `Controller` through `Controlled`, and `ControlledRecoverable` is a special flavor of `Controlled` to ensure complete recoverability. 26 | Since some modules handle token assets, we need to be able to move these assets from one module to another in case they are switched. 27 | 28 | However, these modules can be frozen at any time, once the protocol has reached a high-level of maturity full-immutability will be guaranteed. 29 | In case further changes are required a new whole implementation of the protocol can be deployed and it will be a users decision to chose the one they want to use. 30 | 31 | Detailed information about each module functionality is described in [section 4](../4-entry-points). 32 | 33 | ## 2.3. Permissions 34 | 35 | The permissions layer is self-controlled in the `Controller`. This component works with three `Governor` addresses to implement that: 36 | - Modules governor 37 | - Config governor 38 | - Funds governor 39 | 40 | All the functionality described above whose access needs to be restricted relies on these addresses. 41 | The modules governor is the only one allowed to switch modules. This address can be unset at any time to ensure no further changes on the modules can be made. 42 | Finally the funds governor is the only one allowed to recover assets from the `ControlledRecoverable` modules. As for the modules governor, this address can be unset at any time. 43 | The config governor is the only one allowed to change all the configuration variables of the protocol. This last one, is the only governor address that is not intended to be unset. 44 | The protocol settings should be always able to be tweaked to ensure the proper operation of Court. To guarantee decentralized governance of these variables, the config governor is meant to be the Aragon Network. 45 | 46 | Any other modules functionality that needs to be restricted is either relying on these governor addresses as well, or on any of the other modules. 47 | No other external accounts apart from the `Governor` addresses belong to the protocol implementation. 48 | 49 | As described in the [launch process](https://forum.aragon.org/t/aragon-network-launch-phases-and-target-dates/1263) of Aragon Court, the governor will be a DAO properly called Aragon Network DAO managed by ANT holders. 50 | Initially, the Aragon Network DAO will be governed by a small council with a group trusted members from the community and will be transitioned to the ANT holders at the end of the launch process. 51 | 52 | ## 2.4. Entry point 53 | 54 | The main entry point of the protocol is `AragonCourt`, this component inherits from `Controller`. 55 | This allows to guarantee a single and immutable address to the users of the Court protocol. `AragonCourt` does not implement core logic, only the main entry points of the protocol where each request is forwarded to the corresponding modules of the `Controller` to be fulfilled. 56 | 57 | Detailed information about `AragonCourt` can be found in [section 4](../4-entry-points). 58 | -------------------------------------------------------------------------------- /scripts/tree-profile-gas.js: -------------------------------------------------------------------------------- 1 | const { bn, bigExp } = require('../test/helpers/lib/numbers') 2 | const { printTable } = require('../test/helpers/lib/logging') 3 | const { getEventArgument } = require('@aragon/test-helpers/events') 4 | 5 | const MAX_APPEAL_ROUNDS = 4 6 | const APPEAL_STEP_FACTOR = 3 7 | const INITIAL_JURORS_NUMBER = 3 8 | 9 | const TREE_SIZE_STEP_FACTOR = 10 10 | const TREE_MAX_SIZE = 10000 11 | 12 | const MIN_JUROR_BALANCE = 100 13 | const MAX_JUROR_BALANCE = 1000000 14 | 15 | async function profileGas() { 16 | console.log(`MAX_APPEAL_ROUNDS: ${MAX_APPEAL_ROUNDS}`) 17 | console.log(`APPEAL_STEP_FACTOR: ${APPEAL_STEP_FACTOR}`) 18 | console.log(`INITIAL_JURORS_NUMBER: ${INITIAL_JURORS_NUMBER}`) 19 | const HexSumTree = artifacts.require('HexSumTreeGasProfiler') 20 | 21 | for (let treeSize = TREE_SIZE_STEP_FACTOR; treeSize <= TREE_MAX_SIZE; treeSize *= TREE_SIZE_STEP_FACTOR) { 22 | console.log(`\n=====================================`) 23 | console.log(`PROFILING TREE WITH SIZE ${treeSize}`) 24 | const tree = await HexSumTree.new() 25 | await insert(tree, treeSize) 26 | 27 | for (let round = 1, jurorsNumber = INITIAL_JURORS_NUMBER; round <= MAX_APPEAL_ROUNDS; round++, jurorsNumber *= APPEAL_STEP_FACTOR) { 28 | console.log(`\n------------------------------------`) 29 | console.log(`ROUND #${round} - drafting ${jurorsNumber} jurors`) 30 | await search(tree, jurorsNumber, round) 31 | } 32 | } 33 | } 34 | 35 | async function insert(tree, values) { 36 | const insertGasCosts = [] 37 | for (let i = 0; i < values; i++) { 38 | const balance = Math.floor(Math.random() * MAX_JUROR_BALANCE) + MIN_JUROR_BALANCE 39 | const receipt = await tree.insert(0, bigExp(balance, 18)) 40 | insertGasCosts.push(getGas(receipt)) 41 | } 42 | 43 | await logTreeState(tree) 44 | logInsertStats(`${values} values inserted:`, insertGasCosts) 45 | } 46 | 47 | async function search(tree, jurorsNumber, batches) { 48 | const searchGasCosts = [] 49 | const values = await computeSearchValues(tree, jurorsNumber, batches) 50 | for (let batch = 0; batch < batches; batch++) { 51 | const batchSearchValues = values[batch] 52 | const receipt = await tree.search(batchSearchValues, 0) 53 | searchGasCosts.push({ ...getGas(receipt), values: batchSearchValues.length }) 54 | } 55 | 56 | logSearchStats(`${jurorsNumber} jurors searched in ${batches} batches:`, searchGasCosts) 57 | } 58 | 59 | async function computeSearchValues(tree, jurorsNumber, batches) { 60 | const searchValues = [] 61 | const total = (await tree.total()).div(bigExp(1, 18)) 62 | const step = total.div(bn(jurorsNumber)).sub(bn(1)) 63 | for (let i = 1; i <= jurorsNumber; i++) { 64 | const value = step.mul(bn(i)) 65 | searchValues.push(bigExp(value, 18)) 66 | } 67 | 68 | const searchValuesPerBatch = [] 69 | const jurorsPerBatch = Math.floor(jurorsNumber / batches) 70 | for (let batch = 0, batchSize = 0; batch < batches; batch++, batchSize += jurorsPerBatch) { 71 | const limit = (batch === batches - 1) ? searchValues.length : batchSize + jurorsPerBatch 72 | searchValuesPerBatch.push(searchValues.slice(batchSize, limit)) 73 | } 74 | return searchValuesPerBatch 75 | } 76 | 77 | const getGas = receipt => { 78 | const total = receipt.receipt.gasUsed 79 | const functionCost = getEventArgument(receipt, 'GasConsumed', 'gas').toNumber() 80 | return { total, function: functionCost } 81 | } 82 | 83 | const logTreeState = async (tree) => { 84 | const total = await tree.total() 85 | const height = await tree.height() 86 | const nextKey = await tree.nextKey() 87 | console.log(`\nTree height: ${height.toString()}`) 88 | console.log(`Tree next key: ${nextKey.toNumber().toLocaleString()}`) 89 | console.log(`Tree total: ${total.div(bigExp(1, 18)).toNumber().toLocaleString()} e18`) 90 | } 91 | 92 | const logInsertStats = (title, gasCosts) => { 93 | const min = prop => Math.min(...gasCosts.map(x => x[prop])).toLocaleString() 94 | const max = prop => Math.max(...gasCosts.map(x => x[prop])).toLocaleString() 95 | const avg = prop => Math.round(gasCosts.map(x => x[prop]).reduce((a, b) => a + b, 0) / gasCosts.length).toLocaleString() 96 | 97 | printTable(title, [ 98 | ['', 'Total', 'Function'], 99 | ['Min', min('total'), min('function')], 100 | ['Max', max('total'), max('function')], 101 | ['Average', avg('total'), avg('function')] 102 | ]) 103 | } 104 | 105 | const logSearchStats = (title, gasCosts) => { 106 | const body = gasCosts.map((gasCost, batch) => { 107 | const { total, values, function: fnCost } = gasCost 108 | const batchName = `Batch ${batch} - ${values} values` 109 | return [batchName, total.toLocaleString(), fnCost.toLocaleString()] 110 | }) 111 | 112 | printTable(title, [['', 'Total', 'Function'], ...body]) 113 | } 114 | 115 | module.exports = callback => { 116 | profileGas() 117 | .then(callback) 118 | .catch(callback) 119 | } 120 | -------------------------------------------------------------------------------- /test/voting/crvoting-create.js: -------------------------------------------------------------------------------- 1 | const { assertBn } = require('../helpers/asserts/assertBn') 2 | const { OUTCOMES } = require('../helpers/utils/crvoting') 3 | const { buildHelper } = require('../helpers/wrappers/court')(web3, artifacts) 4 | const { assertRevert } = require('../helpers/asserts/assertThrow') 5 | const { VOTING_EVENTS } = require('../helpers/utils/events') 6 | const { decodeEventsOfType } = require('../helpers/lib/decodeEvent') 7 | const { CONTROLLED_ERRORS, VOTING_ERRORS } = require('../helpers/utils/errors') 8 | const { assertEvent, assertAmountOfEvents } = require('../helpers/asserts/assertEvent') 9 | 10 | const CRVoting = artifacts.require('CRVoting') 11 | const DisputeManager = artifacts.require('DisputeManagerMockForVoting') 12 | 13 | contract('CRVoting', ([_, someone]) => { 14 | let controller, voting, disputeManager 15 | 16 | beforeEach('create base contracts', async () => { 17 | controller = await buildHelper().deploy() 18 | disputeManager = await DisputeManager.new(controller.address) 19 | await controller.setDisputeManager(disputeManager.address) 20 | }) 21 | 22 | beforeEach('create voting module', async () => { 23 | voting = await CRVoting.new(controller.address) 24 | await controller.setVoting(voting.address) 25 | }) 26 | 27 | describe('create', () => { 28 | context('when the sender is the owner', () => { 29 | const voteId = 1 30 | 31 | context('when the given vote ID was not used before', () => { 32 | context('when the given possible outcomes is valid', () => { 33 | const possibleOutcomes = 5 34 | 35 | it('creates the given voting', async () => { 36 | await disputeManager.create(voteId, possibleOutcomes) 37 | 38 | assert.isTrue(await voting.isValidOutcome(voteId, OUTCOMES.REFUSED), 'refused outcome should be invalid') 39 | assertBn((await voting.getMaxAllowedOutcome(voteId)), possibleOutcomes + OUTCOMES.REFUSED.toNumber(), 'max allowed outcome does not match') 40 | }) 41 | 42 | it('emits an event', async () => { 43 | const receipt = await disputeManager.create(voteId, possibleOutcomes) 44 | const logs = decodeEventsOfType(receipt, CRVoting.abi, VOTING_EVENTS.VOTING_CREATED) 45 | 46 | assertAmountOfEvents({ logs }, VOTING_EVENTS.VOTING_CREATED) 47 | assertEvent({ logs }, VOTING_EVENTS.VOTING_CREATED, { voteId, possibleOutcomes }) 48 | }) 49 | 50 | it('considers as valid outcomes any of the possible ones', async () => { 51 | await disputeManager.create(voteId, possibleOutcomes) 52 | 53 | const maxAllowedOutcome = (await voting.getMaxAllowedOutcome(voteId)).toNumber() 54 | for (let outcome = OUTCOMES.REFUSED.toNumber(); outcome <= maxAllowedOutcome; outcome++) { 55 | assert.isTrue(await voting.isValidOutcome(voteId, outcome), 'outcome should be valid') 56 | } 57 | }) 58 | 59 | it('considers the missing and leaked outcomes invalid', async () => { 60 | await disputeManager.create(voteId, possibleOutcomes) 61 | 62 | assert.isFalse(await voting.isValidOutcome(voteId, OUTCOMES.MISSING), 'missing outcome should be invalid') 63 | assert.isFalse(await voting.isValidOutcome(voteId, OUTCOMES.LEAKED), 'leaked outcome should be invalid') 64 | }) 65 | 66 | it('considers refused as the winning outcome initially', async () => { 67 | await disputeManager.create(voteId, possibleOutcomes) 68 | 69 | assertBn((await voting.getWinningOutcome(voteId)), OUTCOMES.REFUSED, 'winning outcome does not match') 70 | }) 71 | }) 72 | 73 | context('when the possible outcomes below the minimum', () => { 74 | it('reverts', async () => { 75 | await assertRevert(disputeManager.create(voteId, 0), VOTING_ERRORS.INVALID_OUTCOMES_AMOUNT) 76 | await assertRevert(disputeManager.create(voteId, 1), VOTING_ERRORS.INVALID_OUTCOMES_AMOUNT) 77 | }) 78 | }) 79 | 80 | context('when the possible outcomes above the maximum', () => { 81 | it('reverts', async () => { 82 | await assertRevert(disputeManager.create(voteId, 254), VOTING_ERRORS.INVALID_OUTCOMES_AMOUNT) 83 | await assertRevert(disputeManager.create(voteId, 510), VOTING_ERRORS.INVALID_OUTCOMES_AMOUNT) 84 | }) 85 | }) 86 | }) 87 | 88 | context('when the given vote ID was already used', () => { 89 | beforeEach('create voting', async () => { 90 | await disputeManager.create(voteId, 2) 91 | }) 92 | 93 | it('reverts', async () => { 94 | await assertRevert(disputeManager.create(voteId, 2), VOTING_ERRORS.VOTE_ALREADY_EXISTS) 95 | }) 96 | }) 97 | }) 98 | 99 | context('when the sender is not the owner', () => { 100 | const from = someone 101 | 102 | it('reverts', async () => { 103 | await assertRevert(voting.create(1, 2, { from }), CONTROLLED_ERRORS.SENDER_NOT_DISPUTES_MODULE) 104 | }) 105 | }) 106 | }) 107 | }) 108 | -------------------------------------------------------------------------------- /contracts/treasury/CourtTreasury.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../lib/os/ERC20.sol"; 4 | import "../lib/os/SafeMath.sol"; 5 | import "../lib/os/SafeERC20.sol"; 6 | 7 | import "./ITreasury.sol"; 8 | import "../court/controller/Controller.sol"; 9 | import "../court/controller/ControlledRecoverable.sol"; 10 | 11 | 12 | contract CourtTreasury is ControlledRecoverable, ITreasury { 13 | using SafeERC20 for ERC20; 14 | using SafeMath for uint256; 15 | 16 | string private constant ERROR_DEPOSIT_AMOUNT_ZERO = "TREASURY_DEPOSIT_AMOUNT_ZERO"; 17 | string private constant ERROR_WITHDRAW_FAILED = "TREASURY_WITHDRAW_FAILED"; 18 | string private constant ERROR_WITHDRAW_AMOUNT_ZERO = "TREASURY_WITHDRAW_AMOUNT_ZERO"; 19 | string private constant ERROR_WITHDRAW_INVALID_AMOUNT = "TREASURY_WITHDRAW_INVALID_AMOUNT"; 20 | string private constant ERROR_WITHDRAWS_DISALLOWED = "TREASURY_WITHDRAWALS_DISALLOWED"; 21 | 22 | // List of balances indexed by token and holder address 23 | mapping (address => mapping (address => uint256)) internal balances; 24 | 25 | event Assign(ERC20 indexed token, address indexed from, address indexed to, uint256 amount); 26 | event Withdraw(ERC20 indexed token, address indexed from, address indexed to, uint256 amount); 27 | 28 | /** 29 | * @dev Constructor function 30 | * @param _controller Address of the controller 31 | */ 32 | constructor(Controller _controller) ControlledRecoverable(_controller) public { 33 | // solium-disable-previous-line no-empty-blocks 34 | // No need to explicitly call `Controlled` constructor since `ControlledRecoverable` is already doing it 35 | } 36 | 37 | /** 38 | * @notice Assign `@tokenAmount(_token, _amount)` to `_to` 39 | * @param _token ERC20 token to be assigned 40 | * @param _to Address of the recipient that will be assigned the tokens to 41 | * @param _amount Amount of tokens to be assigned to the recipient 42 | */ 43 | function assign(ERC20 _token, address _to, uint256 _amount) external onlyDisputeManager { 44 | require(_amount > 0, ERROR_DEPOSIT_AMOUNT_ZERO); 45 | 46 | address tokenAddress = address(_token); 47 | balances[tokenAddress][_to] = balances[tokenAddress][_to].add(_amount); 48 | emit Assign(_token, msg.sender, _to, _amount); 49 | } 50 | 51 | /** 52 | * @notice Withdraw `@tokenAmount(_token, _amount)` from sender to `_to` 53 | * @param _token ERC20 token to be withdrawn 54 | * @param _to Address of the recipient that will receive the tokens 55 | * @param _amount Amount of tokens to be withdrawn from the sender 56 | */ 57 | function withdraw(ERC20 _token, address _to, uint256 _amount) external { 58 | _withdraw(_token, msg.sender, _to, _amount); 59 | } 60 | 61 | /** 62 | * @notice Withdraw all the tokens from `_to` to themself 63 | * @param _token ERC20 token to be withdrawn 64 | * @param _to Address of the recipient that will receive their tokens 65 | */ 66 | function withdrawAll(ERC20 _token, address _to) external { 67 | IConfig config = _courtConfig(); 68 | require(config.areWithdrawalsAllowedFor(_to), ERROR_WITHDRAWS_DISALLOWED); 69 | 70 | uint256 amount = _balanceOf(_token, _to); 71 | _withdraw(_token, _to, _to, amount); 72 | } 73 | 74 | /** 75 | * @dev Tell the token balance of a certain holder 76 | * @param _token ERC20 token balance being queried 77 | * @param _holder Address of the holder querying the balance of 78 | * @return Amount of tokens the holder owns 79 | */ 80 | function balanceOf(ERC20 _token, address _holder) external view returns (uint256) { 81 | return _balanceOf(_token, _holder); 82 | } 83 | 84 | /** 85 | * @dev Internal function to withdraw tokens from an account 86 | * @param _token ERC20 token to be withdrawn 87 | * @param _from Address where the tokens will be removed from 88 | * @param _to Address of the recipient that will receive the corresponding tokens 89 | * @param _amount Amount of tokens to be withdrawn from the sender 90 | */ 91 | function _withdraw(ERC20 _token, address _from, address _to, uint256 _amount) internal { 92 | require(_amount > 0, ERROR_WITHDRAW_AMOUNT_ZERO); 93 | uint256 balance = _balanceOf(_token, _from); 94 | require(balance >= _amount, ERROR_WITHDRAW_INVALID_AMOUNT); 95 | 96 | address tokenAddress = address(_token); 97 | // No need for SafeMath: checked above 98 | balances[tokenAddress][_from] = balance - _amount; 99 | emit Withdraw(_token, _from, _to, _amount); 100 | 101 | require(_token.safeTransfer(_to, _amount), ERROR_WITHDRAW_FAILED); 102 | } 103 | 104 | /** 105 | * @dev Internal function to tell the token balance of a certain holder 106 | * @param _token ERC20 token balance being queried 107 | * @param _holder Address of the holder querying the balance of 108 | * @return Amount of tokens the holder owns 109 | */ 110 | function _balanceOf(ERC20 _token, address _holder) internal view returns (uint256) { 111 | return balances[address(_token)][_holder]; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /docs/4-entry-points/1-aragon-court.md: -------------------------------------------------------------------------------- 1 | ## 4.1. AragonCourt 2 | 3 | `AragonCourt` is the main entry point of the whole Court protocol and is only responsible for providing a few entry points to the users of the protocol while orchestrating the rest of the modules to fulfill these request. 4 | Additionally, as shown in [section 2](../2-architecture), `AragonCourt` inherits from `Controller`. The inherited functionality is core to architecture of the protocol and can be found in the [next section](./2-controller.md). 5 | To read more information about its responsibilities and how the whole architecture structure looks like, go to [section 2](../2-architecture). 6 | 7 | ### 4.1.1. Constructor 8 | 9 | - **Actor:** Deployer account 10 | - **Inputs:** 11 | - **Term duration:** Duration in seconds per Court term 12 | - **First-term start time:** Timestamp in seconds when the Court will start 13 | - **Governor:** Object containing 14 | - **Funds governor:** Address of the governor allowed to manipulate module's funds 15 | - **Config governor:** Address of the governor allowed to manipulate court settings 16 | - **Modules governor:** Address of the governor allowed to manipulate module's addresses 17 | - **Settings:** Object containing 18 | - **Fee token:** Address of the token contract that is used to pay for the fees 19 | - **Juror fee:** Amount of fee tokens paid per drafted juror per dispute 20 | - **Heartbeat fee:** Amount of fee tokens per dispute to cover terms update costs 21 | - **Draft fee:** Amount of fee tokens per juror to cover the drafting costs 22 | - **Settle fee:** Amount of fee tokens per juror to cover round settlement costs 23 | - **Evidence terms:** Max submitting evidence period duration in Court terms 24 | - **Commit terms:** Duration of the commit phase in Court terms 25 | - **Reveal terms:** Duration of the reveal phase in Court terms 26 | - **Appeal terms:** Duration of the appeal phase in Court terms 27 | - **Appeal confirmation terms:** Duration of the appeal confirmation phase in Court terms 28 | - **Penalty permyriad:** ‱ of min active tokens balance to be locked for each drafted juror (1/10,000) 29 | - **Final-round reduction:** ‱ of fee reduction for the last appeal round (1/10,000) 30 | - **First-round jurors number:** Number of jurors to be drafted for the first round of a dispute 31 | - **Appeal step factor:** Increasing factor for the number of jurors of each dispute round 32 | - **Max regular appeal rounds:** Number of regular appeal rounds before the final round is triggered 33 | - **Final round lock terms:** Number of terms that a coherent juror in a final round is disallowed to withdraw 34 | - **Appeal collateral factor:** ‱ multiple of dispute fees (jurors, draft, and settlements) required to appeal a preliminary ruling (1/10,000) 35 | - **Appeal confirmation collateral factor:** ‱ multiple of dispute fees (jurors, draft, and settlements) required to confirm an appeal (1/10,000) 36 | - **Min active balance:** Minimum amount of juror tokens that can be activated 37 | - **Authentication:** Open 38 | - **Pre-flight checks:** None 39 | - **State transitions:** 40 | - Call `Controller` constructor 41 | 42 | ### 4.1.2. Create dispute 43 | 44 | - **Actor:** Arbitrable instances, entities that need a dispute adjudicated 45 | - **Inputs:** 46 | - **Possible rulings:** Number of possible results for a dispute 47 | - **Metadata:** Optional metadata that can be used to provide additional information on the dispute to be created 48 | - **Authentication:** Open. Implicitly, only smart contracts that are up to date on their subscriptions in the `Subscription` module and that have open an ERC20 allowance with an amount of at least the dispute fee to the `DisputeManager` module can call this function 49 | - **Pre-flight checks:** 50 | - Ensure that the msg.sender supports the `IArbitrable` interface 51 | - Ensure that the subject is up-to-date on its subscription fees 52 | - **State transitions:** 53 | - Create a new dispute object in the DisputeManager module 54 | 55 | ### 4.1.3. Close evidence period 56 | 57 | - **Actor:** Arbitrable instances, entities that need a dispute adjudicated. 58 | - **Inputs:** 59 | - **Dispute ID:** Dispute identification number 60 | - **Authentication:** Open. Implicitly, only the Arbitrable instance related to the given dispute 61 | - **Pre-flight checks:** 62 | - Ensure a dispute object with that ID exists 63 | - Ensure that the dispute subject is the Arbitrable calling the function 64 | - Ensure that the dispute evidence period is still open 65 | - **State transitions:** 66 | - Update the dispute to allow being drafted immediately 67 | 68 | ### 4.1.4. Execute dispute 69 | 70 | - **Actor:** External entity incentivized to execute the final ruling decided for a dispute. Alternatively, an altruistic entity to make sure the dispute is ruled. 71 | - **Inputs:** 72 | - **Dispute ID:** Dispute identification number 73 | - **Authentication:** Open 74 | - **Pre-flight checks:** 75 | - Ensure a dispute object with that ID exists 76 | - Ensure that the dispute has not been executed yet 77 | - Ensure that the dispute's last round adjudication phase has ended 78 | - **State transitions:** 79 | - Compute the final ruling in the DisputeManager module 80 | - Execute the `IArbitrable` instance linked to the dispute based on the decided ruling 81 | -------------------------------------------------------------------------------- /contracts/test/standards/ERC20Mock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/os/SafeMath.sol"; 4 | 5 | import "../../standards/ApproveAndCall.sol"; 6 | 7 | 8 | contract ERC20Mock { 9 | using SafeMath for uint256; 10 | 11 | string public name; 12 | string public symbol; 13 | uint8 public decimals; 14 | uint256 public totalSupply; 15 | 16 | bool private allowTransfer_; 17 | mapping (address => uint256) private balances; 18 | mapping (address => mapping (address => uint256)) private allowed; 19 | 20 | event Approval(address indexed owner, address indexed spender, uint256 value); 21 | event Transfer(address indexed from, address indexed to, uint256 value); 22 | 23 | constructor(string memory _name, string memory _symbol, uint8 _decimals) public { 24 | name = _name; 25 | symbol = _symbol; 26 | decimals = _decimals; 27 | allowTransfer_ = true; 28 | } 29 | 30 | /** 31 | * @dev Mint a certain amount of tokens for an address 32 | * @param _to The address that will receive the tokens 33 | * @param _amount The amount of tokens to be minted 34 | */ 35 | function generateTokens(address _to, uint _amount) public { 36 | totalSupply = totalSupply.add(_amount); 37 | balances[_to] = balances[_to].add(_amount); 38 | emit Transfer(address(0), _to, _amount); 39 | } 40 | 41 | /** 42 | * @dev Gets the balance of the specified address. 43 | * @param _owner The address to query the the balance of. 44 | * @return The amount owned by the passed address. 45 | */ 46 | function balanceOf(address _owner) public view returns (uint256) { 47 | return balances[_owner]; 48 | } 49 | 50 | /** 51 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 52 | * @param _owner The address which owns the funds. 53 | * @param _spender The address which will spend the funds. 54 | * @return The amount of tokens still available for the spender. 55 | */ 56 | function allowance(address _owner, address _spender) public view returns (uint256) { 57 | return allowed[_owner][_spender]; 58 | } 59 | 60 | /** 61 | * @dev Set whether the token is transferable or not 62 | * @param _allowTransfer Should token be transferable 63 | */ 64 | function setAllowTransfer(bool _allowTransfer) public { 65 | allowTransfer_ = _allowTransfer; 66 | } 67 | 68 | /** 69 | * @dev Transfer token for a specified address 70 | * @param _to The address to transfer to. 71 | * @param _amount The amount to be transferred. 72 | */ 73 | function transfer(address _to, uint256 _amount) public returns (bool) { 74 | require(allowTransfer_, 'ERROR_TRANSFERS_NOT_ALLOWED'); 75 | require(_amount <= balances[msg.sender], 'ERROR_NOT_ENOUGH_BALANCE'); 76 | require(_to != address(0), 'ERROR_RECIPIENT_ZERO'); 77 | 78 | balances[msg.sender] = balances[msg.sender].sub(_amount); 79 | balances[_to] = balances[_to].add(_amount); 80 | emit Transfer(msg.sender, _to, _amount); 81 | return true; 82 | } 83 | 84 | /** 85 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 86 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 87 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 88 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 89 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 90 | * @param _spender The address which will spend the funds. 91 | * @param _amount The amount of tokens to be spent. 92 | */ 93 | function approve(address _spender, uint256 _amount) public returns (bool) { 94 | // Assume we want to protect for the race condition 95 | require(_amount == 0 || allowed[msg.sender][_spender] == 0, 'ERROR_ALLOWANCE_MUST_BE_ZERO'); 96 | 97 | allowed[msg.sender][_spender] = _amount; 98 | emit Approval(msg.sender, _spender, _amount); 99 | return true; 100 | } 101 | 102 | /** 103 | * @dev `msg.sender` approves `_spender` to send `_amount` tokens on its behalf, and then a function is 104 | * triggered in the contract that is being approved, `_spender`. This allows users to use their 105 | * tokens to interact with contracts in one function call instead of two 106 | * @param _spender Address of the contract able to transfer the tokens 107 | * @param _amount The amount of tokens to be approved for transfer 108 | * @return True if the function call was successful 109 | */ 110 | function approveAndCall(ApproveAndCallFallBack _spender, uint256 _amount, bytes memory _extraData) public returns (bool) { 111 | require(approve(address(_spender), _amount), 'ERROR_APPROVE_FAILED'); 112 | _spender.receiveApproval(msg.sender, _amount, address(this), _extraData); 113 | return true; 114 | } 115 | 116 | /** 117 | * @dev Transfer tokens from one address to another 118 | * @param _from The address which you want to send tokens from 119 | * @param _to The address which you want to transfer to 120 | * @param _amount The amount of tokens to be transferred 121 | */ 122 | function transferFrom(address _from, address _to, uint256 _amount) public returns (bool) { 123 | require(allowTransfer_, 'ERROR_TRANSFERS_NOT_ALLOWED'); 124 | require(_amount <= balances[_from], 'ERROR_NOT_ENOUGH_BALANCE'); 125 | require(_amount <= allowed[_from][msg.sender], 'ERROR_NOT_ENOUGH_ALLOWED_BALANCE'); 126 | require(_to != address(0), 'ERROR_RECIPIENT_ZERO'); 127 | 128 | balances[_from] = balances[_from].sub(_amount); 129 | balances[_to] = balances[_to].add(_amount); 130 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount); 131 | emit Transfer(_from, _to, _amount); 132 | return true; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /contracts/court/controller/Controlled.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../../lib/os/IsContract.sol"; 4 | 5 | import "./Controller.sol"; 6 | import "../clock/IClock.sol"; 7 | import "../config/ConfigConsumer.sol"; 8 | import "../../voting/ICRVoting.sol"; 9 | import "../../treasury/ITreasury.sol"; 10 | import "../../registry/IJurorsRegistry.sol"; 11 | import "../../disputes/IDisputeManager.sol"; 12 | import "../../subscriptions/ISubscriptions.sol"; 13 | 14 | 15 | contract Controlled is IsContract, ConfigConsumer { 16 | string private constant ERROR_CONTROLLER_NOT_CONTRACT = "CTD_CONTROLLER_NOT_CONTRACT"; 17 | string private constant ERROR_SENDER_NOT_CONTROLLER = "CTD_SENDER_NOT_CONTROLLER"; 18 | string private constant ERROR_SENDER_NOT_CONFIG_GOVERNOR = "CTD_SENDER_NOT_CONFIG_GOVERNOR"; 19 | string private constant ERROR_SENDER_NOT_DISPUTES_MODULE = "CTD_SENDER_NOT_DISPUTES_MODULE"; 20 | 21 | // Address of the controller 22 | Controller internal controller; 23 | 24 | /** 25 | * @dev Ensure the msg.sender is the controller's config governor 26 | */ 27 | modifier onlyConfigGovernor { 28 | require(msg.sender == _configGovernor(), ERROR_SENDER_NOT_CONFIG_GOVERNOR); 29 | _; 30 | } 31 | 32 | /** 33 | * @dev Ensure the msg.sender is the controller 34 | */ 35 | modifier onlyController() { 36 | require(msg.sender == address(controller), ERROR_SENDER_NOT_CONTROLLER); 37 | _; 38 | } 39 | 40 | /** 41 | * @dev Ensure the msg.sender is the DisputeManager module 42 | */ 43 | modifier onlyDisputeManager() { 44 | require(msg.sender == address(_disputeManager()), ERROR_SENDER_NOT_DISPUTES_MODULE); 45 | _; 46 | } 47 | 48 | /** 49 | * @dev Constructor function 50 | * @param _controller Address of the controller 51 | */ 52 | constructor(Controller _controller) public { 53 | require(isContract(address(_controller)), ERROR_CONTROLLER_NOT_CONTRACT); 54 | controller = _controller; 55 | } 56 | 57 | /** 58 | * @dev Tell the address of the controller 59 | * @return Address of the controller 60 | */ 61 | function getController() external view returns (Controller) { 62 | return controller; 63 | } 64 | 65 | /** 66 | * @dev Internal function to ensure the Court term is up-to-date, it will try to update it if not 67 | * @return Identification number of the current Court term 68 | */ 69 | function _ensureCurrentTerm() internal returns (uint64) { 70 | return _clock().ensureCurrentTerm(); 71 | } 72 | 73 | /** 74 | * @dev Internal function to fetch the last ensured term ID of the Court 75 | * @return Identification number of the last ensured term 76 | */ 77 | function _getLastEnsuredTermId() internal view returns (uint64) { 78 | return _clock().getLastEnsuredTermId(); 79 | } 80 | 81 | /** 82 | * @dev Internal function to tell the current term identification number 83 | * @return Identification number of the current term 84 | */ 85 | function _getCurrentTermId() internal view returns (uint64) { 86 | return _clock().getCurrentTermId(); 87 | } 88 | 89 | /** 90 | * @dev Internal function to fetch the controller's config governor 91 | * @return Address of the controller's governor 92 | */ 93 | function _configGovernor() internal view returns (address) { 94 | return controller.getConfigGovernor(); 95 | } 96 | 97 | /** 98 | * @dev Internal function to fetch the address of the DisputeManager module from the controller 99 | * @return Address of the DisputeManager module 100 | */ 101 | function _disputeManager() internal view returns (IDisputeManager) { 102 | return IDisputeManager(controller.getDisputeManager()); 103 | } 104 | 105 | /** 106 | * @dev Internal function to fetch the address of the Treasury module implementation from the controller 107 | * @return Address of the Treasury module implementation 108 | */ 109 | function _treasury() internal view returns (ITreasury) { 110 | return ITreasury(controller.getTreasury()); 111 | } 112 | 113 | /** 114 | * @dev Internal function to fetch the address of the Voting module implementation from the controller 115 | * @return Address of the Voting module implementation 116 | */ 117 | function _voting() internal view returns (ICRVoting) { 118 | return ICRVoting(controller.getVoting()); 119 | } 120 | 121 | /** 122 | * @dev Internal function to fetch the address of the Voting module owner from the controller 123 | * @return Address of the Voting module owner 124 | */ 125 | function _votingOwner() internal view returns (ICRVotingOwner) { 126 | return ICRVotingOwner(address(_disputeManager())); 127 | } 128 | 129 | /** 130 | * @dev Internal function to fetch the address of the JurorRegistry module implementation from the controller 131 | * @return Address of the JurorRegistry module implementation 132 | */ 133 | function _jurorsRegistry() internal view returns (IJurorsRegistry) { 134 | return IJurorsRegistry(controller.getJurorsRegistry()); 135 | } 136 | 137 | /** 138 | * @dev Internal function to fetch the address of the Subscriptions module implementation from the controller 139 | * @return Address of the Subscriptions module implementation 140 | */ 141 | function _subscriptions() internal view returns (ISubscriptions) { 142 | return ISubscriptions(controller.getSubscriptions()); 143 | } 144 | 145 | /** 146 | * @dev Internal function to fetch the address of the Clock module from the controller 147 | * @return Address of the Clock module 148 | */ 149 | function _clock() internal view returns (IClock) { 150 | return IClock(controller); 151 | } 152 | 153 | /** 154 | * @dev Internal function to fetch the address of the Config module from the controller 155 | * @return Address of the Config module 156 | */ 157 | function _courtConfig() internal view returns (IConfig) { 158 | return IConfig(controller); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /test/subscriptions/subscriptions-initialization.js: -------------------------------------------------------------------------------- 1 | const { assertBn } = require('../helpers/asserts/assertBn') 2 | const { bn, bigExp } = require('../helpers/lib/numbers') 3 | const { buildHelper } = require('../helpers/wrappers/court')(web3, artifacts) 4 | const { assertRevert } = require('../helpers/asserts/assertThrow') 5 | const { CONTROLLED_ERRORS, SUBSCRIPTIONS_ERRORS } = require('../helpers/utils/errors') 6 | 7 | const CourtSubscriptions = artifacts.require('CourtSubscriptions') 8 | const ERC20 = artifacts.require('ERC20Mock') 9 | 10 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 11 | 12 | contract('CourtSubscriptions', ([_, someone]) => { 13 | let controller, feeToken 14 | 15 | const FEE_AMOUNT = bigExp(10, 18) 16 | const PREPAYMENT_PERIODS = 12 17 | const RESUME_PRE_PAID_PERIODS = 10 18 | const PERIOD_DURATION = 24 * 30 // 30 days, assuming terms are 1h 19 | const GOVERNOR_SHARE_PCT = bn(100) // 100‱ = 1% 20 | const LATE_PAYMENT_PENALTY_PCT = bn(1000) // 1000‱ = 10% 21 | 22 | before('create base contracts', async () => { 23 | controller = await buildHelper().deploy() 24 | feeToken = await ERC20.new('Subscriptions Fee Token', 'SFT', 18) 25 | }) 26 | 27 | describe('constructor', () => { 28 | context('when the initialization succeeds', () => { 29 | it('initializes subscriptions correctly', async () => { 30 | const subscriptions = await CourtSubscriptions.new(controller.address, PERIOD_DURATION, feeToken.address, FEE_AMOUNT, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT) 31 | 32 | assert.equal(await subscriptions.getController(), controller.address, 'subscriptions controller does not match') 33 | assert.equal(await subscriptions.periodDuration(), PERIOD_DURATION, 'subscriptions duration does not match') 34 | assert.equal(await subscriptions.currentFeeToken(), feeToken.address, 'fee token does not match') 35 | assertBn((await subscriptions.currentFeeAmount()), FEE_AMOUNT, 'fee amount does not match') 36 | assertBn((await subscriptions.prePaymentPeriods()), PREPAYMENT_PERIODS, 'pre payment periods does not match') 37 | assertBn((await subscriptions.latePaymentPenaltyPct()), LATE_PAYMENT_PENALTY_PCT, 'late payments penalty pct does not match') 38 | assertBn((await subscriptions.governorSharePct()), GOVERNOR_SHARE_PCT, 'governor share pct does not match') 39 | }) 40 | }) 41 | 42 | context('initialization fails', () => { 43 | context('when the given controller is the zero address', () => { 44 | const controllerAddress = ZERO_ADDRESS 45 | 46 | it('reverts', async () => { 47 | await assertRevert(CourtSubscriptions.new(controllerAddress, PERIOD_DURATION, feeToken.address, FEE_AMOUNT, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT), CONTROLLED_ERRORS.CONTROLLER_NOT_CONTRACT) 48 | }) 49 | }) 50 | 51 | context('when the given controller is not a contract address', () => { 52 | const controllerAddress = someone 53 | 54 | it('reverts', async () => { 55 | await assertRevert(CourtSubscriptions.new(controllerAddress, PERIOD_DURATION, feeToken.address, FEE_AMOUNT, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT), CONTROLLED_ERRORS.CONTROLLER_NOT_CONTRACT) 56 | }) 57 | }) 58 | 59 | context('when the given period duration is zero', () => { 60 | const periodDuration = 0 61 | 62 | it('reverts', async () => { 63 | await assertRevert(CourtSubscriptions.new(controller.address, periodDuration, feeToken.address, FEE_AMOUNT, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT), SUBSCRIPTIONS_ERRORS.PERIOD_DURATION_ZERO) 64 | }) 65 | }) 66 | 67 | context('when the given fee token address is the zero address', () => { 68 | const feeTokenAddress = ZERO_ADDRESS 69 | 70 | it('reverts', async () => { 71 | await assertRevert(CourtSubscriptions.new(controller.address, PERIOD_DURATION, feeTokenAddress, FEE_AMOUNT, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT), SUBSCRIPTIONS_ERRORS.FEE_TOKEN_NOT_CONTRACT) 72 | }) 73 | }) 74 | 75 | context('when the given fee token address is not a contract address', () => { 76 | const feeTokenAddress = someone 77 | 78 | it('reverts', async () => { 79 | await assertRevert(CourtSubscriptions.new(controller.address, PERIOD_DURATION, feeTokenAddress, FEE_AMOUNT, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT), SUBSCRIPTIONS_ERRORS.FEE_TOKEN_NOT_CONTRACT) 80 | }) 81 | }) 82 | 83 | context('when the given fee amount is zero', () => { 84 | const feeAmount = 0 85 | 86 | it('reverts', async () => { 87 | await assertRevert(CourtSubscriptions.new(controller.address, PERIOD_DURATION, feeToken.address, feeAmount, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT), SUBSCRIPTIONS_ERRORS.FEE_AMOUNT_ZERO) 88 | }) 89 | }) 90 | 91 | context('when the given pre payment periods number is zero', () => { 92 | const prePaymentPeriods = 0 93 | 94 | it('reverts', async () => { 95 | await assertRevert(CourtSubscriptions.new(controller.address, PERIOD_DURATION, feeToken.address, FEE_AMOUNT, prePaymentPeriods, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT), SUBSCRIPTIONS_ERRORS.PREPAYMENT_PERIODS_ZERO) 96 | }) 97 | }) 98 | 99 | context('when the given governor share is above 100%', () => { 100 | const governorSharePct = bn(10001) 101 | 102 | it('reverts', async () => { 103 | await assertRevert(CourtSubscriptions.new(controller.address, PERIOD_DURATION, feeToken.address, FEE_AMOUNT, PREPAYMENT_PERIODS, RESUME_PRE_PAID_PERIODS, LATE_PAYMENT_PENALTY_PCT, governorSharePct), SUBSCRIPTIONS_ERRORS.OVERRATED_GOVERNOR_SHARE_PCT) 104 | }) 105 | }) 106 | 107 | context('when the given resume pre-paid periods is above the pre-payment periods', () => { 108 | const resumePrePaidPeriods = PREPAYMENT_PERIODS + 1 109 | 110 | it('reverts', async () => { 111 | await assertRevert(CourtSubscriptions.new(controller.address, PERIOD_DURATION, feeToken.address, FEE_AMOUNT, PREPAYMENT_PERIODS, resumePrePaidPeriods, LATE_PAYMENT_PENALTY_PCT, GOVERNOR_SHARE_PCT), SUBSCRIPTIONS_ERRORS.RESUME_PRE_PAID_PERIODS_BIG) 112 | }) 113 | }) 114 | }) 115 | }) 116 | }) 117 | -------------------------------------------------------------------------------- /test/lib/checkpointing.js: -------------------------------------------------------------------------------- 1 | const { assertBn } = require('../helpers/asserts/assertBn') 2 | const { MAX_UINT256 } = require('../helpers/lib/numbers') 3 | const { assertRevert } = require('../helpers/asserts/assertThrow') 4 | const { CHECKPOINT_ERRORS } = require('../helpers/utils/errors') 5 | 6 | const Checkpointing = artifacts.require('CheckpointingMock') 7 | 8 | contract('Checkpointing', () => { 9 | let checkpointing 10 | 11 | beforeEach('create tree', async () => { 12 | checkpointing = await Checkpointing.new() 13 | }) 14 | 15 | const assertFetchedValue = async (time, expectedValue) => { 16 | for (const searchFn of ['get', 'getRecent']) { 17 | assertBn((await checkpointing[searchFn](time)), expectedValue, 'value does not match') 18 | } 19 | } 20 | 21 | describe('add', () => { 22 | context('when the given value is can be represented by 192 bits', () => { 23 | const value = 100 24 | 25 | context('when there was no value registered yet', async () => { 26 | context('when the given time is zero', async () => { 27 | const time = 0 28 | 29 | it('adds the new value', async () => { 30 | await checkpointing.add(time, value) 31 | 32 | await assertFetchedValue(time, value) 33 | }) 34 | }) 35 | 36 | context('when the given time is greater than zero', async () => { 37 | const time = 1 38 | 39 | it('adds the new value', async () => { 40 | await checkpointing.add(time, value) 41 | 42 | await assertFetchedValue(time, value) 43 | }) 44 | }) 45 | }) 46 | 47 | context('when there were some values already registered', async () => { 48 | beforeEach('add some values', async () => { 49 | await checkpointing.add(30, 1) 50 | await checkpointing.add(50, 2) 51 | await checkpointing.add(90, 3) 52 | }) 53 | 54 | context('when the given time is previous to the latest registered value', async () => { 55 | const time = 40 56 | 57 | it('reverts', async () => { 58 | await assertRevert(checkpointing.add(time, value), CHECKPOINT_ERRORS.CANNOT_ADD_PAST_VALUE) 59 | }) 60 | }) 61 | 62 | context('when the given time is equal to the latest registered value', async () => { 63 | const time = 90 64 | 65 | it('updates the already registered value', async () => { 66 | await checkpointing.add(time, value) 67 | 68 | await assertFetchedValue(time, value) 69 | await assertFetchedValue(time + 1, value) 70 | }) 71 | }) 72 | 73 | context('when the given time is after the latest registered value', async () => { 74 | const time = 95 75 | 76 | it('adds the new last value', async () => { 77 | const previousLast = await checkpointing.getLast() 78 | 79 | await checkpointing.add(time, value) 80 | 81 | await assertFetchedValue(time, value) 82 | await assertFetchedValue(time + 1, value) 83 | await assertFetchedValue(time - 1, previousLast) 84 | }) 85 | }) 86 | }) 87 | }) 88 | 89 | context('when the given value cannot be represented by 192 bits', () => { 90 | const value = MAX_UINT256 91 | 92 | it('reverts', async () => { 93 | await assertRevert(checkpointing.add(0, value), CHECKPOINT_ERRORS.VALUE_TOO_BIG) 94 | }) 95 | }) 96 | }) 97 | 98 | describe('getLast', () => { 99 | context('when there are no values registered yet', () => { 100 | it('returns zero', async () => { 101 | assertBn((await checkpointing.getLast()), 0, 'value does not match') 102 | }) 103 | }) 104 | 105 | context('when there are values already registered', () => { 106 | beforeEach('add some values', async () => { 107 | await checkpointing.add(30, 1) 108 | await checkpointing.add(50, 2) 109 | await checkpointing.add(90, 3) 110 | }) 111 | 112 | it('returns the last registered value', async () => { 113 | assertBn((await checkpointing.getLast()), 3, 'value does not match') 114 | }) 115 | }) 116 | }) 117 | 118 | describe('get', () => { 119 | context('when there are no values registered yet', () => { 120 | context('when there given time is zero', () => { 121 | const time = 0 122 | 123 | it('returns zero', async () => { 124 | await assertFetchedValue(time, 0) 125 | }) 126 | }) 127 | 128 | context('when there given time is greater than zero', () => { 129 | const time = 1 130 | 131 | it('returns zero', async () => { 132 | await assertFetchedValue(time, 0) 133 | }) 134 | }) 135 | }) 136 | 137 | context('when there are values already registered', () => { 138 | beforeEach('add some values', async () => { 139 | await checkpointing.add(30, 1) 140 | await checkpointing.add(50, 2) 141 | await checkpointing.add(90, 3) 142 | }) 143 | 144 | context('when there given time is zero', () => { 145 | const time = 0 146 | 147 | it('returns zero', async () => { 148 | await assertFetchedValue(time, 0) 149 | }) 150 | }) 151 | 152 | context('when the given time is previous to the time of first registered value', () => { 153 | const time = 10 154 | 155 | it('returns zero', async () => { 156 | await assertFetchedValue(time, 0) 157 | }) 158 | }) 159 | 160 | context('when the given time is equal to the time of first registered value', () => { 161 | const time = 30 162 | 163 | it('returns the first registered value', async () => { 164 | await assertFetchedValue(time, 1) 165 | }) 166 | }) 167 | 168 | context('when the given time is between the times of first and the second registered values', () => { 169 | const time = 40 170 | 171 | it('returns the first registered value', async () => { 172 | await assertFetchedValue(time, 1) 173 | }) 174 | }) 175 | 176 | context('when the given time is between the times of second and the third registered values', () => { 177 | const time = 60 178 | 179 | it('returns the second registered value', async () => { 180 | await assertFetchedValue(time, 2) 181 | }) 182 | }) 183 | 184 | context('when the given time is equal to the time of the third registered values', () => { 185 | const time = 90 186 | 187 | it('returns the third registered value', async () => { 188 | await assertFetchedValue(time, 3) 189 | }) 190 | }) 191 | 192 | context('when the given time is after the time of the third registered values', () => { 193 | const time = 100 194 | 195 | it('returns the third registered value', async () => { 196 | await assertFetchedValue(time, 3) 197 | }) 198 | }) 199 | }) 200 | }) 201 | }) 202 | -------------------------------------------------------------------------------- /contracts/lib/JurorsTreeSortition.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | import "../lib/os/SafeMath.sol"; 4 | 5 | import "./HexSumTree.sol"; 6 | 7 | 8 | /** 9 | * @title JurorsTreeSortition - Library to perform jurors sortition over a `HexSumTree` 10 | */ 11 | library JurorsTreeSortition { 12 | using SafeMath for uint256; 13 | using HexSumTree for HexSumTree.Tree; 14 | 15 | string private constant ERROR_INVALID_INTERVAL_SEARCH = "TREE_INVALID_INTERVAL_SEARCH"; 16 | string private constant ERROR_SORTITION_LENGTHS_MISMATCH = "TREE_SORTITION_LENGTHS_MISMATCH"; 17 | 18 | /** 19 | * @dev Search random items in the tree based on certain restrictions 20 | * @param _termRandomness Randomness to compute the seed for the draft 21 | * @param _disputeId Identification number of the dispute to draft jurors for 22 | * @param _termId Current term when the draft is being computed 23 | * @param _selectedJurors Number of jurors already selected for the draft 24 | * @param _batchRequestedJurors Number of jurors to be selected in the given batch of the draft 25 | * @param _roundRequestedJurors Total number of jurors requested to be drafted 26 | * @param _sortitionIteration Number of sortitions already performed for the given draft 27 | * @return jurorsIds List of juror ids obtained based on the requested search 28 | * @return jurorsBalances List of active balances for each juror obtained based on the requested search 29 | */ 30 | function batchedRandomSearch( 31 | HexSumTree.Tree storage tree, 32 | bytes32 _termRandomness, 33 | uint256 _disputeId, 34 | uint64 _termId, 35 | uint256 _selectedJurors, 36 | uint256 _batchRequestedJurors, 37 | uint256 _roundRequestedJurors, 38 | uint256 _sortitionIteration 39 | ) 40 | internal 41 | view 42 | returns (uint256[] memory jurorsIds, uint256[] memory jurorsBalances) 43 | { 44 | (uint256 low, uint256 high) = getSearchBatchBounds(tree, _termId, _selectedJurors, _batchRequestedJurors, _roundRequestedJurors); 45 | uint256[] memory balances = _computeSearchRandomBalances( 46 | _termRandomness, 47 | _disputeId, 48 | _sortitionIteration, 49 | _batchRequestedJurors, 50 | low, 51 | high 52 | ); 53 | 54 | (jurorsIds, jurorsBalances) = tree.search(balances, _termId); 55 | 56 | require(jurorsIds.length == jurorsBalances.length, ERROR_SORTITION_LENGTHS_MISMATCH); 57 | require(jurorsIds.length == _batchRequestedJurors, ERROR_SORTITION_LENGTHS_MISMATCH); 58 | } 59 | 60 | /** 61 | * @dev Get the bounds for a draft batch based on the active balances of the jurors 62 | * @param _termId Term ID of the active balances that will be used to compute the boundaries 63 | * @param _selectedJurors Number of jurors already selected for the draft 64 | * @param _batchRequestedJurors Number of jurors to be selected in the given batch of the draft 65 | * @param _roundRequestedJurors Total number of jurors requested to be drafted 66 | * @return low Low bound to be used for the sortition to draft the requested number of jurors for the given batch 67 | * @return high High bound to be used for the sortition to draft the requested number of jurors for the given batch 68 | */ 69 | function getSearchBatchBounds( 70 | HexSumTree.Tree storage tree, 71 | uint64 _termId, 72 | uint256 _selectedJurors, 73 | uint256 _batchRequestedJurors, 74 | uint256 _roundRequestedJurors 75 | ) 76 | internal 77 | view 78 | returns (uint256 low, uint256 high) 79 | { 80 | uint256 totalActiveBalance = tree.getRecentTotalAt(_termId); 81 | low = _selectedJurors.mul(totalActiveBalance).div(_roundRequestedJurors); 82 | 83 | uint256 newSelectedJurors = _selectedJurors.add(_batchRequestedJurors); 84 | high = newSelectedJurors.mul(totalActiveBalance).div(_roundRequestedJurors); 85 | } 86 | 87 | /** 88 | * @dev Get a random list of active balances to be searched in the jurors tree for a given draft batch 89 | * @param _termRandomness Randomness to compute the seed for the draft 90 | * @param _disputeId Identification number of the dispute to draft jurors for (for randomness) 91 | * @param _sortitionIteration Number of sortitions already performed for the given draft (for randomness) 92 | * @param _batchRequestedJurors Number of jurors to be selected in the given batch of the draft 93 | * @param _lowBatchBound Low bound to be used for the sortition batch to draft the requested number of jurors 94 | * @param _highBatchBound High bound to be used for the sortition batch to draft the requested number of jurors 95 | * @return Random list of active balances to be searched in the jurors tree for the given draft batch 96 | */ 97 | function _computeSearchRandomBalances( 98 | bytes32 _termRandomness, 99 | uint256 _disputeId, 100 | uint256 _sortitionIteration, 101 | uint256 _batchRequestedJurors, 102 | uint256 _lowBatchBound, 103 | uint256 _highBatchBound 104 | ) 105 | internal 106 | pure 107 | returns (uint256[] memory) 108 | { 109 | // Calculate the interval to be used to search the balances in the tree. Since we are using a modulo function to compute the 110 | // random balances to be searched, intervals will be closed on the left and open on the right, for example [0,10). 111 | require(_highBatchBound > _lowBatchBound, ERROR_INVALID_INTERVAL_SEARCH); 112 | uint256 interval = _highBatchBound - _lowBatchBound; 113 | 114 | // Compute an ordered list of random active balance to be searched in the jurors tree 115 | uint256[] memory balances = new uint256[](_batchRequestedJurors); 116 | for (uint256 batchJurorNumber = 0; batchJurorNumber < _batchRequestedJurors; batchJurorNumber++) { 117 | // Compute a random seed using: 118 | // - The inherent randomness associated to the term from blockhash 119 | // - The disputeId, so 2 disputes in the same term will have different outcomes 120 | // - The sortition iteration, to avoid getting stuck if resulting jurors are dismissed due to locked balance 121 | // - The juror number in this batch 122 | bytes32 seed = keccak256(abi.encodePacked(_termRandomness, _disputeId, _sortitionIteration, batchJurorNumber)); 123 | 124 | // Compute a random active balance to be searched in the jurors tree using the generated seed within the 125 | // boundaries computed for the current batch. 126 | balances[batchJurorNumber] = _lowBatchBound.add(uint256(seed) % interval); 127 | 128 | // Make sure it's ordered, flip values if necessary 129 | for (uint256 i = batchJurorNumber; i > 0 && balances[i] < balances[i - 1]; i--) { 130 | uint256 tmp = balances[i - 1]; 131 | balances[i - 1] = balances[i]; 132 | balances[i] = tmp; 133 | } 134 | } 135 | return balances; 136 | } 137 | } 138 | --------------------------------------------------------------------------------