├── 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 | 
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 | 
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 |
--------------------------------------------------------------------------------