├── .eslintignore ├── .gitattributes ├── .eslintrc.json ├── .gitignore ├── migrations ├── 1_initial_migration.js ├── 2_delphistakefactory.js ├── 4_delphivoting_factory.js └── 3_optional_tcr_factory.js ├── conf ├── dsConfig.json └── tcrConfig.json ├── README.md ├── .solcover.js ├── ethpm.json ├── test └── js │ ├── DelphiStakeFactory │ ├── DelphiStakeFactory.js │ └── createDelphiStake.js │ ├── DelphiVotingFactory │ └── makeDelphiVoting.js │ ├── DelphiStake │ ├── extendStakeReleaseTime.js │ ├── settlementFailed.js │ ├── whitelistClaimant.js │ ├── increaseStake.js │ ├── finalizeWithdrawStake.js │ ├── increaseClaimFee.js │ ├── DelphiStake.js │ ├── proposeSettlement.js │ ├── acceptSettlement.js │ ├── ruleOnClaim.js │ ├── openClaim.js │ └── openClaimWithoutSettlement.js │ ├── utils.js │ └── DelphiVoting │ ├── revealVote.js │ ├── commitVote.js │ ├── submitRuling.js │ └── claimFee.js ├── contracts ├── Migrations.sol ├── inherited │ └── Proxy.sol ├── DelphiVotingFactory.sol ├── LookupTable.sol ├── DelphiStakeFactory.sol ├── DelphiStake.sol └── DelphiVoting.sol ├── package.json └── truffle.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/** 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | build/* 3 | secrets.json 4 | *.swp 5 | /coverage/ 6 | coverage.json 7 | installed_contracts/ 8 | 9 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const Migrations = artifacts.require('./Migrations.sol'); 4 | 5 | module.exports = deployer => deployer.deploy(Migrations); 6 | 7 | -------------------------------------------------------------------------------- /conf/dsConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "initialStake": "100", 3 | "stakeTokenAddr": "0x0000000000000000000000000000000000000000", 4 | "data": "i love cats", 5 | "deadline": "9999999999999999999999999999999999", 6 | "arbiter": "0x0000000000000000000000000000000000000000", 7 | "minFee": "10" 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Delphi 2 | A simple mechanism for staking and claims 3 | 4 | ## Initialize 5 | The only environmental dependency you need is Node 8. 6 | ``` 7 | npm install 8 | npm run compile 9 | ``` 10 | 11 | ## Tests 12 | The repo has a comprehensive test suite. You can run it with `npm run test`. 13 | 14 | -------------------------------------------------------------------------------- /migrations/2_delphistakefactory.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const DelphiStake = artifacts.require('DelphiStake.sol'); 4 | const DelphiStakeFactory = artifacts.require('DelphiStakeFactory.sol'); 5 | 6 | module.exports = deployer => deployer.deploy(DelphiStake) 7 | .then(() => deployer.deploy(DelphiStakeFactory, DelphiStake.address)); 8 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // use the local version of truffle 3 | testCommand: '../node_modules/.bin/truffle test --network coverage', 4 | // start blockchain on the same port specified in truffle.js 5 | // use the default delicious Ganache mnemonic 6 | testrpcOptions: '-p 7545 -m "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"' 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /ethpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "package_name": "Delphi", 3 | "version": "0.0.1", 4 | "description": "", 5 | "authors": [ 6 | "Mike Goldin", 7 | "Isaac Kang", 8 | "Terry Li", 9 | "Irene Lin", 10 | "Cem Ozer", 11 | "Aspyn Palatnick", 12 | "Yorke Rhodes", 13 | "Mira Zeitlin" 14 | ], 15 | "keywords": [ 16 | "consensys", 17 | "plcr", 18 | "tokens", 19 | "tcr" 20 | ], 21 | "dependencies": { 22 | "tokens": "1.0.0", 23 | "tcr": "1.1.0", 24 | "democratic-parameterizer": "2.0.0", 25 | "dll": "1.0.4" 26 | }, 27 | "license": "Apache 2.0" 28 | } 29 | -------------------------------------------------------------------------------- /migrations/4_delphivoting_factory.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const DelphiVotingFactory = artifacts.require('DelphiVotingFactory.sol'); 4 | const DemocraticParameterizerFactory = 5 | artifacts.require('democratic-parameterizer/DemocraticParameterizerFactory.sol'); 6 | const DLL = artifacts.require('dll/DLL.sol'); 7 | 8 | module.exports = (deployer) => { 9 | deployer.deploy(DLL); 10 | deployer.link(DLL, DelphiVotingFactory); 11 | 12 | return deployer.deploy(DemocraticParameterizerFactory) 13 | .then(() => deployer.deploy(DelphiVotingFactory, DemocraticParameterizerFactory.address)); 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /test/js/DelphiStakeFactory/DelphiStakeFactory.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStakeFactory = artifacts.require('DelphiStakeFactory'); 5 | const DelphiStake = artifacts.require('DelphiStake'); 6 | 7 | contract('DelphiStakeFactory', () => { 8 | describe('Function: DelphiStakeFactory', () => { 9 | it('should set the master contract correctly', async () => { 10 | const ds = await DelphiStake.new(); 11 | const df = await DelphiStakeFactory.new(ds.address); 12 | 13 | assert.strictEqual(await df.masterCopy.call(), ds.address, 'The master contract did not load properly'); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | // Useless imports to force compiles for the test pipeline 4 | import "plcr-revival/PLCRFactory.sol"; 5 | import "tcr/ParameterizerFactory.sol"; 6 | import "tcr/RegistryFactory.sol"; 7 | 8 | contract Migrations { 9 | address public owner; 10 | uint public last_completed_migration; 11 | 12 | modifier restricted() { 13 | if (msg.sender == owner) _; 14 | } 15 | 16 | function Migrations() public { 17 | owner = msg.sender; 18 | } 19 | 20 | function setCompleted(uint completed) restricted public { 21 | last_completed_migration = completed; 22 | } 23 | 24 | function upgrade(address new_address) restricted public { 25 | Migrations upgraded = Migrations(new_address); 26 | upgraded.setCompleted(last_completed_migration); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delphi", 3 | "version": "0.0.1", 4 | "description": "A decentralized dispute arbitration system", 5 | "scripts": { 6 | "install": "truffle install", 7 | "coverage": "solidity-coverage", 8 | "compile": "truffle compile", 9 | "test": "npm run lint ./ && truffle test", 10 | "fix": "eslint --fix", 11 | "lint": "eslint", 12 | "stop": "sudo kill `sudo lsof -t -i:9545`" 13 | }, 14 | "license": "MIT", 15 | "dependencies": { 16 | "ethjs-provider-http": "0.1.6", 17 | "ethjs-rpc": "0.1.9", 18 | "bignumber.js": "4.0.4", 19 | "ethereumjs-abi": "0.6.5", 20 | "truffle": "4.1.7", 21 | "truffle-hdwallet-provider": "0.0.3", 22 | "web3": "1.0.0-beta.34" 23 | }, 24 | "devDependencies": { 25 | "eslint": "4.4.1", 26 | "eslint-config-airbnb-base": "11.3.1", 27 | "eslint-plugin-import": "2.7.0", 28 | "solidity-coverage": "^0.5.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | const HDWalletProvider = require('truffle-hdwallet-provider'); 2 | const fs = require('fs'); 3 | 4 | let secrets; 5 | let mnemonic = ''; 6 | 7 | if (fs.existsSync('secrets.json')) { 8 | secrets = JSON.parse(fs.readFileSync('secrets.json', 'utf8')); 9 | mnemonic = secrets.mnemonic; 10 | } 11 | 12 | module.exports = { 13 | networks: { 14 | rinkeby: { 15 | provider: () => new HDWalletProvider(mnemonic, 'https://rinkeby.infura.io'), 16 | network_id: '*', 17 | gas: 4500000, 18 | gasPrice: 25000000000, 19 | }, 20 | ganache: { 21 | host: 'localhost', 22 | network_id: '*', 23 | port: 8545, // <-- If you change this, also set the port option in .solcover.js. 24 | gas: 4500000, 25 | gasPrice: 25000000000, 26 | }, 27 | // config for solidity-coverage 28 | coverage: { 29 | host: 'localhost', 30 | network_id: '*', 31 | port: 7545, // <-- If you change this, also set the port option in .solcover.js. 32 | gas: 0xfffffffffff, // <-- Use this high gas value 33 | gasPrice: 0x01, // <-- Use this low gas price 34 | }, 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /migrations/3_optional_tcr_factory.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | /* This migration deploys the TCR Factory and its constituent contracts, and should only be 4 | * executed when the network is either "test" or "coverage". In production, the TCR should be 5 | * deployed from a well-known mainnet factory. 6 | */ 7 | 8 | const DLL = artifacts.require('dll/DLL.sol'); 9 | const AttributeStore = artifacts.require('attrstore/AttributeStore.sol'); 10 | const PLCRFactory = artifacts.require('plcr-revival/PLCRFactory.sol'); 11 | const ParameterizerFactory = artifacts.require('tcr/ParameterizerFactory.sol'); 12 | const RegistryFactory = artifacts.require('tcr/RegistryFactory.sol'); 13 | 14 | module.exports = (deployer, network) => { 15 | if (network === 'test' || network === 'coverage') { 16 | deployer.deploy(DLL); 17 | deployer.deploy(AttributeStore); 18 | 19 | deployer.link(DLL, PLCRFactory); 20 | deployer.link(AttributeStore, PLCRFactory); 21 | 22 | return deployer.deploy(PLCRFactory) 23 | .then(() => deployer.deploy(ParameterizerFactory, PLCRFactory.address)) 24 | .then(() => deployer.deploy(RegistryFactory, ParameterizerFactory.address)); 25 | } 26 | 27 | return deployer; 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /contracts/inherited/Proxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | 4 | /// @title Proxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. 5 | /// @author Stefan George - 6 | contract Proxy { 7 | 8 | address masterCopy; 9 | 10 | /// @dev Constructor function sets address of master copy contract. 11 | /// @param _masterCopy Master copy address. 12 | function Proxy(address _masterCopy) 13 | public 14 | { 15 | require(_masterCopy != 0); 16 | masterCopy = _masterCopy; 17 | } 18 | 19 | /// @dev Fallback function forwards all transactions and returns all received return data. 20 | function () 21 | external 22 | payable 23 | { 24 | assembly { 25 | let masterCopy := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) 26 | calldatacopy(0, 0, calldatasize()) 27 | let success := delegatecall(not(0), masterCopy, 0, calldatasize(), 0, 0) 28 | returndatacopy(0, 0, returndatasize()) 29 | switch success 30 | case 0 { revert(0, returndatasize()) } 31 | default { return(0, returndatasize()) } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /conf/tcrConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "paramDefaults": { 3 | "minDeposit": 10, 4 | "pMinDeposit": 100, 5 | "applyStageLength": 600, 6 | "pApplyStageLength": 1200, 7 | "commitStageLength": 600, 8 | "pCommitStageLength": 1100, 9 | "revealStageLength": 600, 10 | "pRevealStageLength": 1200, 11 | "dispensationPct": 50, 12 | "pDispensationPct": 50, 13 | "voteQuorum": 50, 14 | "pVoteQuorum": 50 15 | }, 16 | "name": "The TestChain Registry", 17 | "token": { 18 | "address": "0x337cDDa6D41A327c5ad456166CCB781a9722AFf9", 19 | "deployToken": true, 20 | "decimals": "18", 21 | "name": "TestCoin", 22 | "symbol": "TEST", 23 | "supply": "1000000000000000000000000000", 24 | "tokenHolders": [ 25 | { "address": "0x627306090abaB3A6e1400e9345bC60c78a8BEf57", 26 | "amount": "200000000000000000000000000" }, 27 | { "address": "0xf17f52151EbEF6C7334FAD080c5704D77216b732", 28 | "amount": "200000000000000000000000000" }, 29 | { "address": "0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef", 30 | "amount": "200000000000000000000000000" }, 31 | { "address": "0x821aEa9a577a9b44299B9c15c88cf3087F3b5544", 32 | "amount": "200000000000000000000000000" }, 33 | { "address": "0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2", 34 | "amount": "200000000000000000000000000" } 35 | ] 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /test/js/DelphiVotingFactory/makeDelphiVoting.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiVotingFactory = artifacts.require('DelphiVotingFactory.sol'); 5 | const DelphiVoting = artifacts.require('DelphiVoting.sol'); 6 | const DemocraticParameterizer = 7 | artifacts.require('democratic-parameterizer/DemocraticParameterizer.sol'); 8 | 9 | const web3 = require('web3'); 10 | 11 | const solkeccak = web3.utils.soliditySha3; 12 | 13 | contract('DelphiVotingFactory', () => { 14 | describe('Function: makeDelphiVoting', () => { 15 | let dvf; 16 | 17 | beforeEach(async () => { 18 | dvf = await DelphiVotingFactory.deployed(); 19 | }); 20 | 21 | it('should deploy a new DelphiVoting contract with a 100 second voting period', async () => { 22 | const receipt = await dvf.makeDelphiVoting(2666, 5, 23 | [solkeccak('parameterizerVotingPeriod')], [100]); 24 | const dv = DelphiVoting.at(receipt.logs[0].args.delphiVoting); 25 | const dp = DemocraticParameterizer.at(await dv.parameterizer.call()); 26 | 27 | const storedPVP = await dp.get.call('parameterizerVotingPeriod'); 28 | assert.strictEqual(storedPVP.toString(10), '100', 'The DelphiVoting contract was not ' + 29 | 'initialized correctly.'); 30 | 31 | const storedAS = await dv.arbiterSet.call(); 32 | assert.strictEqual(parseInt(storedAS, 16), 2666, 'The DelphiVoting contract was not ' + 33 | 'initialized with the correct arbiter set address'); 34 | }); 35 | }); 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /contracts/DelphiVotingFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.20; 2 | 3 | import "democratic-parameterizer/DemocraticParameterizerFactory.sol"; 4 | import "./DelphiVoting.sol"; 5 | import "plcr-revival/ProxyFactory.sol"; 6 | 7 | contract DelphiVotingFactory { 8 | 9 | event newDelphiVoting(address creator, address delphiVoting, address arbiterSet, 10 | address parameterizer); 11 | 12 | ProxyFactory public proxyFactory; 13 | DemocraticParameterizerFactory public parameterizerFactory; 14 | DelphiVoting public canonizedDelphiVoting; 15 | 16 | /// @dev constructor sets the parameterizerFactory address, deploys a new canonical 17 | /// DelphiVoting contract and a proxyFactory. 18 | constructor(address _parameterizerFactory) { 19 | parameterizerFactory = DemocraticParameterizerFactory(_parameterizerFactory); 20 | canonizedDelphiVoting = new DelphiVoting(); 21 | proxyFactory = new ProxyFactory(); 22 | } 23 | 24 | /* 25 | @dev deploys and initializes a new PLCRVoting contract that consumes a token at an address 26 | supplied by the user. 27 | @param _token an EIP20 token to be consumed by the new PLCR contract 28 | */ 29 | function makeDelphiVoting(address _arbiterSet, uint _feeDecayValue, bytes32[] _paramKeys, 30 | uint[] _paramValues) 31 | public returns (DelphiVoting dv) { 32 | address parameterizer = parameterizerFactory.createDemocraticParameterizer( 33 | _arbiterSet, _paramKeys, _paramValues 34 | ); 35 | 36 | dv = DelphiVoting(proxyFactory.createProxy(canonizedDelphiVoting, "")); 37 | dv.init(_arbiterSet, parameterizer, _feeDecayValue); 38 | 39 | emit newDelphiVoting(msg.sender, dv, _arbiterSet, parameterizer); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /contracts/LookupTable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract LookupTable { 4 | // lt is the lookup table. The value at index i is the total percentage of the fee which will 5 | // have been allocated for all arbiters 0..i. As a user, you probably do not want to call this 6 | // directly. getGuaranteedPercentageForIndex is probably what you want. 7 | uint[] public lt; 8 | // computed is the number of values computed for the lookup table so far. 9 | uint public computed; 10 | // decayValue is a magic number used to compute values in the lookup table. 11 | uint public decayValue; 12 | 13 | constructor(uint _decayValue) public { 14 | decayValue = _decayValue; 15 | lt.push(100 / decayValue); 16 | computed = 0; 17 | } 18 | 19 | /* 20 | @dev computes the percentage of a fee an arbiter is owed 21 | @param _index the zero-indexed order in which an arbiter committed a vote to the plurality set 22 | @return the percentage of a fee the arbiter at the given index is owed 23 | */ 24 | function getGuaranteedPercentageForIndex(uint _index) public returns (uint) { 25 | // If the value at this index is not available, compute it. 26 | uint lti = computeLookupTableValues(_index); 27 | 28 | if(_index == 0) { 29 | return lti; 30 | } else { 31 | return lti - lt[_index - 1]; 32 | } 33 | } 34 | 35 | /* 36 | @dev recursive function computes lookup table values. Does nothing if a value is already stored 37 | at the provided index. 38 | @param _index the lookup table index to compute a value for 39 | @return the total percentage of a fee which will have been allocated for all arbiter 0..i. 40 | */ 41 | function computeLookupTableValues(uint _index) internal returns (uint) { 42 | // If we have not computed a value for this index yet, compute it. 43 | if(_index > computed) { 44 | // Computing i always requires the value of i - 1. 45 | uint previousLTValue = computeLookupTableValues(_index - 1); 46 | 47 | // Compute i and append it to the end of the lookupTable 48 | lt.push(((100 - previousLTValue) / decayValue) + previousLTValue); 49 | computed++; 50 | } 51 | 52 | // Return the value in the lookup table at the provided index. 53 | return lt[_index]; 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /contracts/DelphiStakeFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | import "./inherited/Proxy.sol"; 3 | import "./DelphiStake.sol"; 4 | 5 | contract DelphiStakeFactory { 6 | 7 | event StakeCreated(uint _stakeId, address _contractAddress); 8 | 9 | DelphiStake[] public stakes; 10 | 11 | address public masterCopy; 12 | 13 | /* 14 | @dev constructor function which sets the master copy of the stake contract 15 | @param _masterCopy the address where the template DelphiStake contract resides 16 | */ 17 | function DelphiStakeFactory(address _masterCopy){ 18 | masterCopy = _masterCopy; 19 | } 20 | 21 | /* 22 | @dev when creating a new Delphi Stake using a proxy contract architecture, a user must 23 | initialialize their stake, depositing their tokens 24 | @param _value the value of the stake in token units 25 | @param _token the address of the token being deposited 26 | @param _minimumFee the minimum fee which must be deposited by both parties for each claim 27 | @param _data a content hash of the relevant associated data describing the stake 28 | @param _claimDeadline the deadline for opening new cliams; the earliest moment that 29 | a stake can be withdrawn by the staker 30 | @param _arbiter the address which is able to rule on open claims 31 | */ 32 | function createDelphiStake(uint _value, EIP20 _token, uint _minimumFee, string _data, uint _stakeReleaseTime, address _arbiter) 33 | public 34 | { 35 | // Revert if the specified value to stake cannot be transferred in 36 | require(_token.transferFrom(msg.sender, this, _value)); 37 | 38 | address newStake = new Proxy(masterCopy); 39 | stakes.push(DelphiStake(newStake)); 40 | 41 | // Approve for the stake's tokens to be transfered into the stake upon initialization 42 | _token.approve(newStake, _value); 43 | 44 | // Initialize the stake and set the staker address as the msg.sender 45 | stakes[stakes.length - 1].initDelphiStake(msg.sender, _value, _token, _minimumFee, _data, _stakeReleaseTime, _arbiter); 46 | 47 | StakeCreated(stakes.length - 1, stakes[stakes.length - 1]); 48 | } 49 | 50 | /* 51 | @dev Getter function to return the total number of stakes which have ever been created 52 | */ 53 | function getNumStakes() 54 | public 55 | constant 56 | returns (uint) 57 | { 58 | return stakes.length; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /test/js/DelphiStake/extendStakeReleaseTime.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | const EIP20 = artifacts.require('EIP20'); 6 | 7 | const utils = require('../utils.js'); 8 | 9 | const conf = utils.getConfig(); 10 | 11 | const fs = require('fs'); 12 | 13 | const config = JSON.parse(fs.readFileSync('./conf/tcrConfig.json')); 14 | 15 | contract('DelphiStake', (accounts) => {// eslint-disable-line 16 | describe('Function: extendStakeReleaseTime', () => { 17 | const [staker, claimant, arbiter] = accounts; 18 | 19 | let ds; 20 | 21 | beforeEach(async () => { 22 | const token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 23 | await token.transfer(claimant, 100000, { from: staker }); 24 | await token.transfer(arbiter, 100000, { from: staker }); 25 | ds = await DelphiStake.new(); 26 | 27 | await token.approve(ds.address, conf.initialStake, { from: staker }); 28 | await token.transfer(arbiter, 1000, { from: staker }); 29 | 30 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 31 | conf.deadline, arbiter, { from: staker }); 32 | }); 33 | 34 | it('should revert if called by anyone but the staker', async () => { 35 | try { 36 | await ds.extendStakeReleaseTime(config.paramDefaults.revealStageLength, { from: claimant }); 37 | } catch (err) { 38 | assert(utils.isEVMRevert(err), err.toString()); 39 | 40 | return; 41 | } 42 | 43 | assert(false, 'expected revert if called by anyone but the staker'); 44 | }); 45 | 46 | it('should revert if the new _stakeReleaseTime is not later than the current stake Release Time', async () => { 47 | try { 48 | await ds.extendStakeReleaseTime('1', { from: staker }); 49 | } catch (err) { 50 | assert(utils.isEVMRevert(err), err.toString()); 51 | 52 | return; 53 | } 54 | 55 | assert(false, 'expected revert if called by anyone but the staker'); 56 | }); 57 | 58 | it('should set the stakeReleaseTime to the _stakeReleaseTime', async () => { 59 | await ds.extendStakeReleaseTime(conf.deadline + 20, { from: staker }); 60 | const stakeReleaseTime = await ds.stakeReleaseTime(); 61 | assert.strictEqual((conf.deadline + 20).toString(10), stakeReleaseTime.toString(10), 'stakeRelease didnt set correctly'); 62 | }); 63 | 64 | it('should emit a ReleaseTimeIncreased event', async () => { 65 | await ds.extendStakeReleaseTime(conf.deadline + 20, { from: staker }).then((status) => { 66 | assert.strictEqual('ReleaseTimeIncreased', status.logs[0].event, 'did not emit the ReleaseTimeIncreased event'); 67 | }); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/js/DelphiStake/settlementFailed.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | 6 | const EIP20 = artifacts.require('EIP20'); 7 | 8 | const utils = require('../utils.js'); 9 | 10 | const BN = require('bignumber.js'); 11 | 12 | const conf = utils.getConfig(); 13 | 14 | contract('DelphiStake', (accounts) => { 15 | describe('Function: settlementFailed', () => { 16 | const [staker, claimant, arbiter, other] = accounts; 17 | 18 | let ds; 19 | let token; 20 | 21 | beforeEach(async () => { 22 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 23 | await token.transfer(claimant, 100000, { from: staker }); 24 | await token.transfer(arbiter, 100000, { from: staker }); 25 | 26 | ds = await DelphiStake.new(); 27 | 28 | await token.approve(ds.address, conf.initialStake, { from: staker }); 29 | await token.transfer(arbiter, 1000, { from: staker }); 30 | 31 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 32 | conf.deadline, arbiter, { from: staker }); 33 | 34 | const claimAmount = new BN('1', 10); 35 | const feeAmount = new BN('10', 10); 36 | 37 | await token.approve(ds.address, feeAmount, { from: claimant }); 38 | 39 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 40 | 41 | await ds.openClaim(claimAmount, feeAmount, '', { from: claimant }); 42 | }); 43 | 44 | it('should revert if called with an out-of-bounds claimId', async () => { 45 | try { 46 | await ds.settlementFailed(1, { from: staker }); 47 | } catch (err) { 48 | return; 49 | } 50 | assert(false, 'expected revert if called with an out-of-bounds claimId'); 51 | }); 52 | 53 | it('should revert if called by anyone but the staker or the claimant corresponding to the claimId', async () => { 54 | try { 55 | await ds.settlementFailed(0, { from: other }); 56 | } catch (err) { 57 | return; 58 | } 59 | assert(false, 'expected revert if called by anyone but the staker or the claimant corresponding to the claimId'); 60 | }); 61 | 62 | it('should revert if settlement has already failed', async () => { 63 | await ds.settlementFailed(0, { from: claimant }); 64 | try { 65 | await ds.settlementFailed(0, { from: claimant }); 66 | } catch (err) { 67 | return; 68 | } 69 | assert(false, 'expected revert if settlement has already failed '); 70 | }); 71 | it('should emit the SettlementFailed event', async () => { 72 | await ds.settlementFailed(0, { from: claimant }).then((status) => { 73 | assert.strictEqual('SettlementFailed', status.logs[0].event, 'did not emit the SettlementFailed event'); 74 | }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/js/DelphiStake/whitelistClaimant.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | const EIP20 = artifacts.require('EIP20'); 6 | 7 | const utils = require('../utils.js'); 8 | const BN = require('bignumber.js'); 9 | 10 | const conf = utils.getConfig(); 11 | 12 | contract('DelphiStake', (accounts) => {//eslint-disable-line 13 | describe('Function: whitelistClaimant', () => { 14 | const [staker, claimant, arbiter, other] = accounts; 15 | 16 | let ds; 17 | let token; 18 | 19 | beforeEach(async () => { 20 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 21 | await token.transfer(claimant, 100000, { from: staker }); 22 | await token.transfer(arbiter, 100000, { from: staker }); 23 | 24 | ds = await DelphiStake.new(); 25 | 26 | await token.approve(ds.address, conf.initialStake, { from: staker }); 27 | await token.transfer(arbiter, 1000, { from: staker }); 28 | 29 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 30 | conf.deadline, arbiter, { from: staker }); 31 | 32 | const feeAmount = new BN('10', 10); 33 | 34 | await token.approve(ds.address, feeAmount, { from: claimant }); 35 | }); 36 | 37 | it('Should revert if called by arbiter', async () => { 38 | try { 39 | await ds.whitelistClaimant(claimant, conf.deadline, { from: arbiter }); 40 | } catch (err) { 41 | assert(utils.isEVMRevert(err), err.toString()); 42 | return; 43 | } 44 | 45 | assert(false, 'Expected revert if called by arbiter'); 46 | }); 47 | 48 | it('Should revert if called by anyone other than staker', async () => { 49 | try { 50 | await ds.whitelistClaimant(claimant, conf.deadline, { from: other }); 51 | } catch (err) { 52 | assert(utils.isEVMRevert(err), err.toString()); 53 | return; 54 | } 55 | 56 | assert(false, 'Expected revert if called by anyone but the staker'); 57 | }); 58 | 59 | it('Should properly set the _claimant address to the given deadline', async () => { 60 | await ds.whitelistClaimant(claimant, '1000', { from: staker }); 61 | const whitelisten = await ds.whitelistedDeadlines(claimant, { from: staker }); 62 | assert.strictEqual(whitelisten.toString(10), '1000', 63 | 'deadline didnt set correctly'); 64 | }); 65 | 66 | it('Should allow staker to extend the deadline for someone who has already been whitelisted', async () => { 67 | await ds.whitelistClaimant(claimant, '1000', { from: staker }); 68 | let whitelisten = await ds.whitelistedDeadlines(claimant, { from: staker }); 69 | assert.strictEqual(whitelisten.toString(10), '1000', 70 | 'deadline didnt set correctly'); 71 | 72 | await ds.whitelistClaimant(claimant, '1001', { from: staker }); 73 | whitelisten = await ds.whitelistedDeadlines(claimant, { from: staker }); 74 | assert.strictEqual(whitelisten.toString(10), '1001', 75 | 'deadline didnt set correctly'); 76 | }); 77 | 78 | it('Should emit ClaimantWhitelisted event', async () => { 79 | await ds.whitelistClaimant(claimant, '1000', { from: staker }).then((status) => { 80 | assert.strictEqual('ClaimantWhitelisted', status.logs[0].event, 'did not emit the ClaimantWhitelisted event'); 81 | }); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/js/DelphiStake/increaseStake.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | const EIP20 = artifacts.require('EIP20'); 6 | 7 | const utils = require('../utils.js'); 8 | 9 | const conf = utils.getConfig(); 10 | 11 | const BN = require('bignumber.js'); 12 | 13 | contract('DelphiStake', (accounts) => { 14 | describe('Function: increaseStake', () => { 15 | const [staker, claimant, arbiter, dave] = accounts; 16 | const incAmount = '1'; 17 | 18 | let ds; 19 | let token; 20 | let initialStake; 21 | 22 | beforeEach(async () => { 23 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 24 | await token.transfer(claimant, 100000, { from: staker }); 25 | await token.transfer(arbiter, 100000, { from: staker }); 26 | 27 | ds = await DelphiStake.new(); 28 | 29 | await token.approve(ds.address, conf.initialStake, { from: staker }); 30 | 31 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 32 | conf.deadline, arbiter, { from: staker }); 33 | 34 | // approve the staker and dave to spec incAmount to increase the stake 35 | await token.approve(ds.address, incAmount, { from: staker }); 36 | await token.approve(ds.address, incAmount, { from: dave }); 37 | 38 | initialStake = new BN(conf.initialStake, 10); 39 | }); 40 | 41 | it('should revert if called by any entity other than the staker', async () => { 42 | try { 43 | await ds.increaseStake(incAmount, { from: dave }); 44 | } catch (err) { 45 | assert(utils.isEVMRevert(err), err.toString()); 46 | 47 | const finalStake = await ds.claimableStake.call(); 48 | assert.strictEqual(initialStake.toString(10), finalStake.toString(10), 49 | 'the stake mysteriously incremented'); 50 | 51 | // TODO: check actual balances 52 | return; 53 | } 54 | 55 | assert(false, 'should not have allowed somebody other than the staker to increase the stake'); 56 | }); 57 | 58 | it('should revert if _value does not equal the tokens transferred', async () => { 59 | try { 60 | await ds.increaseStake(incAmount + 1, { from: staker }); 61 | } catch (err) { 62 | assert(utils.isEVMRevert(err), err.toString()); 63 | 64 | const finalStake = await ds.claimableStake.call(); 65 | assert.strictEqual(initialStake.toString(10), finalStake.toString(10), 66 | 'the stake mysteriously incremented'); 67 | 68 | const tokenBalance = await token.balanceOf(ds.address); 69 | 70 | assert.strictEqual('100', tokenBalance.toString(10), 71 | 'did not properly deposit the expected number of tokens'); 72 | return; 73 | } 74 | 75 | assert(false, 'should not have allowed the staker to increase the stake by an amount other ' + 76 | 'than that they explicitly specified'); 77 | }); 78 | 79 | it('should increment the stake by _value', async () => { 80 | await ds.increaseStake(incAmount, { from: staker }); 81 | 82 | const finalStake = await ds.claimableStake.call(); 83 | 84 | assert.strictEqual(initialStake.add(new BN(incAmount, 10)).toString(10), 85 | finalStake.toString(10), 86 | 'did not properly increment stake'); 87 | 88 | const tokenBalance = await token.balanceOf(ds.address); 89 | assert.strictEqual('101', tokenBalance.toString(10), 90 | 'did not properly deposit the expected number of tokens'); 91 | }); 92 | 93 | it('should emit a StakeIncreased event', async () => { 94 | await ds.increaseStake(incAmount, { from: staker }).then((status) => { 95 | assert.strictEqual('StakeIncreased', status.logs[0].event, 'did not emit the StakeIncreased event'); 96 | }); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/js/DelphiStake/finalizeWithdrawStake.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | const EIP20 = artifacts.require('EIP20'); 6 | 7 | const utils = require('../utils.js'); 8 | const BN = require('bignumber.js'); 9 | 10 | const conf = utils.getConfig(); 11 | 12 | const Web3 = require('web3'); 13 | 14 | const web3 = new Web3(Web3.givenProvider || 'ws://localhost:7545'); 15 | 16 | 17 | contract('DelphiStake', (accounts) => { 18 | describe('Function: withdrawStake', () => { 19 | const [staker, claimant, arbiter] = accounts; 20 | 21 | let ds; 22 | let token; 23 | 24 | beforeEach(async () => { 25 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 26 | await utils.as(staker, token.transfer, claimant, 100000); 27 | 28 | ds = await DelphiStake.new(); 29 | 30 | await utils.as(staker, token.approve, ds.address, conf.initialStake); 31 | 32 | await utils.as(staker, token.transfer, arbiter, 1000); 33 | 34 | const timeBlock = await web3.eth.getBlock(await web3.eth.getBlockNumber()); 35 | const tims = timeBlock.timestamp + 6; 36 | 37 | await utils.as(staker, ds.initDelphiStake, staker, conf.initialStake, token.address, 38 | conf.minFee, conf.data, tims, arbiter); 39 | }); 40 | 41 | it('should revert if called by any entity other than the staker', async () => { 42 | try { 43 | await utils.as(claimant, ds.withdrawStake); 44 | } catch (err) { 45 | assert(utils.isEVMRevert(err), err.toString()); 46 | return; 47 | } 48 | 49 | assert(false, 'expected revert if called by any entity other than the staker'); 50 | }); 51 | it('should revert if called before the release time', async () => { 52 | try { 53 | await utils.as(staker, ds.withdrawStake); 54 | } catch (err) { 55 | assert(utils.isEVMRevert(err), err.toString()); 56 | return; 57 | } 58 | 59 | assert(false, 'expected revert if called before the release time'); 60 | }); 61 | it('should revert if open claims remain', async () => { 62 | const timeBlock = await web3.eth.getBlock(await web3.eth.getBlockNumber()); 63 | 64 | const claimAmount = new BN('1', 10); 65 | const feeAmount = new BN('10', 10); 66 | 67 | await utils.as(claimant, token.approve, ds.address, feeAmount); 68 | 69 | await utils.as(staker, ds.whitelistClaimant, claimant, timeBlock.timestamp + 30); 70 | 71 | await utils.as(claimant, ds.openClaim, claimAmount, feeAmount, ''); 72 | 73 | await utils.increaseTime(8000); 74 | try { 75 | await utils.as(staker, ds.withdrawStake); 76 | } catch (err) { 77 | assert(utils.isEVMRevert(err), err.toString()); 78 | return; 79 | } 80 | 81 | assert(false, 'expected revert if open claims remain'); 82 | }); 83 | 84 | it('should set claimableStake to zero', async () => { 85 | await utils.increaseTime(5000); 86 | await utils.as(staker, ds.withdrawStake); 87 | const claimableStake = await ds.claimableStake(); 88 | assert.strictEqual(claimableStake.toString(10), '0', 'claimableStake is not zero'); 89 | }); 90 | 91 | it('should transfer the old stake amount to the staker', async () => { 92 | await utils.increaseTime(10000); 93 | await utils.as(staker, ds.withdrawStake); 94 | const stakerCurrentBalance = await token.balanceOf(staker); 95 | assert.strictEqual(stakerCurrentBalance.toString(10), '899000', 96 | 'claimableStake doesnt withdraw correctly'); 97 | }); 98 | 99 | it('should emit a StakeWithdrawn event', async () => { 100 | await utils.increaseTime(10000); 101 | 102 | await ds.withdrawStake({ from: staker }).then((status) => { 103 | assert.strictEqual('StakeWithdrawn', status.logs[0].event, 104 | 'did not emit the StakeWithdrawn event'); 105 | }); 106 | }); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/js/DelphiStake/increaseClaimFee.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | const EIP20 = artifacts.require('EIP20'); 6 | 7 | const utils = require('../utils.js'); 8 | const BN = require('bignumber.js'); 9 | 10 | const conf = utils.getConfig(); 11 | 12 | contract('DelphiStake', (accounts) => { 13 | describe('Function: increaseClaimFee', () => { 14 | const [staker, claimant, arbiter] = accounts; 15 | 16 | let ds; 17 | let token; 18 | 19 | beforeEach(async () => { 20 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 21 | await token.transfer(claimant, 100000, { from: staker }); 22 | await token.transfer(arbiter, 100000, { from: staker }); 23 | 24 | ds = await DelphiStake.new(); 25 | 26 | await token.approve(ds.address, conf.initialStake, { from: staker }); 27 | await token.transfer(arbiter, 1000, { from: staker }); 28 | 29 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 30 | conf.deadline, arbiter, { from: staker }); 31 | 32 | const claimAmount = new BN('1', 10); 33 | const feeAmount = new BN('10', 10); 34 | 35 | await token.approve(ds.address, feeAmount, { from: claimant }); 36 | 37 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 38 | 39 | await ds.openClaim(claimAmount, feeAmount, '', { from: claimant }); 40 | }); 41 | 42 | it('should revert if called with an out-of-bounds claimId', async () => { 43 | try { 44 | await utils.as(arbiter, ds.increaseClaimFee, 3, 11); 45 | } catch (err) { 46 | assert(utils.isEVMRevert(err), err.toString()); 47 | return; 48 | } 49 | 50 | assert(false, 'expected revert if called with an out-of-bounds claimId'); 51 | }); 52 | 53 | it('should revert if called on a claim which has already been ruled upon', async () => { 54 | await ds.settlementFailed(0, { from: claimant }); 55 | await ds.ruleOnClaim(0, '0', { from: arbiter }); 56 | 57 | try { 58 | await utils.as(arbiter, ds.increaseClaimFee, 0, 2); 59 | } catch (err) { 60 | assert(utils.isEVMRevert(err), err.toString()); 61 | return; 62 | } 63 | assert(false, 'expected revert if called on a claim which has already been ruled upon'); 64 | }); 65 | 66 | it('should revert if settlement has not yet failed', async () => { 67 | try { 68 | await utils.as(arbiter, ds.increaseClaimFee, 0, 11); 69 | } catch (err) { 70 | assert(utils.isEVMRevert(err), err.toString()); 71 | return; 72 | } 73 | 74 | assert(false, 'expected revert if settlement has not yet failed'); 75 | }); 76 | 77 | it('should transfer the increase _amount from the sender to the contract', async () => { 78 | assert.strictEqual((await token.balanceOf(ds.address)).toString(10), '110', 'claim surplus fee incorrectly'); 79 | 80 | await ds.settlementFailed(0, { from: staker }); 81 | 82 | await token.approve(ds.address, 1, { from: staker }); 83 | await ds.increaseClaimFee(0, 1, { from: staker }); 84 | 85 | assert.strictEqual((await token.balanceOf(ds.address)).toString(10), '111', 'claim surplus fee incorrectly'); 86 | }); 87 | 88 | it('should increase the surplus fee by the _amount', async () => { 89 | await ds.settlementFailed(0, { from: staker }); 90 | 91 | await token.approve(ds.address, 1, { from: staker }); 92 | await ds.increaseClaimFee(0, 1, { from: staker }); 93 | 94 | const claim1 = await ds.claims.call('0'); 95 | assert.strictEqual(claim1[3].toString(10), '1', 'claim surplus fee incorrectly'); 96 | }); 97 | 98 | it('should emit a FeeIncreased event', async () => { 99 | await ds.settlementFailed(0, { from: staker }); 100 | 101 | await token.approve(ds.address, 1, { from: staker }); 102 | await ds.increaseClaimFee(0, 1, { from: staker }).then((status) => { 103 | assert.strictEqual('FeeIncreased', status.logs[0].event, 'did not emit the FeeIncreased event'); 104 | }); 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /test/js/DelphiStake/DelphiStake.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | const EIP20 = artifacts.require('EIP20'); 6 | 7 | const utils = require('../utils.js'); 8 | 9 | const conf = utils.getConfig(); 10 | 11 | contract('DelphiStake', (accounts) => { 12 | describe('Function: DelphiStake', () => { 13 | const [staker, , arbiter] = accounts; 14 | 15 | let ds; 16 | let token; 17 | 18 | beforeEach(async () => { 19 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 20 | ds = await DelphiStake.new(); 21 | 22 | await token.approve(ds.address, conf.initialStake, { from: staker }); 23 | }); 24 | 25 | it('should instantiate the contract with the expected values', async () => { 26 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 27 | conf.deadline, arbiter, { from: staker }); 28 | 29 | const stake = await ds.claimableStake.call(); 30 | assert.strictEqual(stake.toString(10), conf.initialStake, 31 | 'the stake was initialized improperly'); 32 | 33 | const tokenaddress = await ds.token.call(); 34 | assert.strictEqual(token.address, tokenaddress, 35 | 'the stake token address was initialized improperly'); 36 | 37 | const data = await ds.data.call(); 38 | assert.strictEqual(data, conf.data, 'the stake data was initialized improperly'); 39 | 40 | const deadline = await ds.stakeReleaseTime.call(); 41 | assert.strictEqual(deadline.toString(10), conf.deadline, 42 | 'the deadline was initialized improperly'); 43 | 44 | const storedArbiter = await ds.arbiter.call(); 45 | assert.strictEqual(arbiter, storedArbiter, 'the arbiter was initialized improperly'); 46 | 47 | const balance = await token.balanceOf(ds.address); 48 | assert.strictEqual(balance.toString(10), stake.toString(10), 'the contract\'s balance and stake did not match'); 49 | }); 50 | 51 | it('should revert when _value does not equal the amount of tokens sent', async () => { 52 | try { 53 | await utils.as(staker, ds.initDelphiStake, staker, conf.initialStake + 100, token.address, 54 | conf.minFee, conf.data, conf.deadline, arbiter); 55 | } catch (err) { 56 | assert(utils.isEVMRevert(err), err.toString()); 57 | 58 | return; 59 | } 60 | assert(false, 'did not revert after trying to init the stake with an incorrect amount of tokens'); 61 | }); 62 | 63 | it('should revert when trying to call the initialize function more than once', async () => { 64 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 65 | conf.deadline, arbiter, { from: staker }); 66 | 67 | try { 68 | await utils.as(staker, ds.initDelphiStake, staker, conf.initialStake, token.address, 69 | conf.minFee, conf.data, conf.deadline, arbiter); 70 | } catch (err) { 71 | assert(utils.isEVMRevert(err), err.toString()); 72 | 73 | return; 74 | } 75 | assert(false, 'did not revert after trying to init the stake more than once'); 76 | }); 77 | 78 | it('should revert when trying to call the initialize function with a deadline that is before now', async () => { 79 | try { 80 | await utils.as(staker, ds.initDelphiStake, staker, conf.initialStake, token.address, 81 | conf.minFee, conf.data, '1', arbiter); 82 | } catch (err) { 83 | assert(utils.isEVMRevert(err), err.toString()); 84 | 85 | return; 86 | } 87 | assert(false, 'did not revert after trying to call the initialize function with a deadline that is before now'); 88 | }); 89 | 90 | it('should revert when trying to initialize with an arbiter of address(0)', async () => { 91 | try { 92 | await utils.as(staker, ds.initDelphiStake, staker, conf.initialStake, token.address, 93 | conf.minFee, conf.data, conf.deadline, '0x0'); 94 | } catch (err) { 95 | assert(utils.isEVMRevert(err), err.toString()); 96 | 97 | return; 98 | } 99 | assert(false, 'did not revert after trying to initialize with an arbiter of address(0)'); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/js/utils.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const Registry = artifacts.require('Registry.sol'); 4 | const DelphiStake = artifacts.require('DelphiStake.sol'); 5 | const EIP20 = artifacts.require('tokens/eip20/EIP20.sol'); 6 | 7 | const HttpProvider = require('ethjs-provider-http'); 8 | const EthRPC = require('ethjs-rpc'); 9 | const fs = require('fs'); 10 | const abi = require('ethereumjs-abi'); 11 | const BN = require('bignumber.js'); 12 | 13 | const ethRPC = new EthRPC(new HttpProvider('http://localhost:7545')); 14 | 15 | const config = JSON.parse(fs.readFileSync('./conf/tcrConfig.json')); 16 | const delphiConfig = JSON.parse(fs.readFileSync('./conf/dsConfig.json')); 17 | 18 | const utils = { 19 | 20 | initDelphiStake: async (staker, arbiter) => { 21 | const ds = await DelphiStake.deployed(); 22 | const token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 23 | await token.approve(ds.address, delphiConfig.initialStake, { from: staker }); 24 | 25 | await ds.initDelphiStake(delphiConfig.initialStake, token.address, 26 | delphiConfig.minFee, delphiConfig.data, delphiConfig.deadline, 27 | arbiter, { from: staker }); 28 | }, 29 | 30 | makeNewClaim: async (staker, claimant, amount, fee, data, ds) => { 31 | const token = EIP20.at(await ds.token.call()); 32 | 33 | await utils.as(staker, ds.whitelistClaimant, claimant, delphiConfig.deadline); 34 | 35 | await utils.as(claimant, token.approve, ds.address, new BN(fee, 10)); 36 | await utils.as(claimant, ds.openClaim, amount, fee, data); 37 | 38 | const claimId = (await ds.getNumClaims()).sub(1); 39 | 40 | await utils.as(claimant, ds.settlementFailed, claimId); 41 | 42 | return claimId; 43 | }, 44 | 45 | addToWhitelist: async (listingHash, deposit, actor) => { 46 | const registry = await Registry.deployed(); 47 | 48 | await utils.as(actor, registry.apply, listingHash, deposit, ''); 49 | await utils.increaseTime(config.paramDefaults.applyStageLength + 1); 50 | await utils.as(actor, registry.updateStatus, listingHash); 51 | }, 52 | 53 | getClaimId: (stake, claimNumber) => ( 54 | `0x${abi.soliditySHA3(['address', 'uint'], [stake, claimNumber]).toString('hex')}` 55 | ), 56 | 57 | getArbiterListingId: arbiter => ( 58 | `0x${abi.soliditySHA3(['address'], [arbiter]).toString('hex')}` 59 | ), 60 | 61 | getSecretHash: (vote, salt) => ( 62 | `0x${abi.soliditySHA3(['uint', 'uint'], [vote, salt]).toString('hex')}` 63 | ), 64 | 65 | increaseTime: async seconds => 66 | new Promise((resolve, reject) => ethRPC.sendAsync({ 67 | method: 'evm_increaseTime', 68 | params: [seconds], 69 | }, (err) => { 70 | if (err) reject(err); 71 | resolve(); 72 | })) 73 | .then(() => new Promise((resolve, reject) => ethRPC.sendAsync({ 74 | method: 'evm_mine', 75 | params: [], 76 | }, (err) => { 77 | if (err) reject(err); 78 | resolve(); 79 | }))), 80 | 81 | as: (actor, fn, ...args) => { 82 | function detectSendObject(potentialSendObj) { 83 | function hasOwnProperty(obj, prop) { 84 | const proto = obj.constructor.prototype; 85 | return (prop in obj) && 86 | (!(prop in proto) || proto[prop] !== obj[prop]); 87 | } 88 | if (typeof potentialSendObj !== 'object') { return; } 89 | if ( 90 | hasOwnProperty(potentialSendObj, 'from') || 91 | hasOwnProperty(potentialSendObj, 'to') || 92 | hasOwnProperty(potentialSendObj, 'gas') || 93 | hasOwnProperty(potentialSendObj, 'gasPrice') || 94 | hasOwnProperty(potentialSendObj, 'value') 95 | ) { 96 | throw new Error('It is unsafe to use "as" with custom send objects'); 97 | } 98 | } 99 | detectSendObject(args[args.length - 1]); 100 | const sendObject = { from: actor }; 101 | return fn(...args, sendObject); 102 | }, 103 | 104 | isEVMException: err => ( 105 | err.toString().includes('invalid opcode') 106 | ), 107 | 108 | isEVMRevert: err => ( 109 | err.toString().includes('revert') 110 | ), 111 | 112 | getConfig: () => JSON.parse(fs.readFileSync('conf/dsConfig.json')), 113 | 114 | getLog: (logs, event) => logs.find(log => log.event.includes(event)), 115 | 116 | }; 117 | 118 | 119 | module.exports = utils; 120 | -------------------------------------------------------------------------------- /test/js/DelphiStake/proposeSettlement.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | 6 | const EIP20 = artifacts.require('EIP20'); 7 | 8 | const utils = require('../utils.js'); 9 | 10 | const conf = utils.getConfig(); 11 | 12 | contract('DelphiStake', (accounts) => { 13 | describe('Function: proposeSettlement', () => { 14 | const [staker, claimant, arbiter, other] = accounts; 15 | 16 | const claimAmount = '1'; 17 | 18 | let ds; 19 | let token; 20 | 21 | beforeEach(async () => { 22 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 23 | await token.transfer(claimant, 100000, { from: staker }); 24 | await token.transfer(arbiter, 100000, { from: staker }); 25 | 26 | ds = await DelphiStake.new(); 27 | 28 | await token.approve(ds.address, conf.initialStake, { from: staker }); 29 | await token.transfer(arbiter, 1000, { from: staker }); 30 | 31 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 32 | conf.deadline, arbiter, { from: staker }); 33 | 34 | await token.approve(ds.address, conf.minFee, { from: claimant }); 35 | 36 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 37 | 38 | await ds.openClaim(claimAmount, conf.minFee, '', { from: claimant }); 39 | }); 40 | 41 | it('Should revert if called with an out-of-bounds claimId', async () => { 42 | try { 43 | await ds.proposeSettlement(1, claimAmount, { from: claimant }); 44 | } catch (err) { 45 | return; 46 | } 47 | assert(false, 'Expected revert if called with an out-of-bounds claimId'); 48 | }); 49 | 50 | it('Should revert if called by anyone but the staker or the claimant corresponding to the claimId', async () => { 51 | try { 52 | await ds.proposeSettlement(0, claimAmount, { from: other }); 53 | } catch (err) { 54 | return; 55 | } 56 | assert(false, 57 | 'Expected revert if called by anyone but the staker or the claimant corresponding to the claimId'); 58 | }); 59 | 60 | it('Should revert if settlement has failed', async () => { 61 | await ds.settlementFailed(0, { from: staker }); 62 | try { 63 | await ds.proposeSettlement(0, claimAmount, { from: staker }); 64 | } catch (err) { 65 | return; 66 | } 67 | assert(false, 68 | 'Expected to revert if settlement has failed'); 69 | }); 70 | 71 | it('Should revert if the proposed settlement _amount is more than the sum of the amount and fee of the claim in question', async () => { 72 | try { 73 | const amount = parseInt(claimAmount, 10) + parseInt(conf.minFee, 10) + 1; 74 | await ds.proposeSettlement(0, amount, { from: claimant }); 75 | } catch (err) { 76 | return; 77 | } 78 | assert(false, 79 | 'Expected revert if the proposed settlement _amount is less than the sum of the amount and fee of the claim in question'); 80 | }); 81 | 82 | it('Should create a new settlement by the claimant, and have the settlement properly initialize the fields', async () => { 83 | await ds.proposeSettlement(0, claimAmount, { from: claimant }); 84 | 85 | const settlement = await ds.settlements(0, 0, { from: staker }); 86 | 87 | assert.strictEqual(settlement[0].toString(10), claimAmount, 'initialized amount incorrectly'); 88 | assert.strictEqual(settlement[1], false, 'staker agree'); 89 | assert.strictEqual(settlement[2], true, 'claimant did not agree'); 90 | }); 91 | 92 | it('Should create a new settlement by the staker, and have the settlement properly initialize the fields', async () => { 93 | await ds.proposeSettlement(0, claimAmount, { from: staker }); 94 | 95 | const settlement = await ds.settlements(0, 0, { from: staker }); 96 | 97 | assert.strictEqual(settlement[0].toString(10), claimAmount, 'initialized amount incorrectly'); 98 | assert.strictEqual(settlement[1], true, 'staker did not agree'); 99 | assert.strictEqual(settlement[2], false, 'claimant did not agree'); 100 | }); 101 | it('Should emit a SettlementProposed event', async () => { 102 | await ds.proposeSettlement(0, claimAmount, { from: staker }).then((status) => { 103 | assert.strictEqual('SettlementProposed', status.logs[0].event, 'did not emit the SettlementProposed event'); 104 | }); 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /test/js/DelphiStakeFactory/createDelphiStake.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStakeFactory = artifacts.require('DelphiStakeFactory'); 5 | const DelphiStake = artifacts.require('DelphiStake'); 6 | const EIP20 = artifacts.require('EIP20'); 7 | 8 | const utils = require('../utils.js'); 9 | const BN = require('bignumber.js'); 10 | 11 | const conf = utils.getConfig(); 12 | 13 | contract('DelphiStakeFactory', (accounts) => { 14 | describe('Function: createDelphiStake', () => { 15 | const [staker, claimant, arbiter, other] = accounts; 16 | 17 | let df; 18 | let ds; 19 | let token; 20 | 21 | beforeEach(async () => { 22 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 23 | await token.transfer(claimant, 100000, { from: staker }); 24 | await token.transfer(arbiter, 100000, { from: staker }); 25 | await token.transfer(other, 100000, { from: staker }); 26 | 27 | ds = await DelphiStake.new(); 28 | df = await DelphiStakeFactory.new(ds.address); 29 | }); 30 | 31 | it('should allow the creatation of a single stake', async () => { 32 | await token.approve(df.address, conf.initialStake, { from: staker }); 33 | await utils.as(staker, df.createDelphiStake, conf.initialStake, token.address, 34 | conf.minFee, conf.data, conf.deadline, arbiter); 35 | 36 | const numStakes = await df.getNumStakes.call(); 37 | 38 | // Ensure that a stake was created, before doing other checks 39 | assert.strictEqual(numStakes.toString(10), (new BN('1', 10)).toString(10), 'Incorrect number of stakes in the factory'); 40 | 41 | // Get the stake's info 42 | const stake = await DelphiStake.at(await df.stakes.call('0')); 43 | const value = await stake.claimableStake.call(); 44 | const data = await stake.data.call(); 45 | const owner = await stake.staker.call(); 46 | 47 | assert.strictEqual(value.toString(10), conf.initialStake.toString(10), 'Stake not initialized properly'); 48 | assert.strictEqual(data, conf.data, 'Stake not initialized properly'); 49 | assert.strictEqual(owner, staker, 'Stake owner not set correctly'); 50 | }); 51 | 52 | it('should allow the creation of multiple stakes', async () => { 53 | const N = 3; // This is some arbitrary number of stakes to create 54 | 55 | // Create multiple stakes 56 | /* eslint-disable no-await-in-loop */ 57 | for (let i = 0; i < N; i += 1) { 58 | await token.approve(df.address, conf.initialStake, { from: staker }); 59 | await utils.as(staker, df.createDelphiStake, conf.initialStake, token.address, 60 | conf.minFee, i.toString(10), conf.deadline, arbiter); 61 | } 62 | 63 | const numStakes = await df.getNumStakes.call(); 64 | 65 | // Ensure that the stakes were created, before doing other checks 66 | assert.strictEqual(numStakes.toString(10), (new BN('3', 10)).toString(10), 'Incorrect number of stakes in the factory'); 67 | 68 | /* eslint-disable no-await-in-loop */ 69 | for (let i = 0; i < N; i += 1) { 70 | // Get the stake's 71 | const stake = await DelphiStake.at(await df.stakes.call(i.toString(10))); 72 | const value = await stake.claimableStake.call(); 73 | const data = await stake.data.call(); 74 | const owner = await stake.staker.call(); 75 | 76 | assert.strictEqual(value.toString(10), conf.initialStake.toString(10), `Stake #${i.toString(10)} not initialized properly`); 77 | assert.strictEqual(data, i.toString(10), `Stake #${i.toString(10)} not initialized properly`); 78 | assert.strictEqual(owner, staker, `Stake #${i.toString(10)} owner not set correctly`); 79 | } 80 | }); 81 | 82 | it('should revert when _value is less than the amount of tokens sent ', async () => { 83 | await token.approve(df.address, conf.initialStake - 1, { from: staker }); 84 | 85 | try { 86 | await utils.as(staker, df.createDelphiStake, conf.initialStake, token.address, 87 | conf.minFee, conf.data, conf.deadline, arbiter); 88 | } catch (err) { 89 | assert(utils.isEVMRevert(err), err.toString()); 90 | 91 | const numStakes = await df.getNumStakes.call(); 92 | assert.strictEqual(numStakes.toString(10), (new BN('0', 10)).toString(10), 'Incorrect number of stakes in the factory'); 93 | 94 | return; 95 | } 96 | 97 | assert(false, 'Expected stake creation to fail'); 98 | }); 99 | 100 | it('should revert when trying to call the initialize function with a deadline that is before now', async () => { 101 | await token.approve(df.address, conf.initialStake, { from: staker }); 102 | 103 | try { 104 | await utils.as(staker, df.createDelphiStake, conf.initialStake, token.address, 105 | conf.minFee, conf.data, '1', arbiter); 106 | } catch (err) { 107 | assert(utils.isEVMRevert(err), err.toString()); 108 | 109 | return; 110 | } 111 | assert(false, 'did not revert after trying to call the initialize function with a deadline that is before now'); 112 | }); 113 | 114 | it('should revert when trying to initialize with an arbiter of address(0)', async () => { 115 | await token.approve(df.address, conf.initialStake, { from: staker }); 116 | 117 | try { 118 | await utils.as(staker, df.createDelphiStake, conf.initialStake, token.address, 119 | conf.minFee, conf.data, conf.deadline, '0x0'); 120 | } catch (err) { 121 | assert(utils.isEVMRevert(err), err.toString()); 122 | 123 | return; 124 | } 125 | assert(false, 'did not revert after trying to initialize with an arbiter of address(0)'); 126 | }); 127 | 128 | it('should emit a StakeCreated event', async () => { 129 | await token.approve(df.address, conf.initialStake, { from: staker }); 130 | 131 | await df.createDelphiStake(conf.initialStake, token.address, conf.minFee, conf.data, 132 | conf.deadline, arbiter, { from: staker }).then((status) => { 133 | assert.strictEqual('StakeCreated', status.logs[0].event, 'did not emit the StakeCreated event'); 134 | }); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/js/DelphiStake/acceptSettlement.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | 6 | const EIP20 = artifacts.require('EIP20'); 7 | 8 | const utils = require('../utils.js'); 9 | 10 | const BN = require('bignumber.js'); 11 | 12 | const conf = utils.getConfig(); 13 | 14 | contract('DelphiStake', (accounts) => { 15 | describe('Function: acceptSettlement', () => { 16 | const [staker, claimant, arbiter, other] = accounts; 17 | 18 | let ds; 19 | let token; 20 | 21 | beforeEach(async () => { 22 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 23 | await token.transfer(claimant, 100000, { from: staker }); 24 | await token.transfer(arbiter, 100000, { from: staker }); 25 | 26 | ds = await DelphiStake.new(); 27 | 28 | await token.approve(ds.address, conf.initialStake, { from: staker }); 29 | await token.transfer(arbiter, 1000, { from: staker }); 30 | 31 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 32 | conf.deadline, arbiter, { from: staker }); 33 | 34 | const claimAmount = new BN('1', 10); 35 | const feeAmount = new BN('10', 10); 36 | 37 | await token.approve(ds.address, feeAmount, { from: claimant }); 38 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 39 | await ds.openClaim(claimAmount, feeAmount, '', { from: claimant }); 40 | }); 41 | 42 | it('Should revert if called with an out-of-bounds claimId', async () => { 43 | await ds.proposeSettlement(0, 10, { from: staker }); 44 | try { 45 | await ds.acceptSettlement(2, 0, { from: claimant }); 46 | } catch (err) { 47 | return; 48 | } 49 | assert(false, 'Expected revert if called with an out-of-bounds claimId'); 50 | }); 51 | 52 | it('Should revert if called with an out-of-bounds settlementId', async () => { 53 | await ds.proposeSettlement(0, 10, { from: staker }); 54 | try { 55 | await ds.acceptSettlement(0, 2, { from: claimant }); 56 | } catch (err) { 57 | return; 58 | } 59 | assert(false, 'Expected revert if called with an out-of-bounds claimId'); 60 | }); 61 | 62 | it('Should revert if called by anyone but the staker or the claimant corresponding to the claimId', async () => { 63 | await ds.proposeSettlement(0, 0, { from: staker }); 64 | try { 65 | await ds.acceptSettlement(0, 0, { from: other }); 66 | } catch (err) { 67 | return; 68 | } 69 | assert(false, 'Expected revert if called by anyone but the staker or the claimant corresponding to the claimId'); 70 | }); 71 | 72 | it('Should revert if settlement has failed', async () => { 73 | await ds.proposeSettlement(0, 0, { from: staker }); 74 | 75 | await ds.settlementFailed(0, { from: claimant }); 76 | try { 77 | await ds.acceptSettlement(0, 0, { from: claimant }); 78 | } catch (err) { 79 | return; 80 | } 81 | assert(false, 'Expected revert if settlement has failed'); 82 | }); 83 | 84 | it('Should set the stakerAgrees to true when called by a staker on a claimants settlement', async () => { 85 | await ds.proposeSettlement(0, 0, { from: claimant }); 86 | 87 | let settlement = await ds.settlements(0, 0, { from: staker }); 88 | assert.strictEqual(settlement[1], false, 'staker agree'); 89 | await ds.acceptSettlement(0, 0, { from: staker }); 90 | settlement = await ds.settlements(0, 0, { from: staker }); 91 | assert.strictEqual(settlement[1], true, 'staker did not agree'); 92 | 93 | try { 94 | await ds.acceptSettlement(0, 0, { from: other }); 95 | } catch (err) { 96 | return; 97 | } 98 | assert(false, 'Expected revert if other party\' accept settlement '); 99 | }); 100 | 101 | it('Should set the claimantAgrees to true when called by a claimant on a claimants settlement', async () => { 102 | await ds.proposeSettlement(0, 0, { from: claimant }); 103 | const settlement = await ds.settlements(0, 0, { from: staker }); 104 | assert.strictEqual(settlement[2], true, 'claimant did not agree'); 105 | try { 106 | await ds.acceptSettlement(0, 0, { from: other }); 107 | } catch (err) { 108 | return; 109 | } 110 | assert(false, 'Expected revert if other party\' accept settlement '); 111 | }); 112 | 113 | it('Should revert if called by a staker on their own settlement', async () => { 114 | await ds.proposeSettlement(0, 0, { from: staker }); 115 | 116 | try { 117 | await ds.acceptSettlement(0, 0, { from: staker }); 118 | } catch (err) { 119 | return; 120 | } 121 | assert(false, 'Expected revert if called by a staker on their own settlement'); 122 | }); 123 | it('Should revert if called by a claimant on their own settlement', async () => { 124 | await ds.proposeSettlement(0, 0, { from: claimant }); 125 | 126 | try { 127 | await ds.acceptSettlement(0, 0, { from: claimant }); 128 | } catch (err) { 129 | return; 130 | } 131 | assert(false, 'Expected revert if called by a claimant on their own settlement'); 132 | }); 133 | 134 | it('Should revert if the settlement has failed', async () => { 135 | const openclaims = await ds.openClaims(); 136 | 137 | assert.strictEqual(openclaims.toString(10), '1', 'openClaims value is not correctly'); 138 | 139 | await ds.proposeSettlement(0, 0, { from: claimant }); 140 | 141 | await ds.settlementFailed(0, { from: staker }); // As other party 142 | 143 | try { 144 | // This function do not entry in {file:DelphiStake.sol,line 342} 145 | // because it is already checked in line {file:DelphiStake.sol,line 325} 146 | await ds.acceptSettlement(0, 0, { from: claimant }); 147 | } catch (err) { 148 | return; 149 | } 150 | assert(false, 'Expected revert if called by a staker on their own settlement'); 151 | }); 152 | 153 | it('Should revert if a claim\'s settlement has been accepted', async () => { 154 | const openclaims = await ds.openClaims(); 155 | 156 | assert.strictEqual(openclaims.toString(10), '1', 'openClaims value is not correctly'); 157 | 158 | await ds.proposeSettlement(0, 0, { from: claimant }); 159 | 160 | await ds.acceptSettlement(0, 0, { from: staker }); 161 | const claim1 = await ds.claims.call('0'); 162 | assert.strictEqual(claim1[6], true, 'Ruled bool is not correct'); 163 | 164 | try { 165 | await ds.acceptSettlement(0, 0, { from: claimant }); 166 | } catch (err) { 167 | return; 168 | } 169 | assert(false, 'Expected to revert if a claim\'s settlement has been accepted'); 170 | }); 171 | 172 | it('Should set the claim.ruled to true', async () => { 173 | await ds.proposeSettlement(0, 0, { from: claimant }); 174 | await ds.acceptSettlement(0, 0, { from: staker }); 175 | const claim = await ds.claims.call('0'); 176 | assert.strictEqual(claim[6], true, 'wrong ruled false, Expected true'); 177 | }); 178 | 179 | it('Should return the unused claim funds from the staker back to their stake', async () => { 180 | let claimableStake = await ds.claimableStake(); 181 | assert.strictEqual(claimableStake.toString(10), '89', 'Expected 89 tokens available'); 182 | 183 | await ds.proposeSettlement(0, 0, { from: claimant }); 184 | await ds.acceptSettlement(0, 0, { from: staker }); 185 | claimableStake = await ds.claimableStake(); 186 | assert.strictEqual(claimableStake.toString(10), '100', 'Expected 100 tokens available'); 187 | }); 188 | 189 | it('Should decrement the number of open claims', async () => { 190 | let openclaims = await ds.openClaims(); 191 | 192 | assert.strictEqual(openclaims.toString(10), '1', 'openClaims value is not correctly'); 193 | 194 | await ds.proposeSettlement(0, 0, { from: claimant }); 195 | await ds.acceptSettlement(0, 0, { from: staker }); 196 | openclaims = await ds.openClaims(); 197 | assert.strictEqual(openclaims.toString(10), '0', 'openClaims value is not correctly'); 198 | }); 199 | it('Should transfer the settlement amount, plus their original deposit, back to the claimant', async () => { 200 | const originalClaimantBalance = 100000; 201 | let claimantBalance = await token.balanceOf(claimant); 202 | 203 | assert.strictEqual((originalClaimantBalance - 10).toString(10), claimantBalance.toString(10), 'Expected less claimant balance'); 204 | await ds.proposeSettlement(0, 0, { from: claimant }); 205 | await ds.acceptSettlement(0, 0, { from: staker }); 206 | 207 | claimantBalance = await token.balanceOf(claimant); 208 | 209 | assert.strictEqual(originalClaimantBalance.toString(10), claimantBalance.toString(10), 'Expected equal claimant balance'); 210 | }); 211 | 212 | it('Should emit a SettlementAccepted event', async () => { 213 | await ds.proposeSettlement(0, 0, { from: claimant }); 214 | await ds.acceptSettlement(0, 0, { from: staker }).then((status) => { 215 | assert.strictEqual('SettlementAccepted', status.logs[0].event, 'did not emit the SettlementAccepted event'); 216 | }); 217 | }); 218 | }); 219 | }); 220 | -------------------------------------------------------------------------------- /test/js/DelphiStake/ruleOnClaim.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | // TODO: Grab claimIds from events 5 | 6 | const DelphiStake = artifacts.require('DelphiStake'); 7 | const EIP20 = artifacts.require('EIP20'); 8 | 9 | const utils = require('../utils.js'); 10 | const BN = require('bignumber.js'); 11 | 12 | 13 | const conf = utils.getConfig(); 14 | 15 | 16 | contract('DelphiStake', (accounts) => { 17 | describe('Function: ruleOnClaim', () => { 18 | const [staker, claimant, arbiter, other] = accounts; 19 | 20 | const claimAmount = '1'; 21 | const defaultRuling = '1'; 22 | 23 | let token; 24 | let ds; 25 | 26 | let originalArbiterBalance; 27 | let originalClaimantBalance; 28 | 29 | beforeEach(async () => { 30 | // Create a new token, apportioning shares to the staker, claimant, and arbiter 31 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 32 | await token.transfer(claimant, 100000, { from: staker }); 33 | await token.transfer(arbiter, 100000, { from: staker }); 34 | 35 | // Create a new DelphiStake 36 | ds = await DelphiStake.new(); 37 | 38 | // Initialize the DelphiStake (it will need to transferFrom the staker, so approve it first) 39 | await token.approve(ds.address, conf.initialStake, { from: staker }); 40 | await token.transfer(arbiter, 1000, { from: staker }); 41 | 42 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 43 | conf.deadline, arbiter, { from: staker }); 44 | 45 | await token.approve(ds.address, conf.minFee, { from: claimant }); 46 | 47 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 48 | 49 | await ds.openClaim(claimAmount, conf.minFee, '', { from: claimant }); 50 | 51 | await ds.settlementFailed('0', { from: claimant }); 52 | 53 | originalArbiterBalance = await token.balanceOf(arbiter); 54 | originalClaimantBalance = await token.balanceOf(claimant); 55 | }); 56 | 57 | it('should revert if called by a non-arbiter', async () => { 58 | try { 59 | await utils.as(other, ds.ruleOnClaim, '0', defaultRuling); 60 | } catch (err) { 61 | assert(utils.isEVMRevert(err), err.toString()); 62 | return; 63 | } 64 | 65 | assert(false, 'A non-arbiter was able to rule on the claim'); 66 | }); 67 | it('should revert if called on an out-of-bounds claimId', async () => { 68 | try { 69 | await utils.as(arbiter, ds.ruleOnClaim, 1, defaultRuling); 70 | } catch (err) { 71 | assert(utils.isEVMRevert(err), err.toString()); 72 | return; 73 | } 74 | assert(false, 'expected revert if called on an out-of-bounds claimId'); 75 | }); 76 | 77 | it('should revert if called on an out-of-bounds ruling', async () => { 78 | try { 79 | await utils.as(arbiter, ds.ruleOnClaim, '0', 6); 80 | } catch (err) { 81 | assert(utils.isEVMRevert(err), err.toString()); 82 | return; 83 | } 84 | 85 | assert(false, 'expected revert if called on an out-of-bounds claimId'); 86 | }); 87 | 88 | it('should revert if settlement never failed', async () => { 89 | await token.approve(ds.address, conf.minFee, { from: claimant }); 90 | await ds.openClaim(claimAmount, conf.minFee, '', { from: claimant }); 91 | 92 | try { 93 | await utils.as(arbiter, ds.ruleOnClaim, 1, defaultRuling); 94 | } catch (err) { 95 | assert(utils.isEVMRevert(err), err.toString()); 96 | return; 97 | } 98 | assert(false, 'did not revert after trying to rule on a claim whose settlement has not yet failed'); 99 | }); 100 | 101 | it('should revert if the claim has already been ruled', async () => { 102 | await utils.as(arbiter, ds.ruleOnClaim, '0', defaultRuling); 103 | 104 | try { 105 | await utils.as(arbiter, ds.ruleOnClaim, '0', defaultRuling); 106 | } catch (err) { 107 | assert(utils.isEVMRevert(err), err.toString()); 108 | return; 109 | } 110 | 111 | assert(false, 'A claim was able to be ruled on twice'); 112 | }); 113 | 114 | it('should properly set the claim\'s ruling', async () => { 115 | await utils.as(arbiter, ds.ruleOnClaim, '0', defaultRuling); 116 | 117 | const claim = await ds.claims.call('0'); 118 | 119 | assert.strictEqual(claim[5].toString(10), defaultRuling, 'initialized claim ruling incorrectly'); 120 | }); 121 | 122 | it('should add the claim\'s amount and fee to the stake iff the claim is not accepted', async () => { 123 | await ds.ruleOnClaim('0', defaultRuling, { from: arbiter }); 124 | const stake = await ds.claimableStake.call(); 125 | assert.strictEqual(stake.toString(10), conf.initialStake, 'stake not returned to original amount'); 126 | }); 127 | 128 | it('should not alter the stake if the claim is accepted', async () => { 129 | await ds.ruleOnClaim('0', defaultRuling, { from: arbiter }); 130 | 131 | const stakeAfterRuling = await ds.claimableStake.call(); 132 | 133 | assert.strictEqual(conf.initialStake, stakeAfterRuling.toString(10), 134 | 'stake incorrectly changed after ruling'); 135 | }); 136 | 137 | it('should transfer the fee and surplus to the arbiter and the claim amount + fee to the claimant if the ruling is 0', async () => { 138 | const ruling = '0'; 139 | 140 | await ds.ruleOnClaim('0', ruling, { from: arbiter }); 141 | 142 | const arbiterBalance = await token.balanceOf(arbiter); 143 | const claimantBalance = await token.balanceOf(claimant); 144 | 145 | assert.strictEqual(originalArbiterBalance.add(new BN(conf.minFee, 10)).toString(10), arbiterBalance.toString(10), 'Arbiter Balance doesnt grow up'); 146 | assert.strictEqual(originalClaimantBalance.add(new BN(parseInt(conf.minFee, 10) + parseInt(claimAmount, 10), 10)).toString(10), claimantBalance.toString(10), 'Claimant Balance doesnt grow up'); 147 | }); 148 | 149 | it('should transfer the fee and surplus to the arbiter and return the claim amount + fee to the stakers stake if the ruling is 1', async () => { 150 | const ruling = '1'; 151 | 152 | await ds.ruleOnClaim('0', ruling, { from: arbiter }); 153 | 154 | const arbiterBalance = await token.balanceOf(arbiter); 155 | const stake = await ds.claimableStake.call(); 156 | 157 | assert.strictEqual(originalArbiterBalance.add(new BN(conf.minFee, 10)).toString(10), arbiterBalance.toString(10), 'Arbiter Balance doesnt grow up'); 158 | assert.strictEqual(stake.toString(10), conf.initialStake, 'stake not returned to original amount'); 159 | }); 160 | 161 | it('should transfer 2 times the fee plus the surplus to the arbiter and should burn the claim amount if the ruling is 2', async () => { 162 | const ruling = '2'; 163 | 164 | await ds.ruleOnClaim('0', ruling, { from: arbiter }); 165 | 166 | const arbiterBalance = await token.balanceOf(arbiter); 167 | 168 | assert.strictEqual(originalArbiterBalance.add(new BN(parseInt(conf.minFee, 10) * 2, 10)).toString(10), arbiterBalance.toString(10), 'Arbiter balance incorrect'); 169 | 170 | const balance0x0 = await token.balanceOf('0x0000000000000000000000000000000000000000'); 171 | 172 | assert.strictEqual(balance0x0.toString(10), claimAmount, 'address 0x0 balance incorrect'); 173 | }); 174 | 175 | it('should transfer the fee deposit back to the claimant, transfer the fee surplus to the arbiter, and return the claim amount and fee to the stakers stake', async () => { 176 | const ruling = '3'; 177 | 178 | await ds.ruleOnClaim('0', ruling, { from: arbiter }); 179 | 180 | // const arbiterBalance = await token.balanceOf(arbiter); 181 | const stake = await ds.claimableStake.call(); 182 | const claimantBalance = await token.balanceOf(claimant); 183 | 184 | assert.strictEqual(stake.toString(10), conf.initialStake, 'stake not returned to original amount'); 185 | assert.strictEqual(originalClaimantBalance.add( 186 | new BN(parseInt(conf.minFee, 10) + parseInt(claimAmount, 10), 10)) 187 | .toString(10), claimantBalance.toString(10), 'Incorrect claimant balance'); 188 | }); 189 | 190 | it('should decrement openClaims', async () => { 191 | // Open a new claim 192 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 193 | await token.approve(ds.address, conf.minFee, { from: claimant }); 194 | const { logs } = await ds.openClaim(claimAmount, conf.minFee, '', { from: claimant }); 195 | const claimId = utils.getLog(logs, 'ClaimOpened').args._claimId; // eslint-disable-line 196 | 197 | // Get the initial number of open claims 198 | const initialOpenClaims = await ds.openClaims.call(); 199 | 200 | // Cancel settlement and rule on the claim 201 | await ds.settlementFailed(claimId, { from: claimant }); 202 | await ds.ruleOnClaim(claimId, defaultRuling, { from: arbiter }); 203 | 204 | // Since the claim is closed now, expect openClaims to be less than it was before we closed 205 | // the claim. 206 | const finalOpenClaims = await ds.openClaims.call(); 207 | assert(finalOpenClaims.lt(initialOpenClaims), 'openClaims not decremented after ruling'); 208 | }); 209 | 210 | it('should emit a ClaimRuled event', async () => { 211 | // Open a new claim 212 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 213 | await token.approve(ds.address, conf.minFee, { from: claimant }); 214 | const openClaimLogs 215 | = (await ds.openClaim(claimAmount, conf.minFee, '', { from: claimant })).logs; 216 | const claimId = 217 | utils.getLog(openClaimLogs, 'ClaimOpened').args._claimId; // eslint-disable-line 218 | 219 | // Cancel settlement and rule on claim. Capture the logs on ruling. 220 | await ds.settlementFailed(claimId, { from: claimant }); 221 | const ruledLogs = (await ds.ruleOnClaim(claimId, defaultRuling, { from: arbiter })).logs; 222 | 223 | // Expect utils.getLog to find in the logs returned in openClaim a 'ClaimOpened' event 224 | assert(typeof utils.getLog(ruledLogs, 'ClaimRuled') !== 'undefined', 225 | 'An expected log was not emitted'); 226 | 227 | // Expect the ClaimRuled log to have a valid claimId argument 228 | assert.strictEqual( 229 | utils.getLog(ruledLogs, 'ClaimRuled').args._claimId.toString(10), // eslint-disable-line 230 | '1', 231 | 'The event either did not contain a _claimId arg, or the emitted claimId was incorrect'); 232 | }); 233 | }); 234 | }); 235 | -------------------------------------------------------------------------------- /test/js/DelphiVoting/revealVote.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiVoting = artifacts.require('DelphiVoting'); 5 | const DelphiStake = artifacts.require('DelphiStake'); 6 | const DelphiStakeFactory = artifacts.require('DelphiStakeFactory'); 7 | const DelphiVotingFactory = artifacts.require('DelphiVotingFactory'); 8 | const RegistryFactory = artifacts.require('tcr/RegistryFactory.sol'); 9 | const Registry = artifacts.require('tcr/Registry.sol'); 10 | const EIP20 = artifacts.require('tokens/eip20/EIP20.sol'); 11 | 12 | const utils = require('../utils.js'); 13 | 14 | const HttpProvider = require('ethjs-provider-http'); 15 | const EthRPC = require('ethjs-rpc'); 16 | const Web3 = require('web3'); 17 | 18 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545')); 19 | const rpc = new EthRPC(new HttpProvider('http://localhost:7545')); 20 | 21 | const solkeccak = Web3.utils.soliditySha3; 22 | 23 | contract('DelphiVoting', (accounts) => { 24 | describe('Function: revealVote', () => { 25 | const [staker, claimant, arbiterAlice, arbiterBob, arbiterCharlie] = accounts; 26 | 27 | let delphiStake; 28 | let delphiVoting; 29 | let token; 30 | 31 | beforeEach(async () => { 32 | // Get deployed factory contracts 33 | const delphiVotingFactory = await DelphiVotingFactory.deployed(); 34 | const delphiStakeFactory = await DelphiStakeFactory.deployed(); 35 | const registryFactory = await RegistryFactory.deployed(); 36 | 37 | // Create a new registry and curation token 38 | const registryReceipt = await registryFactory.newRegistryWithToken( 39 | 1000000, 40 | 'RegistryCoin', 41 | 0, 42 | 'REG', 43 | [100, 100, 100, 100, 100, 100, 100, 100, 60, 60, 50, 50], 44 | 'The Arbiter Registry', 45 | ); 46 | 47 | // Get instances of the registry and its token 48 | const registryToken = EIP20.at(registryReceipt.logs[0].args.token); 49 | const registry = Registry.at(registryReceipt.logs[0].args.registry); 50 | 51 | // Give 100k REG to each account, and approve the Registry to transfer it 52 | await Promise.all(accounts.map(async (account) => { 53 | await registryToken.transfer(account, 100000); 54 | await registryToken.approve(registry.address, 100, { from: account }); 55 | })); 56 | 57 | // Apply Alice, Bob, and Charlie to the registry 58 | await registry.apply(solkeccak(arbiterAlice), 100, '', { from: arbiterAlice }); 59 | await registry.apply(solkeccak(arbiterBob), 100, '', { from: arbiterBob }); 60 | await registry.apply(solkeccak(arbiterCharlie), 100, '', { from: arbiterCharlie }); 61 | 62 | // Increase time past the registry application period 63 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 64 | 65 | // Add arbiters to the Registry 66 | await registry.updateStatus(solkeccak(arbiterAlice)); 67 | await registry.updateStatus(solkeccak(arbiterBob)); 68 | await registry.updateStatus(solkeccak(arbiterCharlie)); 69 | 70 | // Create a DelphiVoting with 100 second voting periods, fee decay value of five, 71 | // and which uses the registry we just created as its arbiter set 72 | const delphiVotingReceipt = await delphiVotingFactory.makeDelphiVoting(registry.address, 73 | 5, [solkeccak('parameterizerVotingPeriod'), solkeccak('commitStageLen'), 74 | solkeccak('revealStageLen')], 75 | [100, 100, 100]); 76 | delphiVoting = DelphiVoting.at(delphiVotingReceipt.logs[0].args.delphiVoting); 77 | 78 | // Create DisputeCoin and give 100k DIS to each account 79 | token = await EIP20.new(1000000, 'DisputeCoin', 0, 'DIS'); 80 | await Promise.all(accounts.map(async account => token.transfer(account, 100000))); 81 | 82 | // Create a DelphiStake with 90k DIS tokens, 1k minFee, and a release time 1k seconds 83 | // from now 84 | await token.approve(delphiStakeFactory.address, 90000, { from: staker }); 85 | const expirationTime = (await web3.eth.getBlock('latest')).timestamp + 1000; 86 | const delphiStakeReceipt = await delphiStakeFactory.createDelphiStake(90000, token.address, 87 | 1000, '', expirationTime, delphiVoting.address, { from: staker }); 88 | // eslint-disable-next-line 89 | delphiStake = DelphiStake.at(delphiStakeReceipt.logs[0].args._contractAddress); 90 | }); 91 | 92 | it('should reveal an arbiter\'s vote and update the vote tally for a vote of 0', async () => { 93 | // Set constants 94 | const CLAIM_AMOUNT = '10000'; 95 | const FEE_AMOUNT = '1000'; 96 | const VOTE = '0'; 97 | const SALT = '420'; 98 | const DATA = 'i love cats'; 99 | 100 | // Open a new claim on the DS and generate a claim ID for it 101 | const claimNumber = 102 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 103 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 104 | 105 | // Generate a secret hash and commit it as a vote 106 | const secretHash = utils.getSecretHash(VOTE, SALT); 107 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 108 | { from: arbiterAlice }); 109 | 110 | // Increase time to get to the reveal phase 111 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 112 | 113 | // Capture the initial tally for the vote option we committed, before revealing. It should 114 | // be zero. 115 | const initialTally = (await delphiVoting.revealedVotesForOption.call(claimId, VOTE)); 116 | assert.strictEqual(initialTally.toString(10), '0', 117 | 'the initial vote tally was not as-expected'); 118 | 119 | // Reveal the arbiter's vote 120 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 121 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 122 | 123 | // The final tally for the option we revealed for should be one. 124 | const finalTally = (await delphiVoting.revealedVotesForOption.call(claimId, VOTE)); 125 | assert.strictEqual(finalTally.toString(10), '1', 126 | 'the final vote tally was not as-expected'); 127 | }); 128 | 129 | it('should not allow an arbiter to reveal twice', async () => { 130 | // Set constants 131 | const CLAIM_AMOUNT = '10000'; 132 | const FEE_AMOUNT = '1000'; 133 | const VOTE = '0'; 134 | const SALT = '420'; 135 | const DATA = 'i love cats'; 136 | 137 | // Open a new claim on the DS and generate a claim ID for it 138 | const claimNumber = 139 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 140 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 141 | 142 | // Generate a secret hash and commit it as a vote 143 | const secretHash = utils.getSecretHash(VOTE, SALT); 144 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 145 | { from: arbiterAlice }); 146 | 147 | // Increase time to get to the reveal phase 148 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 149 | 150 | // Reveal the arbiter's vote 151 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 152 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 153 | 154 | try { 155 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 156 | } catch (err) { 157 | assert(utils.isEVMRevert(err), err.toString()); 158 | 159 | return; 160 | } 161 | 162 | assert(false, 'an arbiter was able to reveal twice'); 163 | }); 164 | 165 | it('Should revert if the provided vote and salt don\'t match the commitHash', async () => { 166 | // Set constants 167 | const CLAIM_AMOUNT = '10000'; 168 | const FEE_AMOUNT = '1000'; 169 | const VOTE = '0'; 170 | const SALT = '420'; 171 | const DATA = 'i love cats'; 172 | 173 | // Open a new claim on the DS and generate a claim ID for it 174 | const claimNumber = 175 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 176 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 177 | 178 | // Generate a secret hash and commit it as a vote 179 | const secretHash = utils.getSecretHash(VOTE, SALT); 180 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 181 | { from: arbiterAlice }); 182 | 183 | // Increase time to get to the reveal phase 184 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 185 | 186 | try { 187 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 188 | await delphiVoting.revealVote(claimId, VOTE, 421, insertPoint, { from: arbiterAlice }); 189 | } catch (err) { 190 | assert(utils.isEVMRevert(err), err.toString()); 191 | 192 | return; 193 | } 194 | 195 | assert(false, 'Expected to revert if the provided vote and salt don\'t match the commitHash'); 196 | }); 197 | 198 | it('Should not allow an arbiter to reveal before the reveal stage has begun', async () => { 199 | // Set constants 200 | const CLAIM_AMOUNT = '10000'; 201 | const FEE_AMOUNT = '1000'; 202 | const VOTE = '0'; 203 | const SALT = '420'; 204 | const DATA = 'i love cats'; 205 | 206 | // Open a new claim on the DS and generate a claim ID for it 207 | const claimNumber = 208 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 209 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 210 | 211 | // Generate a secret hash and commit it as a vote 212 | const secretHash = utils.getSecretHash(VOTE, SALT); 213 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 214 | { from: arbiterAlice }); 215 | 216 | try { 217 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 218 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 219 | } catch (err) { 220 | assert(utils.isEVMRevert(err), err.toString()); 221 | 222 | return; 223 | } 224 | 225 | assert(false, 'Expected to not allow an arbiter to reveal before the reveal stage has begun'); 226 | }); 227 | 228 | it('Should not allow an arbiter to reveal after the reveal stage has ended', async () => { 229 | // Set constants 230 | const CLAIM_AMOUNT = '10000'; 231 | const FEE_AMOUNT = '1000'; 232 | const VOTE = '0'; 233 | const SALT = '420'; 234 | const DATA = 'i love cats'; 235 | 236 | // Open a new claim on the DS and generate a claim ID for it 237 | const claimNumber = 238 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 239 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 240 | 241 | // Generate a secret hash and commit it as a vote 242 | const secretHash = utils.getSecretHash(VOTE, SALT); 243 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 244 | { from: arbiterAlice }); 245 | 246 | // Increase time to past the reveal stage 247 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [201] }); 248 | 249 | try { 250 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 251 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 252 | } catch (err) { 253 | assert(utils.isEVMRevert(err), err.toString()); 254 | 255 | return; 256 | } 257 | assert(false, 'Expected to not allow an arbiter to reveal after the reveal stage has ended'); 258 | }); 259 | }); 260 | }); 261 | -------------------------------------------------------------------------------- /test/js/DelphiVoting/commitVote.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiVoting = artifacts.require('DelphiVoting'); 5 | const DelphiStake = artifacts.require('DelphiStake'); 6 | const DelphiStakeFactory = artifacts.require('DelphiStakeFactory'); 7 | const DelphiVotingFactory = artifacts.require('DelphiVotingFactory'); 8 | const RegistryFactory = artifacts.require('tcr/RegistryFactory.sol'); 9 | const Registry = artifacts.require('tcr/Registry.sol'); 10 | const EIP20 = artifacts.require('tokens/eip20/EIP20.sol'); 11 | 12 | const utils = require('../utils.js'); 13 | 14 | const HttpProvider = require('ethjs-provider-http'); 15 | const EthRPC = require('ethjs-rpc'); 16 | const Web3 = require('web3'); 17 | 18 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545')); 19 | const rpc = new EthRPC(new HttpProvider('http://localhost:7545')); 20 | 21 | const solkeccak = Web3.utils.soliditySha3; 22 | 23 | contract('DelphiVoting', (accounts) => { 24 | describe('Function: commitVote', () => { 25 | const [staker, claimant, arbiterAlice, arbiterBob, arbiterCharlie] = accounts; 26 | 27 | let delphiStake; 28 | let delphiVoting; 29 | let token; 30 | 31 | beforeEach(async () => { 32 | // Get deployed factory contracts 33 | const delphiVotingFactory = await DelphiVotingFactory.deployed(); 34 | const delphiStakeFactory = await DelphiStakeFactory.deployed(); 35 | const registryFactory = await RegistryFactory.deployed(); 36 | 37 | // Create a new registry and curation token 38 | const registryReceipt = await registryFactory.newRegistryWithToken( 39 | 1000000, 40 | 'RegistryCoin', 41 | 0, 42 | 'REG', 43 | [100, 100, 100, 100, 100, 100, 100, 100, 60, 60, 50, 50], 44 | 'The Arbiter Registry', 45 | ); 46 | 47 | // Get instances of the registry and its token 48 | const registryToken = EIP20.at(registryReceipt.logs[0].args.token); 49 | const registry = Registry.at(registryReceipt.logs[0].args.registry); 50 | 51 | // Give 100k REG to each account, and approve the Registry to transfer it 52 | await Promise.all(accounts.map(async (account) => { 53 | await registryToken.transfer(account, 100000); 54 | await registryToken.approve(registry.address, 100, { from: account }); 55 | })); 56 | 57 | // Apply Alice, Bob, and Charlie to the registry 58 | await registry.apply(solkeccak(arbiterAlice), 100, '', { from: arbiterAlice }); 59 | await registry.apply(solkeccak(arbiterBob), 100, '', { from: arbiterBob }); 60 | await registry.apply(solkeccak(arbiterCharlie), 100, '', { from: arbiterCharlie }); 61 | 62 | // Increase time past the registry application period 63 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 64 | 65 | // Add arbiters to the Registry 66 | await registry.updateStatus(solkeccak(arbiterAlice)); 67 | await registry.updateStatus(solkeccak(arbiterBob)); 68 | await registry.updateStatus(solkeccak(arbiterCharlie)); 69 | 70 | // Create a DelphiVoting with 100 second voting periods, fee decay value of five, 71 | // and which uses the registry we just created as its arbiter set 72 | const delphiVotingReceipt = await delphiVotingFactory.makeDelphiVoting(registry.address, 73 | 5, [solkeccak('parameterizerVotingPeriod'), solkeccak('commitStageLen'), 74 | solkeccak('revealStageLen')], 75 | [100, 100, 100]); 76 | delphiVoting = DelphiVoting.at(delphiVotingReceipt.logs[0].args.delphiVoting); 77 | 78 | // Create DisputeCoin and give 100k DIS to each account 79 | token = await EIP20.new(1000000, 'DisputeCoin', 0, 'DIS'); 80 | await Promise.all(accounts.map(async account => token.transfer(account, 100000))); 81 | 82 | // Create a DelphiStake with 90k DIS tokens, 1k minFee, and a release time 1k seconds 83 | // from now 84 | await token.approve(delphiStakeFactory.address, 90000, { from: staker }); 85 | const expirationTime = (await web3.eth.getBlock('latest')).timestamp + 1000; 86 | const delphiStakeReceipt = await delphiStakeFactory.createDelphiStake(90000, token.address, 87 | 1000, '', expirationTime, delphiVoting.address, { from: staker }); 88 | // eslint-disable-next-line 89 | delphiStake = DelphiStake.at(delphiStakeReceipt.logs[0].args._contractAddress); 90 | }); 91 | 92 | it('should initialize a new claim and log the arbiter\'s vote', async () => { 93 | // Set constants 94 | const CLAIM_AMOUNT = '10000'; 95 | const FEE_AMOUNT = '1000'; 96 | const VOTE = '1'; 97 | const SALT = '420'; 98 | 99 | // Make a new claim in the DelphiStake and generate a claim ID 100 | const claimNumber = 101 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 102 | delphiStake); 103 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 104 | 105 | // Nobody has voted yet for the new claim, so from the DelphiVoting contract's perpective, 106 | // this claim does not exist. 107 | const initialClaimExists = await delphiVoting.claimExists.call(claimId); 108 | assert.strictEqual(initialClaimExists, false, 109 | 'The claim was instantiated before it should have been'); 110 | 111 | // Generate a secret hash and, as the arbiter, commit it for the claim which was just 112 | // opened 113 | const secretHash = utils.getSecretHash(VOTE, SALT); 114 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 115 | { from: arbiterAlice }); 116 | 117 | // Now, because an arbiter has voted, a claim should exist in the eyes of the DV contract 118 | const finalClaimExists = await delphiVoting.claimExists.call(claimId); 119 | assert.strictEqual(finalClaimExists, true, 'The claim was not instantiated'); 120 | 121 | // Lets also make sure the secret hash which was stored was the same which we committed. 122 | const storedSecretHash = 123 | await delphiVoting.getArbiterCommitForClaim.call(claimId, arbiterAlice); 124 | assert.strictEqual(storedSecretHash, secretHash, 'The vote was not properly stored'); 125 | }); 126 | 127 | it('should update an arbiter\'s vote in a claim', async () => { 128 | // Set constants 129 | const CLAIM_AMOUNT = '10000'; 130 | const FEE_AMOUNT = '1000'; 131 | const SALT = '420'; 132 | 133 | // Make a new claim in the DelphiStake and generate a claim ID 134 | const claimNumber = 135 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 136 | delphiStake); 137 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 138 | 139 | // Generate an initial secret hash and, as the arbiter, commit it for the claim which 140 | // was just opened 141 | const initialSecretHash = utils.getSecretHash(1, SALT); 142 | await delphiVoting.commitVote(delphiStake.address, claimNumber, initialSecretHash, 143 | { from: arbiterAlice }); 144 | 145 | // Generate a final secret hash and, as the arbiter, commit it for the claim which 146 | // was just opened 147 | const finalSecretHash = utils.getSecretHash(0, SALT); 148 | await delphiVoting.commitVote(delphiStake.address, claimNumber, finalSecretHash, 149 | { from: arbiterAlice }); 150 | 151 | // Lets also make sure the secret hash which was stored was the same which we committed. 152 | const storedSecretHash = 153 | await delphiVoting.getArbiterCommitForClaim.call(claimId, arbiterAlice); 154 | assert.strictEqual(storedSecretHash, finalSecretHash, 'The vote was not properly stored'); 155 | }); 156 | 157 | it('should not allow a non-arbiter to vote', async () => { 158 | // Set constants 159 | const CLAIM_AMOUNT = '10000'; 160 | const FEE_AMOUNT = '1000'; 161 | const VOTE = '1'; 162 | const SALT = '420'; 163 | 164 | // Make a new claim in the DelphiStake and generate a claim ID 165 | const claimNumber = 166 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 167 | delphiStake); 168 | 169 | // Generate a secret hash and, as a non-arbiter, try to commit it for the claim which 170 | // was just opened 171 | const secretHash = utils.getSecretHash(VOTE, SALT); 172 | try { 173 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 174 | { from: staker }); 175 | } catch (err) { 176 | assert(utils.isEVMRevert(err), err.toString()); 177 | 178 | return; 179 | } 180 | 181 | assert(false, 'should not have been able to vote as non-arbiter'); 182 | }); 183 | 184 | it('should not allow an arbiter to commit after the commit period has ended', async () => { 185 | // Set constants 186 | const CLAIM_AMOUNT = '10000'; 187 | const FEE_AMOUNT = '1000'; 188 | const VOTE = '1'; 189 | const SALT = '420'; 190 | 191 | // Make a new claim in the DelphiStake and generate a claim ID 192 | const claimNumber = 193 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 194 | delphiStake); 195 | 196 | // Generate a secret hash and, as the arbiter, commit it for the claim which was just 197 | // opened. This instantiates the claim and gets the clock ticking. 198 | const secretHash = utils.getSecretHash(VOTE, SALT); 199 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 200 | { from: arbiterAlice }); 201 | 202 | // Increase time past the commit period 203 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 204 | 205 | try { 206 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 207 | { from: arbiterAlice }); 208 | } catch (err) { 209 | assert(utils.isEVMRevert(err), err.toString()); 210 | 211 | try { 212 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 213 | { from: arbiterBob }); 214 | } catch (err2) { 215 | assert(utils.isEVMRevert(err2), err2.toString()); 216 | 217 | return; 218 | } 219 | } 220 | 221 | assert(false, 'Expetected to not allow an arbiter to commit after the commit period has ended'); 222 | }); 223 | 224 | it('should not allow an arbiter to commit a vote for a claim which does not exist', 225 | async () => { 226 | // Set constants 227 | const NON_EXISTANT_CLAIM = '420'; 228 | const SALT = '420'; 229 | const VOTE_CHOICE = '1'; 230 | 231 | // Generate a secret hash 232 | const secretHash = utils.getSecretHash(VOTE_CHOICE, SALT); 233 | 234 | try { 235 | // As the arbiter, try to commit a vote for a claim which does not exist in the DS 236 | await delphiVoting.commitVote(delphiStake.address, NON_EXISTANT_CLAIM, secretHash, 237 | { from: arbiterAlice }); 238 | } catch (err) { 239 | assert(utils.isEVMRevert(err), err.toString()); 240 | 241 | return; 242 | } 243 | assert(false, 'should not have been able to vote in an uninitialized claim'); 244 | }, 245 | ); 246 | 247 | it('should not allow an arbiter to commit a secret hash of 0', 248 | async () => { 249 | // Set constants 250 | const CLAIM_AMOUNT = '10000'; 251 | const FEE_AMOUNT = '1000'; 252 | 253 | // Make a new claim in the DelphiStake and generate a claim ID 254 | const claimNumber = 255 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 256 | delphiStake); 257 | 258 | try { 259 | await delphiVoting.commitVote(delphiStake.address, claimNumber, 0, 260 | { from: arbiterAlice }); 261 | } catch (err) { 262 | assert(utils.isEVMRevert(err), err.toString()); 263 | 264 | return; 265 | } 266 | 267 | assert(false, 'expected to not allow an arbiter to commit a secret hash of 0'); 268 | }); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /test/js/DelphiVoting/submitRuling.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiVoting = artifacts.require('DelphiVoting'); 5 | const DelphiStake = artifacts.require('DelphiStake'); 6 | const DelphiStakeFactory = artifacts.require('DelphiStakeFactory'); 7 | const DelphiVotingFactory = artifacts.require('DelphiVotingFactory'); 8 | const RegistryFactory = artifacts.require('tcr/RegistryFactory.sol'); 9 | const Registry = artifacts.require('tcr/Registry.sol'); 10 | const EIP20 = artifacts.require('tokens/eip20/EIP20.sol'); 11 | 12 | const utils = require('../utils.js'); 13 | 14 | const HttpProvider = require('ethjs-provider-http'); 15 | const EthRPC = require('ethjs-rpc'); 16 | const Web3 = require('web3'); 17 | 18 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545')); 19 | const rpc = new EthRPC(new HttpProvider('http://localhost:7545')); 20 | 21 | const solkeccak = Web3.utils.soliditySha3; 22 | 23 | contract('DelphiVoting', (accounts) => { //eslint-disable-line 24 | describe('Function: submitRuling', () => { 25 | const [staker, claimant, arbiterAlice, arbiterBob, arbiterCharlie] = accounts; 26 | 27 | let delphiStake; 28 | let delphiVoting; 29 | let token; 30 | 31 | beforeEach(async () => { 32 | // Get deployed factory contracts 33 | const delphiVotingFactory = await DelphiVotingFactory.deployed(); 34 | const delphiStakeFactory = await DelphiStakeFactory.deployed(); 35 | const registryFactory = await RegistryFactory.deployed(); 36 | 37 | // Create a new registry and curation token 38 | const registryReceipt = await registryFactory.newRegistryWithToken( 39 | 1000000, 40 | 'RegistryCoin', 41 | 0, 42 | 'REG', 43 | [100, 100, 100, 100, 100, 100, 100, 100, 60, 60, 50, 50], 44 | 'The Arbiter Registry', 45 | ); 46 | 47 | // Get instances of the registry and its token 48 | const registryToken = EIP20.at(registryReceipt.logs[0].args.token); 49 | const registry = Registry.at(registryReceipt.logs[0].args.registry); 50 | 51 | // Give 100k REG to each account, and approve the Registry to transfer it 52 | await Promise.all(accounts.map(async (account) => { 53 | await registryToken.transfer(account, 100000); 54 | await registryToken.approve(registry.address, 100, { from: account }); 55 | })); 56 | 57 | // Apply Alice, Bob, and Charlie to the registry 58 | await registry.apply(solkeccak(arbiterAlice), 100, '', { from: arbiterAlice }); 59 | await registry.apply(solkeccak(arbiterBob), 100, '', { from: arbiterBob }); 60 | await registry.apply(solkeccak(arbiterCharlie), 100, '', { from: arbiterCharlie }); 61 | 62 | // Increase time past the registry application period 63 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 64 | 65 | // Add arbiters to the Registry 66 | await registry.updateStatus(solkeccak(arbiterAlice)); 67 | await registry.updateStatus(solkeccak(arbiterBob)); 68 | await registry.updateStatus(solkeccak(arbiterCharlie)); 69 | 70 | // Create a DelphiVoting with 100 second voting periods, fee decay value of five, 71 | // and which uses the registry we just created as its arbiter set 72 | const delphiVotingReceipt = await delphiVotingFactory.makeDelphiVoting(registry.address, 73 | 5, [solkeccak('parameterizerVotingPeriod'), solkeccak('commitStageLen'), 74 | solkeccak('revealStageLen')], 75 | [100, 100, 100]); 76 | delphiVoting = DelphiVoting.at(delphiVotingReceipt.logs[0].args.delphiVoting); 77 | 78 | // Create DisputeCoin and give 100k DIS to each account 79 | token = await EIP20.new(1000000, 'DisputeCoin', 0, 'DIS'); 80 | await Promise.all(accounts.map(async account => token.transfer(account, 100000))); 81 | 82 | // Create a DelphiStake with 90k DIS tokens, 1k minFee, and a release time 1k seconds 83 | // from now 84 | await token.approve(delphiStakeFactory.address, 90000, { from: staker }); 85 | const expirationTime = (await web3.eth.getBlock('latest')).timestamp + 1000; 86 | const delphiStakeReceipt = await delphiStakeFactory.createDelphiStake(90000, token.address, 87 | 1000, '', expirationTime, delphiVoting.address, { from: staker }); 88 | // eslint-disable-next-line 89 | delphiStake = DelphiStake.at(delphiStakeReceipt.logs[0].args._contractAddress); 90 | }); 91 | 92 | it('should allow anyone to submit a ruling (non-arbiters)', async () => { 93 | // Set constants 94 | const CLAIM_AMOUNT = '10000'; 95 | const FEE_AMOUNT = '1000'; 96 | const VOTE = '0'; 97 | const SALT = '420'; 98 | const DATA = 'i love cats'; 99 | 100 | // Open a new claim on the DS and generate a claim ID for it 101 | const claimNumber = 102 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 103 | 104 | // Generate a secret hash and commit it as a vote 105 | const secretHash = utils.getSecretHash(VOTE, SALT); 106 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 107 | { from: arbiterAlice }); 108 | 109 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [201] }); 110 | 111 | // Submit rulling as non-arbiter 112 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: staker }); 113 | }); 114 | 115 | it('should revert if the claimId generated from the stake address and claim number doesnt exist', async () => { 116 | // Set constants 117 | const CLAIM_AMOUNT = '10000'; 118 | const FEE_AMOUNT = '1000'; 119 | const VOTE = '0'; 120 | const SALT = '420'; 121 | const DATA = 'i love cats'; 122 | 123 | // Open a new claim on the DS and generate a claim ID for it 124 | const claimNumber = 125 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 126 | 127 | // Generate a secret hash and commit it as a vote 128 | const secretHash = utils.getSecretHash(VOTE, SALT); 129 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 130 | { from: arbiterAlice }); 131 | 132 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [201] }); 133 | 134 | try { 135 | // Submit rulling with a wrong claim number 136 | await delphiVoting.submitRuling(delphiStake.address, '2'); 137 | } catch (err) { 138 | assert(utils.isEVMRevert(err), err.toString()); 139 | 140 | return; 141 | } 142 | assert(false, 'Expected to revert if the claimId generated from the stake address and claim number doesnt exist'); 143 | }); 144 | 145 | it('should revert if the commit period is active', async () => { 146 | // Set constants 147 | const CLAIM_AMOUNT = '10000'; 148 | const FEE_AMOUNT = '1000'; 149 | const VOTE = '0'; 150 | const SALT = '420'; 151 | const DATA = 'i love cats'; 152 | 153 | // Open a new claim on the DS and generate a claim ID for it 154 | const claimNumber = 155 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 156 | 157 | // Generate a secret hash and commit it as a vote 158 | const secretHash = utils.getSecretHash(VOTE, SALT); 159 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 160 | { from: arbiterAlice }); 161 | 162 | try { 163 | // Submit rulling 164 | await delphiVoting.submitRuling(delphiStake.address, claimNumber); 165 | } catch (err) { 166 | assert(utils.isEVMRevert(err), err.toString()); 167 | 168 | return; 169 | } 170 | 171 | assert(false, 'Expected to revert if the commit period is active'); 172 | }); 173 | 174 | it('should revert if the reveal period is active', async () => { 175 | // Set constants 176 | const CLAIM_AMOUNT = '10000'; 177 | const FEE_AMOUNT = '1000'; 178 | const VOTE = '0'; 179 | const SALT = '420'; 180 | const DATA = 'i love cats'; 181 | 182 | // Open a new claim on the DS and generate a claim ID for it 183 | const claimNumber = 184 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 185 | 186 | // Generate a secret hash and commit it as a vote 187 | const secretHash = utils.getSecretHash(VOTE, SALT); 188 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 189 | { from: arbiterAlice }); 190 | 191 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 192 | 193 | try { 194 | // Submit rulling 195 | await delphiVoting.submitRuling(delphiStake.address, claimNumber); 196 | } catch (err) { 197 | assert(utils.isEVMRevert(err), err.toString()); 198 | 199 | return; 200 | } 201 | 202 | assert(false, 'Expected to revert if the reveal period is active'); 203 | }); 204 | 205 | it('should correctly tally the votes', async () => { 206 | // Set constants 207 | const CLAIM_AMOUNT = '10000'; 208 | const FEE_AMOUNT = '1000'; 209 | const VOTE = '1'; 210 | const SALT = '420'; 211 | const DATA = 'i love cats'; 212 | 213 | // Open a new claim on the DS and generate a claim ID for it 214 | const claimNumber = 215 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 216 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 217 | 218 | // Generate a secret hash and commit it as a vote 219 | const secretHash = utils.getSecretHash(VOTE, SALT); 220 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 221 | { from: arbiterAlice }); 222 | 223 | // Increase time past commit period 224 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 225 | 226 | // Reveal vote 227 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 228 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 229 | 230 | // Increase time past reveal period 231 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 232 | 233 | // Submit rulling 234 | await delphiVoting.submitRuling(delphiStake.address, claimNumber); 235 | 236 | // Check if the result of the ruling was equal to VOTE 237 | assert.strictEqual((await delphiVoting.claims(claimId))[2].toString(), VOTE, 'tally vote is not correct'); 238 | }); 239 | 240 | it('should unlock funds in the DelphiStake when the claim is ruled not-justified', 241 | async () => { 242 | // Set constants 243 | const CLAIM_AMOUNT = '10000'; 244 | const FEE_AMOUNT = '1000'; 245 | const VOTE = '1'; // not justified 246 | const SALT = '420'; 247 | const DATA = 'i love cats'; 248 | 249 | // Open a new claim on the DS and generate a claim ID for it 250 | const claimNumber = 251 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, DATA, delphiStake); 252 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 253 | 254 | // Generate a secret hash and commit it as a vote 255 | const secretHash = utils.getSecretHash(VOTE, SALT); 256 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 257 | { from: arbiterAlice }); 258 | 259 | // Increase time past commit period 260 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 261 | 262 | // Reveal vote 263 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 264 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 265 | 266 | // Increase time past reveal period 267 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 268 | 269 | assert.strictEqual((await delphiStake.claimableStake()).toString(), '79000', ''); 270 | 271 | await delphiVoting.submitRuling(delphiStake.address, claimNumber); 272 | 273 | assert.strictEqual((await delphiStake.claimableStake()).toString(), '90000', 274 | 'Funds in the DelphiStake were not unlocked after the ruling'); 275 | }); 276 | }); 277 | }); 278 | -------------------------------------------------------------------------------- /test/js/DelphiStake/openClaim.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | const EIP20 = artifacts.require('EIP20'); 6 | 7 | const utils = require('../utils.js'); 8 | const BN = require('bignumber.js'); 9 | 10 | const conf = utils.getConfig(); 11 | 12 | contract('DelphiStake', (accounts) => { 13 | describe('Function: openClaim', () => { 14 | const [staker, claimant, arbiter, other] = accounts; 15 | 16 | const claimAmount = '1'; 17 | const feeAmount = '10'; 18 | const startingClaims = new BN('0', 10); 19 | 20 | let ds; 21 | let token; 22 | 23 | beforeEach(async () => { 24 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 25 | await token.transfer(claimant, 100000, { from: staker }); 26 | await token.transfer(arbiter, 100000, { from: staker }); 27 | await token.transfer(other, 100000, { from: staker }); 28 | 29 | ds = await DelphiStake.new(); 30 | 31 | await token.approve(ds.address, conf.initialStake, { from: staker }); 32 | 33 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 34 | conf.deadline, arbiter, { from: staker }); 35 | 36 | // whitelist the claimant since most tests 37 | // use this format. Use 'other' for other situations 38 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 39 | }); 40 | 41 | it('should not allow the arbiter to open a claim', async () => { 42 | await ds.whitelistClaimant(arbiter, conf.deadline, { from: staker }); 43 | 44 | await token.approve(ds.address, feeAmount, { from: arbiter }); 45 | 46 | try { 47 | await ds.openClaim(claimAmount, feeAmount, '', { from: arbiter }); 48 | } catch (err) { 49 | assert(utils.isEVMRevert(err), err.toString()); 50 | 51 | const finalClaims = await ds.getNumClaims.call(); 52 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 53 | 'claims counter incremented mysteriously'); 54 | 55 | return; 56 | } 57 | 58 | assert(false, 'Expected claim by arbiter to fail'); 59 | }); 60 | 61 | it('should not allow the staker to open a claim', async () => { 62 | await ds.whitelistClaimant(staker, conf.deadline, { from: staker }); 63 | 64 | await token.approve(ds.address, feeAmount, { from: staker }); 65 | 66 | try { 67 | await ds.openClaim(claimAmount, feeAmount, '', { from: staker }); 68 | } catch (err) { 69 | assert(utils.isEVMRevert(err), err.toString()); 70 | 71 | const finalClaims = await ds.getNumClaims.call(); 72 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 73 | 'claims counter incremented mysteriously'); 74 | 75 | return; 76 | } 77 | 78 | assert(false, 'expected claim by staker to fail'); 79 | }); 80 | 81 | it('should not allow a whitelisted individual to open a claim after their deadline', async () => { 82 | await ds.whitelistClaimant(other, '1', { from: staker }); 83 | 84 | await token.approve(ds.address, feeAmount, { from: other }); 85 | 86 | try { 87 | await ds.openClaim(claimAmount, feeAmount, '', { from: other }); 88 | } catch (err) { 89 | assert(utils.isEVMRevert(err), err.toString()); 90 | 91 | const finalClaims = await ds.getNumClaims.call(); 92 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 93 | 'claims counter incremented mysteriously'); 94 | 95 | return; 96 | } 97 | 98 | assert(false, 'expected to not allow a whitelisted individual to open a claim after their deadline'); 99 | }); 100 | 101 | it('should not allow a non-whitelisted individual to open a claim', async () => { 102 | await token.approve(ds.address, feeAmount, { from: other }); 103 | 104 | try { 105 | await ds.openClaim(claimAmount, feeAmount, '', { from: other }); 106 | } catch (err) { 107 | assert(utils.isEVMRevert(err), err.toString()); 108 | 109 | const finalClaims = await ds.getNumClaims.call(); 110 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 111 | 'claims counter incremented mysteriously'); 112 | 113 | return; 114 | } 115 | 116 | assert(false, 'expected claim by non-whitelisted individual to fail'); 117 | }); 118 | 119 | it('should revert if _fee is smaller than the minimum', async () => { 120 | await token.approve(ds.address, parseInt(conf.minFee, 10) - 1, { from: claimant }); 121 | 122 | try { 123 | await ds.openClaim(claimAmount, conf.minFee - 1, '', { from: claimant }); 124 | } catch (err) { 125 | assert(utils.isEVMRevert(err), err.toString()); 126 | 127 | const finalClaims = await ds.getNumClaims.call(); 128 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 129 | 'claims counter incremented mysteriously'); 130 | 131 | return; 132 | } 133 | 134 | assert(false, 'expected claim for more than is available in stake to fail'); 135 | }); 136 | 137 | it('should revert if _amount + _fee is greater than the available stake', async () => { 138 | const amount = (parseInt(conf.minFee, 10) + parseInt(conf.initialStake, 10) + 1).toString(10); 139 | await token.approve(ds.address, amount, { from: claimant }); 140 | 141 | try { 142 | await ds.openClaim(claimAmount, amount, '', { from: claimant }); 143 | } catch (err) { 144 | assert(utils.isEVMRevert(err), err.toString()); 145 | 146 | const finalClaims = await ds.getNumClaims.call(); 147 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 148 | 'claims counter incremented mysteriously'); 149 | 150 | return; 151 | } 152 | 153 | assert(false, 'expected claim for more than is available in stake to fail'); 154 | }); 155 | 156 | it('should revert if the fee is not transferred with the transaction', async () => { 157 | try { 158 | await ds.openClaim(claimAmount, feeAmount, '', { from: claimant }); 159 | } catch (err) { 160 | assert(utils.isEVMRevert(err), err.toString()); 161 | 162 | const finalClaims = await ds.openClaims.call(); 163 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 164 | 'claims counter incremented mysteriously'); 165 | 166 | return; 167 | } 168 | 169 | assert(false, 'expected revert if the fee is not transferred with the transaction'); 170 | }); 171 | 172 | it('should increment the getNumClaims counter', async () => { 173 | await token.approve(ds.address, feeAmount, { from: claimant }); 174 | 175 | await ds.openClaim(claimAmount, feeAmount, '', { from: claimant }); 176 | 177 | const finalClaims = await ds.getNumClaims(); 178 | assert.strictEqual(startingClaims.add(new BN('1', 10)).toString(10), 179 | finalClaims.toString(10), 180 | 'claim counter not incremented as-expected'); 181 | }); 182 | 183 | it('should add a new claim to the claims array and properly initialize its properties', 184 | async () => { 185 | await token.approve(ds.address, feeAmount, { from: claimant }); 186 | 187 | const claimId = await ds.getNumClaims(); 188 | 189 | await ds.openClaim(claimAmount, feeAmount, 'newclaim', { from: claimant }); 190 | 191 | const claim = await ds.claims.call(claimId); 192 | 193 | assert.strictEqual(claim[0], claimant, 'initialized claimant incorrectly'); 194 | assert.strictEqual(claim[1].toString(10), claimAmount, 'initialized claim amount incorrectly'); 195 | assert.strictEqual(claim[2].toString(10), feeAmount, 'initialized claim fee incorrectly'); 196 | assert.strictEqual(claim[3].toString(10), '0', 'initialized claim surplus fee incorrectly'); 197 | assert.strictEqual(claim[4], 'newclaim', 'initialized claim data incorrectly'); 198 | assert.strictEqual(claim[5].toString(10), '0', 'initialized claim ruling incorrectly'); 199 | assert.strictEqual(claim[6], false, 'initialized ruled bool incorrectly'); 200 | assert.strictEqual(claim[7], false, 'initialized settlementFailed incorrectly'); 201 | }); 202 | 203 | it('should increment the openClaims.call counter', async () => { 204 | await token.approve(ds.address, feeAmount, { from: claimant }); 205 | 206 | await ds.openClaim(claimAmount, feeAmount, '', { from: claimant }); 207 | 208 | const finalClaims = await ds.openClaims(); 209 | assert.strictEqual(startingClaims.add(new BN('1', 10)).toString(10), 210 | finalClaims.toString(10), 211 | 'claim counter not incremented as-expected'); 212 | }); 213 | 214 | it('should decrement the stakers stake by amount + fee', async () => { 215 | await token.approve(ds.address, feeAmount, { from: claimant }); 216 | 217 | const startingStake = await ds.claimableStake.call(); 218 | 219 | await ds.openClaim(claimAmount, feeAmount, '', { from: claimant }); 220 | 221 | const finalStake = await ds.claimableStake(); 222 | assert.strictEqual(startingStake.sub(new BN(claimAmount, 10).add(feeAmount)).toString(10), 223 | finalStake.toString(10), 224 | 'stake was not decremented as-expected when a new claim was opened'); 225 | 226 | const newBalance = await token.balanceOf(ds.address); 227 | assert.strictEqual(startingStake.add(feeAmount).toString(10), newBalance.toString(10), 228 | 'balance does not reflect the originally deposited funds and additional fee'); 229 | }); 230 | 231 | it('should emit a NewClaim event', async () => { 232 | await token.approve(ds.address, feeAmount, { from: claimant }); 233 | 234 | await ds.openClaim(claimAmount, feeAmount, '', { from: claimant }).then((status) => { 235 | assert.strictEqual('ClaimOpened', status.logs[0].event, 'did not emit the NewClaim event'); 236 | }); 237 | }); 238 | 239 | it('should append claims to the end of the claim array, without overwriting earlier claims', async () => { 240 | await token.approve(ds.address, feeAmount, { from: claimant }); 241 | await ds.openClaim(claimAmount, feeAmount, 'claim1', { from: claimant }); 242 | 243 | await token.approve(ds.address, feeAmount, { from: claimant }); 244 | await ds.openClaim(claimAmount, feeAmount, 'claim2', { from: claimant }); 245 | 246 | await token.approve(ds.address, feeAmount, { from: claimant }); 247 | await ds.openClaim(claimAmount, feeAmount, 'claim3', { from: claimant }); 248 | 249 | const claim1 = await ds.claims.call('0'); 250 | 251 | assert.strictEqual(claim1[0], claimant, 'initialized claimant incorrectly'); 252 | assert.strictEqual(claim1[1].toString(10), claimAmount, 'initialized claim amount incorrectly'); 253 | assert.strictEqual(claim1[2].toString(10), feeAmount, 'initialized claim fee incorrectly'); 254 | assert.strictEqual(claim1[3].toString(10), '0', 'initialized claim surplus fee incorrectly'); 255 | assert.strictEqual(claim1[4], 'claim1', 'initialized claim data incorrectly'); 256 | assert.strictEqual(claim1[5].toString(10), '0', 'initialized claim ruling incorrectly'); 257 | assert.strictEqual(claim1[6], false, 'initialized ruled bool incorrectly'); 258 | assert.strictEqual(claim1[7], false, 'initialized settlementFailed incorrectly'); 259 | 260 | const claim2 = await ds.claims.call('1'); 261 | 262 | assert.strictEqual(claim2[0], claimant, 'initialized claimant incorrectly'); 263 | assert.strictEqual(claim2[1].toString(10), claimAmount, 'initialized claim amount incorrectly'); 264 | assert.strictEqual(claim2[2].toString(10), feeAmount, 'initialized claim fee incorrectly'); 265 | assert.strictEqual(claim2[3].toString(10), '0', 'initialized claim surplus fee incorrectly'); 266 | assert.strictEqual(claim2[4], 'claim2', 'initialized claim data incorrectly'); 267 | assert.strictEqual(claim2[5].toString(10), '0', 'initialized claim ruling incorrectly'); 268 | assert.strictEqual(claim2[6], false, 'initialized ruled bool incorrectly'); 269 | assert.strictEqual(claim2[7], false, 'initialized settlementFailed incorrectly'); 270 | 271 | const claim3 = await ds.claims.call('2'); 272 | 273 | assert.strictEqual(claim3[0], claimant, 'initialized claimant incorrectly'); 274 | assert.strictEqual(claim3[1].toString(10), claimAmount, 'initialized claim amount incorrectly'); 275 | assert.strictEqual(claim3[2].toString(10), feeAmount, 'initialized claim fee incorrectly'); 276 | assert.strictEqual(claim3[3].toString(10), '0', 'initialized claim surplus fee incorrectly'); 277 | assert.strictEqual(claim3[4], 'claim3', 'initialized claim data incorrectly'); 278 | assert.strictEqual(claim3[5].toString(10), '0', 'initialized claim ruling incorrectly'); 279 | assert.strictEqual(claim3[6], false, 'initialized ruled bool incorrectly'); 280 | assert.strictEqual(claim3[7], false, 'initialized settlementFailed incorrectly'); 281 | }); 282 | }); 283 | }); 284 | -------------------------------------------------------------------------------- /test/js/DelphiStake/openClaimWithoutSettlement.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiStake = artifacts.require('DelphiStake'); 5 | const EIP20 = artifacts.require('EIP20'); 6 | 7 | const utils = require('../utils.js'); 8 | const BN = require('bignumber.js'); 9 | 10 | const conf = utils.getConfig(); 11 | 12 | contract('DelphiStake', (accounts) => { 13 | describe('Function: openClaimWithoutSettlement', () => { 14 | const [staker, claimant, arbiter, other] = accounts; 15 | 16 | const claimAmount = '1'; 17 | const startingClaims = new BN('0', 10); 18 | 19 | let ds; 20 | let token; 21 | 22 | beforeEach(async () => { 23 | token = await EIP20.new(1000000, 'Delphi Tokens', 18, 'DELPHI', { from: staker }); 24 | await token.transfer(claimant, 100000, { from: staker }); 25 | await token.transfer(arbiter, 100000, { from: staker }); 26 | await token.transfer(other, 100000, { from: staker }); 27 | 28 | ds = await DelphiStake.new(); 29 | 30 | await token.approve(ds.address, conf.initialStake, { from: staker }); 31 | 32 | await ds.initDelphiStake(staker, conf.initialStake, token.address, conf.minFee, conf.data, 33 | conf.deadline, arbiter, { from: staker }); 34 | 35 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 36 | }); 37 | 38 | it('should not allow the arbiter to open a claim', async () => { 39 | await ds.whitelistClaimant(arbiter, conf.deadline, { from: staker }); 40 | 41 | await token.approve(ds.address, conf.minFee, { from: arbiter }); 42 | 43 | try { 44 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, '', { from: arbiter }); 45 | } catch (err) { 46 | assert(utils.isEVMRevert(err), err.toString()); 47 | 48 | const finalClaims = await ds.getNumClaims.call(); 49 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 50 | 'claims counter incremented mysteriously'); 51 | 52 | return; 53 | } 54 | 55 | assert(false, 'Expected claim by arbiter to fail'); 56 | }); 57 | 58 | it('should not allow the staker to open a claim', async () => { 59 | await ds.whitelistClaimant(staker, conf.deadline, { from: staker }); 60 | 61 | await token.approve(ds.address, conf.minFee, { from: staker }); 62 | 63 | try { 64 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, '', { from: staker }); 65 | } catch (err) { 66 | assert(utils.isEVMRevert(err), err.toString()); 67 | 68 | const finalClaims = await ds.getNumClaims.call(); 69 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 70 | 'claims counter incremented mysteriously'); 71 | 72 | return; 73 | } 74 | 75 | assert(false, 'expected claim by staker to fail'); 76 | }); 77 | 78 | it('should not allow a non-whitelisted individual to open a claim', async () => { 79 | await token.approve(ds.address, conf.minFee, { from: other }); 80 | 81 | try { 82 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, '', { from: claimant }); 83 | } catch (err) { 84 | assert(utils.isEVMRevert(err), err.toString()); 85 | 86 | const finalClaims = await ds.getNumClaims.call(); 87 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 88 | 'claims counter incremented mysteriously'); 89 | 90 | return; 91 | } 92 | 93 | assert(false, 'expected claim by non-whitelisted individual to fail'); 94 | }); 95 | 96 | it('should revert if someone is attempting to open a claim after the deadline', async () => { 97 | await ds.whitelistClaimant(other, parseInt(conf.deadline, 10) - 1, { from: staker }); 98 | 99 | await token.approve(ds.address, conf.minFee, { from: claimant }); 100 | 101 | try { 102 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, 'claim1', { from: other }); 103 | } catch (err) { 104 | assert(utils.isEVMRevert(err), err.toString()); 105 | return; 106 | } 107 | 108 | assert(false, 'expected revert if someone is attempting to open a claim after the deadline'); 109 | }); 110 | 111 | it('should revert if _fee is smaller than the minimum', async () => { 112 | await ds.whitelistClaimant(claimant, conf.deadline, { from: staker }); 113 | 114 | const feeAmount = parseInt(conf.minFee, 10) - 1; 115 | 116 | await token.approve(ds.address, feeAmount, { from: claimant }); 117 | 118 | try { 119 | await ds.openClaimWithoutSettlement(claimAmount, feeAmount, '', { from: claimant }); 120 | } catch (err) { 121 | assert(utils.isEVMRevert(err), err.toString()); 122 | 123 | const finalClaims = await ds.getNumClaims.call(); 124 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 125 | 'claims counter incremented mysteriously'); 126 | 127 | return; 128 | } 129 | 130 | assert(false, 'expected claim for more than is available in stake to fail'); 131 | }); 132 | 133 | it('should revert if _amount + _fee is greater than the available stake', async () => { 134 | const amount = parseInt(conf.minFee, 10) + parseInt(conf.initialStake, 10) + 1; 135 | 136 | await token.approve(ds.address, amount, { from: claimant }); 137 | 138 | try { 139 | await ds.openClaimWithoutSettlement(claimAmount, amount, '', { from: claimant }); 140 | } catch (err) { 141 | assert(utils.isEVMRevert(err), err.toString()); 142 | 143 | const finalClaims = await ds.getNumClaims.call(); 144 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 145 | 'claims counter incremented mysteriously'); 146 | 147 | return; 148 | } 149 | 150 | assert(false, 'expected claim for more than is available in stake to fail'); 151 | }); 152 | 153 | it('should revert if the fee is not transferred with the transaction', async () => { 154 | try { 155 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, '', { from: claimant }); 156 | } catch (err) { 157 | assert(utils.isEVMRevert(err), err.toString()); 158 | 159 | const finalClaims = await ds.openClaims.call(); 160 | assert.strictEqual(startingClaims.toString(10), finalClaims.toString(10), 161 | 'claims counter incremented mysteriously'); 162 | 163 | return; 164 | } 165 | 166 | assert(false, 'expected revert if the fee is not transferred with the transaction'); 167 | }); 168 | 169 | it('should increment the getNumClaims counter', async () => { 170 | await token.approve(ds.address, conf.minFee, { from: claimant }); 171 | 172 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, '', { from: claimant }); 173 | 174 | const finalClaims = await ds.getNumClaims(); 175 | assert.strictEqual(startingClaims.add(new BN('1', 10)).toString(10), 176 | finalClaims.toString(10), 177 | 'claim counter not incremented as-expected'); 178 | }); 179 | 180 | it('should add a new claim to the claims array and properly initialize its properties', 181 | async () => { 182 | await token.approve(ds.address, conf.minFee, { from: claimant }); 183 | 184 | const claimId = await ds.getNumClaims(); 185 | 186 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, 'newclaim', { from: claimant }); 187 | 188 | const claim = await ds.claims.call(claimId); 189 | 190 | assert.strictEqual(claim[0], claimant, 'initialized claimant incorrectly'); 191 | assert.strictEqual(claim[1].toString(10), claimAmount, 'initialized claim amount incorrectly'); 192 | assert.strictEqual(claim[2].toString(10), conf.minFee, 'initialized claim fee incorrectly'); 193 | assert.strictEqual(claim[3].toString(10), '0', 'initialized claim surplus fee incorrectly'); 194 | assert.strictEqual(claim[4], 'newclaim', 'initialized claim data incorrectly'); 195 | assert.strictEqual(claim[5].toString(10), '0', 'initialized claim ruling incorrectly'); 196 | assert.strictEqual(claim[6], false, 'initialized ruled bool incorrectly'); 197 | assert.strictEqual(claim[7], true, 'initialized settlementFailed incorrectly'); 198 | }); 199 | 200 | it('should increment the openClaims.call counter', async () => { 201 | await token.approve(ds.address, conf.minFee, { from: claimant }); 202 | 203 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, '', { from: claimant }); 204 | 205 | const finalClaims = await ds.openClaims(); 206 | assert.strictEqual(startingClaims.add(new BN('1', 10)).toString(10), 207 | finalClaims.toString(10), 208 | 'claim counter not incremented as-expected'); 209 | }); 210 | 211 | it('should decrement the stakers stake by amount + fee', async () => { 212 | await token.approve(ds.address, conf.minFee, { from: claimant }); 213 | 214 | const startingStake = await ds.claimableStake.call(); 215 | 216 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, '', { from: claimant }); 217 | 218 | const finalStake = await ds.claimableStake(); 219 | assert.strictEqual(startingStake.sub(new BN(claimAmount, 10).add(conf.minFee)).toString(10), 220 | finalStake.toString(10), 221 | 'stake was not decremented as-expected when a new claim was opened'); 222 | 223 | const newBalance = await token.balanceOf(ds.address); 224 | assert.strictEqual(startingStake.add(conf.minFee).toString(10), newBalance.toString(10), 225 | 'balance does not reflect the originally deposited funds and additional fee'); 226 | }); 227 | 228 | it('should emit a NewClaim event', async () => { 229 | await token.approve(ds.address, conf.minFee, { from: claimant }); 230 | 231 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, 'claim1', { from: claimant }).then((status) => { 232 | assert.strictEqual('ClaimOpened', status.logs[0].event, 'did not emit the NewClaim event'); 233 | }); 234 | }); 235 | 236 | it('should append claims to the end of the claim array, without overwriting earlier claims', async () => { 237 | await token.approve(ds.address, conf.minFee, { from: claimant }); 238 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, 'claim1', { from: claimant }); 239 | 240 | await token.approve(ds.address, conf.minFee, { from: claimant }); 241 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, 'claim2', { from: claimant }); 242 | 243 | await token.approve(ds.address, conf.minFee, { from: claimant }); 244 | await ds.openClaimWithoutSettlement(claimAmount, conf.minFee, 'claim3', { from: claimant }); 245 | 246 | const claim1 = await ds.claims.call('0'); 247 | 248 | assert.strictEqual(claim1[0], claimant, 'initialized claimant incorrectly'); 249 | assert.strictEqual(claim1[1].toString(10), claimAmount, 'initialized claim amount incorrectly'); 250 | assert.strictEqual(claim1[2].toString(10), conf.minFee, 'initialized claim fee incorrectly'); 251 | assert.strictEqual(claim1[3].toString(10), '0', 'initialized claim surplus fee incorrectly'); 252 | assert.strictEqual(claim1[4], 'claim1', 'initialized claim data incorrectly'); 253 | assert.strictEqual(claim1[5].toString(10), '0', 'initialized claim ruling incorrectly'); 254 | assert.strictEqual(claim1[6], false, 'initialized ruled bool incorrectly'); 255 | assert.strictEqual(claim1[7], true, 'initialized settlementFailed incorrectly'); 256 | 257 | const claim2 = await ds.claims.call('1'); 258 | 259 | assert.strictEqual(claim2[0], claimant, 'initialized claimant incorrectly'); 260 | assert.strictEqual(claim2[1].toString(10), claimAmount, 'initialized claim amount incorrectly'); 261 | assert.strictEqual(claim2[2].toString(10), conf.minFee, 'initialized claim fee incorrectly'); 262 | assert.strictEqual(claim2[3].toString(10), '0', 'initialized claim surplus fee incorrectly'); 263 | assert.strictEqual(claim2[4], 'claim2', 'initialized claim data incorrectly'); 264 | assert.strictEqual(claim2[5].toString(10), '0', 'initialized claim ruling incorrectly'); 265 | assert.strictEqual(claim2[6], false, 'initialized ruled bool incorrectly'); 266 | assert.strictEqual(claim2[7], true, 'initialized settlementFailed incorrectly'); 267 | 268 | const claim3 = await ds.claims.call('2'); 269 | 270 | assert.strictEqual(claim3[0], claimant, 'initialized claimant incorrectly'); 271 | assert.strictEqual(claim3[1].toString(10), claimAmount, 'initialized claim amount incorrectly'); 272 | assert.strictEqual(claim3[2].toString(10), conf.minFee, 'initialized claim fee incorrectly'); 273 | assert.strictEqual(claim3[3].toString(10), '0', 'initialized claim surplus fee incorrectly'); 274 | assert.strictEqual(claim3[4], 'claim3', 'initialized claim data incorrectly'); 275 | assert.strictEqual(claim3[5].toString(10), '0', 'initialized claim ruling incorrectly'); 276 | assert.strictEqual(claim3[6], false, 'initialized ruled bool incorrectly'); 277 | assert.strictEqual(claim3[7], true, 'initialized settlementFailed incorrectly'); 278 | }); 279 | }); 280 | }); 281 | -------------------------------------------------------------------------------- /contracts/DelphiStake.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "tokens/eip20/EIP20.sol"; 4 | 5 | 6 | contract DelphiStake { 7 | 8 | event ClaimantWhitelisted(address _claimant); 9 | event ClaimOpened(address _claimant, uint _claimId); 10 | event FeeIncreased(address _increasedBy, uint _claimId, uint _amount); 11 | event SettlementProposed(address _proposedBy, uint _claimId, uint _settlementId); 12 | event SettlementAccepted(address _acceptedBy, uint _claimId, uint _settlementId); 13 | event SettlementFailed(address _failedBy, uint _claimId); 14 | event ClaimRuled(uint _claimId); 15 | event ReleaseTimeIncreased(uint _stakeReleaseTime); 16 | event StakeWithdrawn(); 17 | event StakeIncreased(address _increasedBy, uint _value); 18 | 19 | 20 | struct Claim { 21 | address claimant; 22 | uint amount; 23 | uint fee; 24 | uint surplusFee; 25 | string data; 26 | uint ruling; 27 | bool ruled; 28 | bool settlementFailed; 29 | } 30 | 31 | struct Settlement { 32 | uint amount; 33 | bool stakerAgrees; 34 | bool claimantAgrees; 35 | } 36 | 37 | address public masterCopy; // THIS MUST ALWAYS BE IN THE FIRST STORAGE SLOT 38 | 39 | uint public minimumFee; 40 | 41 | uint public claimableStake; 42 | EIP20 public token; 43 | 44 | string public data; 45 | 46 | address public staker; 47 | address public arbiter; 48 | 49 | uint public stakeReleaseTime; 50 | 51 | Claim[] public claims; 52 | uint public openClaims; 53 | mapping(uint => Settlement[]) public settlements; 54 | 55 | mapping(address => uint) public whitelistedDeadlines; 56 | 57 | modifier onlyStaker(){ 58 | require(msg.sender == staker); 59 | _; 60 | } 61 | 62 | modifier validClaimID(uint _claimId){ 63 | require(_claimId < claims.length); 64 | _; 65 | } 66 | 67 | modifier validSettlementId(uint _claimId, uint _settlementId){ 68 | require(_settlementId < settlements[_claimId].length); 69 | _; 70 | } 71 | 72 | modifier notStakerOrArbiter(){ 73 | require(msg.sender != staker && msg.sender != arbiter); 74 | _; 75 | } 76 | 77 | modifier onlyArbiter(){ 78 | require(msg.sender == arbiter); 79 | _; 80 | } 81 | 82 | modifier onlyClaimant(uint _claimId){ 83 | require(msg.sender == claims[_claimId].claimant); 84 | _; 85 | } 86 | 87 | modifier largeEnoughFee(uint _newFee){ 88 | require(_newFee >= minimumFee); 89 | _; 90 | } 91 | 92 | modifier claimNotRuled(uint _claimId){ 93 | require(!claims[_claimId].ruled); 94 | _; 95 | } 96 | 97 | modifier stakerCanPay(uint _amount, uint _fee){ 98 | require(claimableStake >= (_amount + _fee)); 99 | _; 100 | } 101 | 102 | modifier settlementDidFail(uint _claimId){ 103 | require(claims[_claimId].settlementFailed); 104 | _; 105 | } 106 | 107 | modifier settlementDidNotFail(uint _claimId){ 108 | require(!claims[_claimId].settlementFailed); 109 | _; 110 | } 111 | 112 | modifier onlyStakerOrClaimant(uint _claimId){ 113 | require(msg.sender == staker || msg.sender == claims[_claimId].claimant); 114 | _; 115 | } 116 | 117 | modifier onlyWhitelistedClaimant(){ 118 | require(whitelistedDeadlines[msg.sender] >= now); 119 | _; 120 | } 121 | 122 | modifier noOpenClaims(){ 123 | require(openClaims == 0); 124 | _; 125 | } 126 | 127 | modifier stakeIsReleased(){ 128 | require (now > stakeReleaseTime); 129 | _; 130 | } 131 | 132 | /* 133 | @dev when creating a new Delphi Stake using a proxy contract architecture, a user must 134 | initialialize their stake, depositing their tokens 135 | @param _staker the address which is creating the stake through the proxy contract 136 | @param _value the value of the stake in token units 137 | @param _token the address of the token being deposited 138 | @param _minimumFee the minimum fee which must be deposited by both parties for each claim 139 | @param _data a content hash of the relevant associated data describing the stake 140 | @param _claimDeadline the deadline for opening new cliams; the earliest moment that 141 | a stake can be withdrawn by the staker 142 | @param _arbiter the address which is able to rule on open claims 143 | */ 144 | function initDelphiStake(address _staker, uint _value, EIP20 _token, uint _minimumFee, string _data, uint _stakeReleaseTime, address _arbiter) 145 | public 146 | { 147 | require(_stakeReleaseTime > now); 148 | 149 | // This function can only be called if it hasn't been called before, or if the token was 150 | // set to 0 when it was called previously. 151 | require(token == address(0)); 152 | 153 | // Require reasonable inputs 154 | require(_arbiter != address(0)); 155 | 156 | // Revert if the specified value to stake cannot be transferred in 157 | require(_token.transferFrom(msg.sender, this, _value)); 158 | 159 | // Initialize contract storage. 160 | claimableStake = _value; 161 | token = _token; 162 | minimumFee = _minimumFee; 163 | data = _data; 164 | stakeReleaseTime = _stakeReleaseTime; 165 | arbiter = _arbiter; 166 | staker = _staker; 167 | } 168 | 169 | /* 170 | @dev before going into business with a staker, the staker's counterparty should expect to be 171 | "whitelisted for claims" such that a clear path exists for the adjudication of disputes should 172 | one arise in the course of events. 173 | @param _claimant an address which, once whitelisted, can make claims against this stake 174 | @param _deadline the timestamp before which the whitelisted individual may open a claim 175 | */ 176 | function whitelistClaimant(address _claimant, uint _deadline) 177 | public 178 | onlyStaker 179 | { 180 | // the new deadline should be greater than the existing one 181 | require(_deadline >= whitelistedDeadlines[_claimant]); 182 | 183 | // Whitelist the claimant by setting their entry in the whitelistedDeadlines mapping to their deadline 184 | whitelistedDeadlines[_claimant] = _deadline; 185 | 186 | // Emit an event noting that this claimant was whitelisted 187 | ClaimantWhitelisted(_claimant); 188 | } 189 | 190 | /* 191 | @dev a whitelisted claimant can use this function to make a claim for remuneration. Once 192 | opened, an opportunity for pre-arbitration settlement will commence, but claims cannot be 193 | unilaterally cancelled. 194 | @param _amount the size of the claim being made, denominated in the stake's token. Must be less 195 | than or equal to the current amount of stake not locked up in other disputes, minus the fee deposited. 196 | @param _fee the size of the fee, denominated in the stake's token, to be offered to the arbiter 197 | as compensation for their service in adjudicating the dispute. If the claimant loses the claim, 198 | they lose this fee. 199 | @param _data an arbitrary string, perhaps an IPFS hash, containing data substantiating the 200 | basis for the claim. 201 | */ 202 | function openClaim(uint _amount, uint _fee, string _data) 203 | public 204 | notStakerOrArbiter 205 | stakerCanPay(_amount, _fee) 206 | onlyWhitelistedClaimant 207 | largeEnoughFee(_fee) 208 | { 209 | // Transfer the fee into the DelphiStake 210 | require(token.transferFrom(msg.sender, this, _fee)); 211 | 212 | // Add a new claim to the claims array and increment the openClaims counter. Because there 213 | // is necessarily at least one open claim now, pause any active withdrawal (lockup) 214 | // countdown. 215 | claims.push(Claim(msg.sender, _amount, _fee, 0, _data, 0, false, false)); 216 | openClaims ++; 217 | 218 | // The claim amount and claim fee are reserved for this particular claim until the arbiter 219 | // rules 220 | claimableStake -= (_amount + _fee); 221 | 222 | // Emit an event that a claim was opened by the message sender (not the claimant), and 223 | // include the claim's ID. 224 | ClaimOpened(msg.sender, claims.length - 1); 225 | } 226 | 227 | /* 228 | @dev a whitelisted claimant can use this function to make a claim for remuneration. Opened claims 229 | will proceed directly to full arbitration, when their claims can be ruled upon. 230 | @param _claimant the entity which will act as the claimant in the course of the adjudication. 231 | @param _amount the size of the claim being made, denominated in the stake's token. Must be less 232 | than or equal to the current amount of stake not locked up in other disputes, minus the fee deposited. 233 | @param _fee the size of the fee, denominated in the stake's token, to be offered to the arbiter 234 | as compensation for their service in adjudicating the dispute. If the claimant loses the claim, 235 | they lose this fee. 236 | @param _data an arbitrary string, perhaps an IPFS hash, containing data substantiating the 237 | basis for the claim. 238 | */ 239 | function openClaimWithoutSettlement(uint _amount, uint _fee, string _data) 240 | public 241 | notStakerOrArbiter 242 | stakerCanPay(_amount, _fee) 243 | onlyWhitelistedClaimant 244 | largeEnoughFee(_fee) 245 | { 246 | require(token.transferFrom(msg.sender, this, _fee)); 247 | claims.push(Claim(msg.sender, _amount, _fee, 0, _data, 0, false, true)); 248 | openClaims ++; 249 | 250 | // The claim amount and claim fee are reserved for this particular claim until the arbiter 251 | // rules 252 | claimableStake -= (_amount + _fee); 253 | // the claim amount and claim fee are locked up in this contract until the arbiter rules 254 | 255 | ClaimOpened(msg.sender, claims.length - 1); 256 | } 257 | 258 | /* 259 | @dev increase the arbiter fee being offered for this claim. Regardless of how the claim is 260 | ruled, this fee is not returned. The fee cannot be increased while still in the settlement 261 | phase, or after a ruling has been submitted. 262 | @param _claimId the ID of the claim to boost the fee for 263 | @param _amount the amount, denominated in the stake's token, to increase the fee by 264 | */ 265 | function increaseClaimFee(uint _claimId, uint _amount) 266 | public 267 | validClaimID(_claimId) 268 | claimNotRuled(_claimId) 269 | settlementDidFail(_claimId) 270 | { 271 | // Transfer tokens from the message sender to this contract and increment the surplusFee 272 | // record for this claim by the amount transferred. 273 | require(token.transferFrom(msg.sender, this, _amount)); 274 | claims[_claimId].surplusFee += _amount; 275 | 276 | // Emit a FeeIncreased event including data on who increased the fee, which claim the fee was 277 | // increased for, and by what amount. 278 | FeeIncreased(msg.sender, _claimId, _amount); 279 | } 280 | 281 | /* 282 | @dev once a claim has been opened, either party can propose settlements to resolve the matter 283 | without getting the arbiter involved. If a settlement is accepted, both parties recover the fee 284 | they would otherwise forfeit in the arbitration process. 285 | @param _claimId the claim to propose a settlement amount for 286 | @param _amount the size of the proposed settlement, denominated in the stake's token 287 | */ 288 | function proposeSettlement(uint _claimId, uint _amount) 289 | public 290 | validClaimID(_claimId) 291 | onlyStakerOrClaimant(_claimId) 292 | settlementDidNotFail(_claimId) 293 | { 294 | // Only allow settlements for up to the amount that has been reserved (locked) for this claim 295 | require((claims[_claimId].amount + claims[_claimId].fee) >= _amount); 296 | 297 | // Add a new settlement to this claim's settlement array. Depending on who proposed the 298 | // settlement, set their "agrees" flag to true upon proposal. 299 | if (msg.sender == staker){ 300 | settlements[_claimId].push(Settlement(_amount, true, false)); 301 | } else { 302 | settlements[_claimId].push(Settlement(_amount, false, true)); 303 | } 304 | 305 | // Emit an event including the settlement proposed, the claimID the settlement is proposed 306 | // for, and the settlement ID. 307 | SettlementProposed(msg.sender, _claimId, settlements[_claimId].length - 1); 308 | } 309 | 310 | /* 311 | @dev once either party in a claim has proposed a settlement, the opposite party can choose to 312 | accept the settlement. The settlement proposer implicitly accepts, so only the counterparty 313 | needs to invoke this function. 314 | @param _claimId the ID of the claim to accept a settlement for 315 | @param _settlementId the ID of the specific settlement to accept for the specified claim 316 | */ 317 | function acceptSettlement(uint _claimId, uint _settlementId) 318 | public 319 | validClaimID(_claimId) 320 | validSettlementId(_claimId, _settlementId) 321 | onlyStakerOrClaimant(_claimId) 322 | settlementDidNotFail(_claimId) 323 | { 324 | Settlement storage settlement = settlements[_claimId][_settlementId]; 325 | Claim storage claim = claims[_claimId]; 326 | 327 | // Depending on who sent this message, set their agreement flag in the settlement to true 328 | if (msg.sender == staker){ 329 | settlement.stakerAgrees = true; 330 | } else { 331 | settlement.claimantAgrees = true; 332 | } 333 | 334 | // Check if all conditions are met for the settlement to be agreed, and revert otherwise. 335 | // For a settlement to be agreed, both the staker and claimaint must accept the settlement, 336 | // settlement must not have been rejected previously by either party, and the claim must not 337 | // be ruled. Claims are ruled for which settlements have been accepted, so this prevents 338 | // multiple settlements from being accepted for a single claim. 339 | require (settlement.claimantAgrees && 340 | settlement.stakerAgrees && 341 | !claim.settlementFailed && 342 | !claim.ruled); 343 | 344 | // Set this claim's ruled flag to true to prevent further actions (settlements or 345 | // arbitration) being taken against this claim. 346 | claim.ruled = true; 347 | 348 | // Increase the stake's claimable stake by the claim amount and fee, minus the agreed 349 | // settlement amount. Then decrement the openClaims counter, since this claim is resolved. 350 | claimableStake += (claim.amount + claim.fee - settlement.amount); 351 | openClaims --; 352 | 353 | // Transfer to the claimant the settlement amount, plus the fee they deposited. 354 | require(token.transfer(claim.claimant, (settlement.amount + claim.fee))); 355 | 356 | // Emit an event including who accepted the settlement, the claimId and the settlementId 357 | SettlementAccepted(msg.sender, _claimId, _settlementId); 358 | } 359 | 360 | /* 361 | @dev Either party in a claim can call settlementFailed at any time to move the claim from 362 | settlement to arbitration. 363 | @param _claimId the ID of the claim to reject settlement for 364 | */ 365 | function settlementFailed(uint _claimId) 366 | public 367 | validClaimID(_claimId) 368 | onlyStakerOrClaimant(_claimId) 369 | settlementDidNotFail(_claimId) 370 | { 371 | // Set the claim's settlementFailed flag to true, preventing further settlement proposals 372 | // and settlement agreements. 373 | claims[_claimId].settlementFailed = true; 374 | 375 | // Emit an event stating who rejected the settlement, and for which claim settlement was 376 | // rejected. 377 | SettlementFailed(msg.sender, _claimId); 378 | } 379 | 380 | /* 381 | @dev This function can only be invoked by the stake's arbiter, and is used to resolve the 382 | claim. Invoking this function will rule the claim and pay out the appropriate parties. 383 | @param _claimId The ID of the claim to submit the ruling for 384 | @param _ruling The ruling. 0 if the claim is justified, 1 if the claim is not justified, 2 if 385 | the claim is collusive (the claimant is the staker or an ally of the staker), or 3 if the claim 386 | cannot be ruled for any reason. 387 | */ 388 | function ruleOnClaim(uint _claimId, uint _ruling) 389 | public 390 | onlyArbiter 391 | validClaimID(_claimId) 392 | claimNotRuled(_claimId) 393 | settlementDidFail(_claimId) 394 | { 395 | Claim storage claim = claims[_claimId]; 396 | 397 | // Set the claim's ruled flag to true, and record the ruling. 398 | claim.ruled = true; 399 | claim.ruling = _ruling; 400 | 401 | if (_ruling == 0){ 402 | // The claim is justified. Transfer to the arbiter their fee. 403 | require(token.transfer(arbiter, (claim.fee + claim.surplusFee))); 404 | require(token.transfer(claim.claimant, (claim.amount + claim.fee))); 405 | } else if (_ruling == 1){ 406 | // The claim is not justified. Free up the claim amount and fee for future claims, and 407 | // transfer to the arbiter their fee. 408 | claimableStake += (claim.amount + claim.fee); 409 | require(token.transfer(arbiter, (claim.fee + claim.surplusFee))); 410 | } else if (_ruling == 2){ 411 | // The claim is collusive. Transfer to the arbiter both the staker and claimant fees, and 412 | // burn the claim amount. 413 | require(token.transfer(arbiter, (claim.fee + claim.fee + claim.surplusFee))); 414 | require(token.transfer(address(0), claim.amount)); 415 | // burns the claim amount in the event of collusion 416 | } else if (_ruling == 3){ 417 | // The claim cannot be ruled. Free up the claim amount and fee. 418 | claimableStake += (claim.amount + claim.fee); 419 | require(token.transfer(claim.claimant, (claim.amount + claim.fee))); 420 | // TODO: send fsurplus to arbiters 421 | } else { 422 | revert(); 423 | } 424 | 425 | // The claim is ruled. Decrement the total number of open claims. 426 | openClaims--; 427 | 428 | // Emit an event stating which claim was ruled. 429 | ClaimRuled(_claimId); 430 | } 431 | 432 | /* 433 | @dev Increases the stake in this DelphiStake 434 | @param _value the number of tokens to transfer into this stake 435 | */ 436 | function increaseStake(uint _value) 437 | public 438 | onlyStaker 439 | { 440 | // Transfer _value tokens from the message sender into this contract, and increment the 441 | // claimableStake by _value. 442 | require(token.transferFrom(msg.sender, this, _value)); 443 | claimableStake += _value; 444 | StakeIncreased(msg.sender, _value); 445 | } 446 | 447 | /* 448 | @dev Increases the deadline for opening claims 449 | @param _newClaimDeadline the unix time stamp (in seconds) before which claims may be opened 450 | */ 451 | function extendStakeReleaseTime(uint _stakeReleaseTime) 452 | public 453 | onlyStaker 454 | { 455 | require(_stakeReleaseTime > stakeReleaseTime); 456 | stakeReleaseTime = _stakeReleaseTime; 457 | ReleaseTimeIncreased(_stakeReleaseTime); 458 | } 459 | 460 | /* 461 | @dev Returns the stake to the staker, if the claim deadline has elapsed and no open claims remain 462 | @param _newClaimDeadline the unix time stamp (in seconds) before which claims may be opened 463 | */ 464 | function withdrawStake() 465 | public 466 | onlyStaker 467 | stakeIsReleased 468 | noOpenClaims 469 | { 470 | uint oldStake = claimableStake; 471 | claimableStake = 0; 472 | require(token.transfer(staker, oldStake)); 473 | StakeWithdrawn(); 474 | } 475 | 476 | /* 477 | @dev Getter function to return the total number of claims which have ever been made against 478 | this stake. 479 | */ 480 | function getNumClaims() 481 | public 482 | view 483 | returns (uint) 484 | { 485 | // Return the length of the claims array. Claims are never removed from this array, no matter 486 | // if or how they are resolved. 487 | return claims.length; 488 | } 489 | 490 | /* 491 | @dev Getter function to return the total available fee for any historical claim 492 | */ 493 | function getTotalFeeForClaim(uint _claimId) 494 | public 495 | view 496 | returns (uint) 497 | { 498 | Claim storage claim = claims[_claimId]; 499 | 500 | // The total available fee is the claim fee, plus any surplus fee provided by either party 501 | return claim.fee + claim.surplusFee; 502 | } 503 | 504 | } 505 | -------------------------------------------------------------------------------- /contracts/DelphiVoting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "tcr/Registry.sol"; 4 | import "tcr/Parameterizer.sol"; 5 | import "./DelphiStake.sol"; 6 | import "dll/DLL.sol"; 7 | import "./LookupTable.sol"; 8 | 9 | contract DelphiVoting { 10 | 11 | event VoteCommitted(address voter, bytes32 claimId, bytes32 secret); 12 | event VoteRevealed(address voter, bytes32 claimId, uint faction); 13 | 14 | enum VoteOptions { Justified, NotJustified, Collusive, Fault } 15 | 16 | using AttributeStore for AttributeStore.Data; 17 | using DLL for DLL.Data; 18 | 19 | struct Commit { 20 | bytes32 commit; 21 | uint timestamp; 22 | } 23 | 24 | struct Claim { 25 | uint commitEndTime; 26 | uint revealEndTime; 27 | VoteOptions result; 28 | mapping(uint => DLL.Data) factions; 29 | mapping(uint => uint) tallies; 30 | mapping(address => Commit) commits; 31 | mapping(address => bool) hasRevealed; 32 | mapping(address => bool) claimedReward; 33 | mapping(address => uint) ranks; 34 | } 35 | 36 | Registry public arbiterSet; 37 | Parameterizer public parameterizer; 38 | LookupTable public lt; 39 | 40 | mapping(bytes32 => Claim) public claims; 41 | 42 | modifier onlyArbiters(address _arbiter) { 43 | require(arbiterSet.isWhitelisted(keccak256(_arbiter))); 44 | _; 45 | } 46 | 47 | function init(address _arbiterSet, address _parameterizer, uint _feeDecayValue) public { 48 | require(_arbiterSet != 0 && arbiterSet == address(0)); 49 | require(_parameterizer != 0 && parameterizer == address(0)); 50 | 51 | arbiterSet = Registry(_arbiterSet); 52 | parameterizer = Parameterizer(_parameterizer); 53 | lt = new LookupTable(_feeDecayValue); 54 | } 55 | 56 | /* 57 | @dev Commits a vote for the specified claim. Can be overwritten while commitPeriod is active 58 | @param _stake the address of a DelphiStake contract 59 | @param _claimNumber an initialized claim in the provided DelphiStake 60 | @param _secretHash keccak256 of a vote and a salt 61 | */ 62 | function commitVote(address _stake, uint _claimNumber, bytes32 _secretHash) 63 | public onlyArbiters(msg.sender) { 64 | bytes32 claimId = keccak256(_stake, _claimNumber); 65 | DelphiStake ds = DelphiStake(_stake); 66 | 67 | // Do not allow secretHash to be zero 68 | require(_secretHash != 0); 69 | // Check if the claim has been instantiated in the DelphiStake. 70 | // Do not allow voting on claims which are uninitialized in the DS. 71 | require(_claimNumber < ds.getNumClaims()); 72 | 73 | // Check if anybody has ever committed a vote for this claim before. If not, initialize a new 74 | // claim by setting commit and reveal end times for this claim in the claims mapping 75 | if(!claimExists(claimId)) { 76 | initializeClaim(claimId); 77 | } 78 | 79 | // Do not allow votes to be committed after the commit period has ended 80 | require(commitPeriodActive(claimId)); 81 | 82 | // Set this voter's commit for this claim to their provided secretHash. 83 | claims[claimId].commits[msg.sender] = Commit({commit: _secretHash, timestamp: block.number}); 84 | 85 | // Fire an event saying the message sender voted for this claimID. 86 | // TODO: Make this event fire the stake and claim number instead of the claimID. 87 | emit VoteCommitted(msg.sender, claimId, _secretHash); 88 | } 89 | 90 | /* 91 | @dev Reveals a vote for the specified claim. 92 | @param _claimId the keccak256 of a DelphiStake address and a claim number for which the message 93 | sender has previously committed a vote 94 | @param _vote the option voted for in the original secret hash. 95 | @param _salt the salt concatenated to the vote option when originally hashed to its secret form 96 | @param _previousCommitter the node in the faction's DLL for this claim which should come before 97 | the one we will insert here. Can be computed using getInsertPoint. 98 | */ 99 | function revealVote(bytes32 _claimId, uint _vote, uint _salt, address _previousCommitter) 100 | public onlyArbiters(msg.sender) { 101 | Claim storage claim = claims[_claimId]; 102 | 103 | // Do not allow revealing while the reveal period is not active 104 | require(revealPeriodActive(_claimId)); 105 | // Do not allow a voter to reveal more than once 106 | require(!claim.hasRevealed[msg.sender]); 107 | // Require the provided vote is consistent with the original commit 108 | require(keccak256(_vote, _salt) == claims[_claimId].commits[msg.sender].commit); 109 | 110 | // We need the nodes on either side of the node we are proposing to insert, so grab the 111 | // next node of the provided previous node. Once we have these, check if the insertion point 112 | // is valid with the validPosition function. 113 | address nextCommitter = 114 | address(claim.factions[_vote].getNext(uint(_previousCommitter))); 115 | require(validPosition(_previousCommitter, nextCommitter, _claimId, _vote)); 116 | 117 | // Insert the voter into their faction's list, and increment the tally for that vote option 118 | claim.factions[_vote].insert(uint(_previousCommitter), 119 | uint(msg.sender), 120 | uint(nextCommitter)); 121 | claim.tallies[_vote]++; 122 | 123 | // Set hasRevealed to true so this voter cannot reveal again 124 | claim.hasRevealed[msg.sender] = true; 125 | 126 | 127 | emit VoteRevealed(msg.sender, _claimId, _vote); 128 | } 129 | 130 | /* 131 | @dev prevents a user from inserting themselves ahead of other arbiters improperly by checking 132 | when they committed their faction vote, and then making sure the arbiter they propose to come 133 | after committed earlier, and the arbiter they propose to come before committed later. 134 | @param _previousCommitter an arbiter in the same faction who committed before the msg.sender 135 | @param _nextCommitter an arbiter in the same faction who committed after the msg.sender 136 | @param _claimId the claim whose factions are being inspected. 137 | @param _faction the faction in this claim where we make the insertion 138 | @return bool asserting whether the proposed insert point is valid or not 139 | */ 140 | function validPosition(address _previousCommitter, address _nextCommitter, bytes32 _claimId, 141 | uint _faction) 142 | public view returns (bool) { 143 | Claim storage claim = claims[_claimId]; 144 | 145 | // Assert the provided arbiters are all in the same faction (or that we are inserting into the 146 | // beginning, end of, or into an empty, list. 147 | require((claim.factions[_faction].contains(uint(_previousCommitter)) || 148 | uint(_previousCommitter) == 0) && 149 | (claim.factions[_faction].contains(uint(_nextCommitter)) || 150 | uint(_nextCommitter) == 0)); 151 | 152 | // Assert that the proposed insertion point is between two adjacent nodes 153 | require(claim.factions[_faction].getNext(uint(_previousCommitter)) == uint(_nextCommitter)); 154 | 155 | // Get timestamps for when all of the involved arbiters made their commits 156 | uint timestamp = claim.commits[msg.sender].timestamp; 157 | uint prevTimestamp = claim.commits[_previousCommitter].timestamp; 158 | uint nextTimestamp = claim.commits[_nextCommitter].timestamp; 159 | 160 | // If the committer committed later than the specified previous committer and earlier than 161 | // the specified next committer, return true. Else false. 162 | if((prevTimestamp <= timestamp) && ((timestamp <= nextTimestamp) || _nextCommitter == 0)) { 163 | return true; 164 | } else { 165 | return false; 166 | } 167 | } 168 | 169 | /* 170 | @dev computes the _previousCommitter argument required by the revealVote function 171 | @param _claimId the claim a vote is being revealed for 172 | @param _committer a committer in the claim being revealed for 173 | @param _faction the faction of a committer in the claim being revealed for 174 | @return address the committer (or insert point) the provided committer should insert after 175 | */ 176 | function getInsertPoint(bytes32 _claimId, address _committer, uint _faction) 177 | public view returns (address) { 178 | Claim storage claim = claims[_claimId]; 179 | 180 | uint timestamp = claim.commits[_committer].timestamp; 181 | DLL.Data storage faction = claim.factions[_faction]; 182 | 183 | // In this loop, we will iterate over the list until we find an insertion point for our node. 184 | // When the currentNode is zero, we have reached the end of the list (or the list was empty 185 | // to start). 186 | uint currentNode = faction.getStart(); 187 | while(currentNode != 0) { 188 | uint nextNode = faction.getNext(currentNode); 189 | // Check whether the committer's timestamp is >= the current committer's && <= the next 190 | // committer's (or we are inserting at the end of the list) 191 | if((claim.commits[address(currentNode)].timestamp <= timestamp) && 192 | ((timestamp <= claim.commits[address(nextNode)].timestamp) || 193 | nextNode == 0)) { 194 | return address(currentNode); 195 | } 196 | currentNode = nextNode; 197 | } 198 | 199 | // If we reach the end of the list, either the list was empty or our insertion point is at 200 | // the very beginning. 201 | return address(0); 202 | } 203 | 204 | /* 205 | @dev Submits a ruling to a DelphiStake contract 206 | @param _stake address of a DelphiStake contract 207 | @param _claimNumber nonce of a unique claim for the provided stake 208 | */ 209 | function submitRuling(address _stake, uint _claimNumber) public { 210 | bytes32 claimId = keccak256(_stake, _claimNumber); 211 | DelphiStake ds = DelphiStake(_stake); 212 | Claim storage claim = claims[claimId]; // Grabbing a pointer 213 | 214 | // Do not allow submissions for claims which nobody has voted in 215 | require(claimExists(claimId)); 216 | // Do not allow submissions where either the commit or reveal periods have not ended 217 | require(!commitPeriodActive(claimId) && !revealPeriodActive(claimId)); 218 | 219 | // Tally the votes and set the result 220 | tallyVotes(claim); 221 | 222 | // Call the DS contract with the result of the arbitration 223 | ds.ruleOnClaim(_claimNumber, uint256(claim.result)); 224 | } 225 | 226 | /* 227 | @dev allow an arbiter who participated in the plurality voting bloc to claim their share of the 228 | fee 229 | @param _stake address of a DelphiStake contract 230 | @param _claimNumber nonce of a unique claim for the provided stake 231 | @param _vote the option voted for in the original secret hash. 232 | @param _salt the salt concatenated to the vote option when originally hashed to its secret form 233 | */ 234 | function claimFee(address _stake, uint _claimNumber, uint _vote, uint _salt) 235 | public onlyArbiters(msg.sender) { 236 | DelphiStake ds = DelphiStake(_stake); 237 | Claim storage claim = claims[keccak256(_stake, _claimNumber)]; // Grabbing a pointer 238 | 239 | // The ruling needs to have been submitted before an arbiter can claim their reward 240 | require(claimIsRuled(_stake, _claimNumber)); 241 | // Do not allow arbiters to claim rewards for a claim more than once 242 | require(!claim.claimedReward[msg.sender]); 243 | // Check that the arbiter actually committed the vote they say they did 244 | require(keccak256(_vote, _salt) == claim.commits[msg.sender].commit); 245 | // Require the vote cast was in the plurality 246 | require(VoteOptions(_vote) == claim.result); 247 | 248 | // FAQ 249 | // Q: Could the arbiterFee equation be simpler? 250 | // A: It certainly could in theory, but because the Solidity compiler vomits if you have 251 | // more than 16 stack items, I had to remove a bunch of named variables. If I had more named 252 | // variables available I would write it like this: 253 | // 254 | // arbiterGuaranteedPct * totalFee + 255 | // (totalFee * percentageOfFeeNotReservedForGuaranteedPayouts) * 256 | // (winningFactionSize / totalReveals) / 257 | // winningFactionSize 258 | // 259 | // Now an explanation of the above mapped the to the actual equation below. I've identified 260 | // each line in the actual equation with a letter. 261 | // 262 | // On the first line we compute the guaranteed fee. (A + B). 263 | // On the second line we compute the number of tokens not reserved in this fee for 264 | // guaranteed payouts. (C + D) 265 | // On the third line we compute the percentage of all arbiters who revealed, who revealed in 266 | // the winning faction. (E + F) 267 | // We multiply the results of lines 2 and 3 to get the leftover fee we will actually be 268 | // apportioning to the arbiters. 269 | // On the last line, we divide the leftover fee we will actually be apportioning to the 270 | // arbiters by the number of arbiters in the winning faction. (G) 271 | // 272 | // Also, I divide and multiply by 100 in a bunch of places because integer arithmetic. 273 | 274 | uint arbiterFee = 275 | // Calculate the guaranteed fee 276 | lt.getGuaranteedPercentageForIndex(computeArbiterRank(claim, _vote, msg.sender)) * // A 277 | (ds.getTotalFeeForClaim(_claimNumber) / 100) + // B 278 | // Now compute the owed leftover fee and add it to the guaranteed fee 279 | ((ds.getTotalFeeForClaim(_claimNumber) / 100) * // C 280 | (100 - lt.lt(claim.tallies[_vote] - 1)) / 100) * // D 281 | ((claim.tallies[_vote] * 100) / // E 282 | (claim.tallies[0] + claim.tallies[1] + claim.tallies[2] + claim.tallies[3])) / // F 283 | claim.tallies[_vote]; // G 284 | 285 | // Transfer the arbiter their owed fee 286 | require(ds.token().transfer(msg.sender, arbiterFee)); 287 | 288 | // Set claimedReward to true so the arbiter cannot claim again 289 | claim.claimedReward[msg.sender] = true; 290 | } 291 | 292 | /* 293 | @dev Checks if the commit period is still active for the specified claim 294 | @param _claimId Integer identifier associated with target claim 295 | @return bool indicating whetherh the commit period is active for this claim 296 | */ 297 | function commitPeriodActive(bytes32 _claimId) view public returns (bool) { 298 | require(claimExists(_claimId)); 299 | 300 | return (block.timestamp < claims[_claimId].commitEndTime); 301 | } 302 | 303 | /* 304 | @dev Checks if the reveal period is still active for the specified claim 305 | @param _claimId the keccak256 of a DelphiStake address and a claim number 306 | @return bool indicating whetherh the reveal period is active for this claim 307 | */ 308 | function revealPeriodActive(bytes32 _claimId) view public returns (bool) { 309 | require(claimExists(_claimId)); 310 | 311 | return 312 | ((!commitPeriodActive(_claimId)) && (block.timestamp < claims[_claimId].revealEndTime)); 313 | } 314 | 315 | /* 316 | @dev Checks if a claim exists, throws if the provided claim is in an impossible state 317 | @param _claimId the keccak256 of a DelphiStake address and a claim number 318 | @return Boolean Indicates whether a claim exists for the provided claimId 319 | */ 320 | function claimExists(bytes32 _claimId) view public returns (bool) { 321 | uint commitEndTime = claims[_claimId].commitEndTime; 322 | uint revealEndTime = claims[_claimId].revealEndTime; 323 | 324 | // It should not be possible that one of these is zero while the other is not. 325 | assert(!(commitEndTime == 0 && revealEndTime != 0)); 326 | assert(!(commitEndTime != 0 && revealEndTime == 0)); 327 | 328 | // If either is zero, this claim does not exist. 329 | if(commitEndTime == 0 || revealEndTime == 0) { return false; } 330 | return true; 331 | } 332 | 333 | /* 334 | @dev returns the commit hash of the provided arbiter for some claim 335 | @param _claimId the keccak256 of a DelphiStake address and a claim number 336 | @return bytes32 the arbiter's commit hash for this claim 337 | */ 338 | function getArbiterCommitForClaim(bytes32 _claimId, address _arbiter) 339 | view public returns (bytes32) { 340 | return claims[_claimId].commits[_arbiter].commit; 341 | } 342 | 343 | /* 344 | @dev Returns the number of revealed votes for the provided vote option in a given claim 345 | @param _claimId the keccak256 of a DelphiStake address and a claim number 346 | @param _option The vote option to return a total for 347 | @return uint Tally of revealed votes for the provided option in the given claimId 348 | */ 349 | function revealedVotesForOption(bytes32 _claimId, uint _option) public view returns (uint) { 350 | return claims[_claimId].tallies[_option]; 351 | } 352 | 353 | /* 354 | @dev utility function for determining whether a claim has been ruled. Used by claimFee to 355 | determine whether fees should be disbured. 356 | @param _stake the DelphiStake whose storage is to be inspected. 357 | @param _claimNumber the unique claim number we are determining if a ruling has been submitted 358 | for 359 | @return bool True if a ruling has been submitted for the claim, false otherwise 360 | */ 361 | function claimIsRuled(address _stake, uint _claimNumber) public view returns (bool) { 362 | DelphiStake ds = DelphiStake(_stake); 363 | bool ruled; 364 | bool settlementFailed; 365 | 366 | // Tuple destructuring. settlementFailed is a throwaway value, but is needed by the compiler. 367 | (, ruled, settlementFailed) = ds.claims(_claimNumber); 368 | 369 | return ruled; 370 | } 371 | 372 | /* 373 | @dev Initialize a claim struct by setting its commit and reveal end times 374 | @param _claimId the keccak256 of a DelphiStake address and a claim number 375 | */ 376 | function initializeClaim(bytes32 _claimId) private { 377 | claims[_claimId].commitEndTime = now + parameterizer.get('commitStageLen'); 378 | claims[_claimId].revealEndTime = 379 | claims[_claimId].commitEndTime + parameterizer.get('revealStageLen'); 380 | } 381 | 382 | function computeArbiterRank(Claim storage _claim, uint _faction, address _arbiter) 383 | private returns (uint) { 384 | // A rank value for this arbiter has already been computed 385 | if(_claim.ranks[_arbiter] != 0) { 386 | return _claim.ranks[_arbiter]; 387 | } 388 | 389 | // Get the previous arbiter 390 | address previousArbiter = address(_claim.factions[_faction].getPrev(uint(_arbiter))); 391 | 392 | // previousArbiter is the null node. The _arbiter was the first to commit. Its rank is 0. 393 | if(previousArbiter == address(0)) { 394 | return 0; 395 | } 396 | 397 | // The previous arbiter's previous arbiter is the null node, meaning the previous arbiter 398 | // was the first to commit 399 | if(address(_claim.factions[_faction].getPrev(uint(previousArbiter))) == address(0)) { 400 | _claim.ranks[_arbiter] = 1; 401 | return _claim.ranks[_arbiter]; 402 | } 403 | 404 | // Compute or get the rank of the previous arbiter, depending on if it has already been 405 | // computed 406 | uint previousArbiterRank = computeArbiterRank(_claim, _faction, previousArbiter); 407 | 408 | // A rank value has been computed for the previous arbiter. Use it to compute a rank for the 409 | // current arbiter. 410 | if(previousArbiterRank != 0) { 411 | _claim.ranks[_arbiter] = previousArbiterRank + 1; 412 | return _claim.ranks[_arbiter]; 413 | } 414 | } 415 | 416 | /* 417 | @dev Updates the winning option in the claim to that with the greatest number of votes 418 | @param _claim storage pointer to a Claim struct 419 | */ 420 | function tallyVotes(Claim storage _claim) private { 421 | uint greatest = _claim.tallies[uint(VoteOptions.Justified)]; 422 | _claim.result = VoteOptions.Justified; 423 | 424 | // get greatest and set result 425 | if(greatest < _claim.tallies[uint(VoteOptions.NotJustified)]) { 426 | greatest = _claim.tallies[uint(VoteOptions.NotJustified)]; 427 | _claim.result = VoteOptions.NotJustified; 428 | } 429 | if(greatest < _claim.tallies[uint(VoteOptions.Collusive)]) { 430 | greatest = _claim.tallies[uint(VoteOptions.Collusive)]; 431 | _claim.result = VoteOptions.Collusive; 432 | } 433 | if(greatest < _claim.tallies[uint(VoteOptions.Fault)]) { 434 | greatest = _claim.tallies[uint(VoteOptions.Fault)]; 435 | _claim.result = VoteOptions.Fault; 436 | } 437 | 438 | // see if greatest is tied with anything else and set fault if so 439 | if(_claim.result == VoteOptions.Justified) { 440 | if(greatest == _claim.tallies[uint(VoteOptions.NotJustified)] || 441 | greatest == _claim.tallies[uint(VoteOptions.Collusive)] || 442 | greatest == _claim.tallies[uint(VoteOptions.Fault)]) { 443 | _claim.result = VoteOptions.Fault; 444 | } 445 | } 446 | if(_claim.result == VoteOptions.NotJustified) { 447 | if(greatest == _claim.tallies[uint(VoteOptions.Justified)] || 448 | greatest == _claim.tallies[uint(VoteOptions.Collusive)] || 449 | greatest == _claim.tallies[uint(VoteOptions.Fault)]) { 450 | _claim.result = VoteOptions.Fault; 451 | } 452 | } 453 | if(_claim.result == VoteOptions.Collusive) { 454 | if(greatest == _claim.tallies[uint(VoteOptions.Justified)] || 455 | greatest == _claim.tallies[uint(VoteOptions.NotJustified)] || 456 | greatest == _claim.tallies[uint(VoteOptions.Fault)]) { 457 | _claim.result = VoteOptions.Fault; 458 | } 459 | } 460 | // if(_claim.result = VoteOptions.Fault), the result is already fault, so don't bother checking 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /test/js/DelphiVoting/claimFee.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract artifacts assert */ 3 | 4 | const DelphiVoting = artifacts.require('DelphiVoting'); 5 | const DelphiStake = artifacts.require('DelphiStake'); 6 | const DelphiStakeFactory = artifacts.require('DelphiStakeFactory'); 7 | const DelphiVotingFactory = artifacts.require('DelphiVotingFactory'); 8 | const RegistryFactory = artifacts.require('tcr/RegistryFactory.sol'); 9 | const Registry = artifacts.require('tcr/Registry.sol'); 10 | const EIP20 = artifacts.require('tokens/eip20/EIP20.sol'); 11 | const LookupTable = artifacts.require('LookupTable'); 12 | 13 | const utils = require('../utils.js'); 14 | const BN = require('bignumber.js'); 15 | 16 | const HttpProvider = require('ethjs-provider-http'); 17 | const EthRPC = require('ethjs-rpc'); 18 | const Web3 = require('web3'); 19 | 20 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545')); 21 | const rpc = new EthRPC(new HttpProvider('http://localhost:7545')); 22 | 23 | const solkeccak = Web3.utils.soliditySha3; 24 | 25 | contract('DelphiVoting', (accounts) => { 26 | describe('Function: claimFee', () => { 27 | const [staker, claimant, arbiterAlice, arbiterBob, arbiterCharlie, arbiterDanielle, 28 | arbiterEdwin, arbiterFederika, arbiterGale, arbiterHenry] = accounts; 29 | 30 | let delphiStake; 31 | let delphiVoting; 32 | let token; 33 | 34 | beforeEach(async () => { 35 | // Get deployed factory contracts 36 | const delphiVotingFactory = await DelphiVotingFactory.deployed(); 37 | const delphiStakeFactory = await DelphiStakeFactory.deployed(); 38 | const registryFactory = await RegistryFactory.deployed(); 39 | 40 | // Create a new registry and curation token 41 | const registryReceipt = await registryFactory.newRegistryWithToken( 42 | 1000000, 43 | 'RegistryCoin', 44 | 0, 45 | 'REG', 46 | [100, 100, 100, 100, 100, 100, 100, 100, 60, 60, 50, 50], 47 | 'The Arbiter Registry', 48 | ); 49 | 50 | // Get instances of the registry and its token 51 | const registryToken = EIP20.at(registryReceipt.logs[0].args.token); 52 | const registry = Registry.at(registryReceipt.logs[0].args.registry); 53 | 54 | // Give 100k REG to each account, and approve the Registry to transfer it 55 | await Promise.all(accounts.map(async (account) => { 56 | await registryToken.transfer(account, 100000); 57 | await registryToken.approve(registry.address, 100, { from: account }); 58 | })); 59 | 60 | // Apply Alice, Bob, and Charlie to the registry 61 | await registry.apply(solkeccak(arbiterAlice), 100, '', { from: arbiterAlice }); 62 | await registry.apply(solkeccak(arbiterBob), 100, '', { from: arbiterBob }); 63 | await registry.apply(solkeccak(arbiterCharlie), 100, '', { from: arbiterCharlie }); 64 | await registry.apply(solkeccak(arbiterDanielle), 100, '', { from: arbiterDanielle }); 65 | await registry.apply(solkeccak(arbiterEdwin), 100, '', { from: arbiterEdwin }); 66 | await registry.apply(solkeccak(arbiterFederika), 100, '', { from: arbiterFederika }); 67 | await registry.apply(solkeccak(arbiterGale), 100, '', { from: arbiterGale }); 68 | await registry.apply(solkeccak(arbiterHenry), 100, '', { from: arbiterHenry }); 69 | 70 | // Increase time past the registry application period 71 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 72 | 73 | // Add arbiters to the Registry 74 | await registry.updateStatus(solkeccak(arbiterAlice)); 75 | await registry.updateStatus(solkeccak(arbiterBob)); 76 | await registry.updateStatus(solkeccak(arbiterCharlie)); 77 | await registry.updateStatus(solkeccak(arbiterDanielle)); 78 | await registry.updateStatus(solkeccak(arbiterEdwin)); 79 | await registry.updateStatus(solkeccak(arbiterFederika)); 80 | await registry.updateStatus(solkeccak(arbiterGale)); 81 | await registry.updateStatus(solkeccak(arbiterHenry)); 82 | 83 | // Create a DelphiVoting with 100 second voting periods, fee decay value of five, 84 | // and which uses the registry we just created as its arbiter set 85 | const delphiVotingReceipt = await delphiVotingFactory.makeDelphiVoting(registry.address, 86 | 5, [solkeccak('parameterizerVotingPeriod'), solkeccak('commitStageLen'), 87 | solkeccak('revealStageLen')], 88 | [100, 100, 100]); 89 | delphiVoting = DelphiVoting.at(delphiVotingReceipt.logs[0].args.delphiVoting); 90 | 91 | // Pre-compute LookupTable values 92 | const lookupTable = LookupTable.at(await delphiVoting.lt.call()); 93 | await lookupTable.getGuaranteedPercentageForIndex(10); 94 | 95 | // Create DisputeCoin and give 100k DIS to each account 96 | token = await EIP20.new(1000000, 'DisputeCoin', 0, 'DIS'); 97 | await Promise.all(accounts.map(async account => token.transfer(account, 100000))); 98 | 99 | // Create a DelphiStake with 90k DIS tokens, 1k minFee, and a release time 1k seconds 100 | // from now 101 | await token.approve(delphiStakeFactory.address, 90000, { from: staker }); 102 | const expirationTime = (await web3.eth.getBlock('latest')).timestamp + 1000; 103 | const delphiStakeReceipt = await delphiStakeFactory.createDelphiStake(90000, token.address, 104 | 1000, '', expirationTime, delphiVoting.address, { from: staker }); 105 | // eslint-disable-next-line 106 | delphiStake = DelphiStake.at(delphiStakeReceipt.logs[0].args._contractAddress); 107 | }); 108 | 109 | it('should allow an arbiter to claim a fee', async () => { 110 | // Set constants 111 | const CLAIM_AMOUNT = '10000'; 112 | const FEE_AMOUNT = '1000'; 113 | const VOTE = '1'; 114 | const SALT = '420'; 115 | 116 | // Make a new claim and get its claimId 117 | const claimNumber = 118 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 119 | delphiStake); 120 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 121 | 122 | // Get the secret hash for the salted vote 123 | const secretHash = utils.getSecretHash(VOTE, SALT); 124 | 125 | // Commit vote 126 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 127 | { from: arbiterAlice }); 128 | 129 | // Increase time to get to the reveal phase 130 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 131 | 132 | // Reveal vote 133 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 134 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 135 | 136 | // Increase time to finish the reveal phase so we can submit 137 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 138 | 139 | // Submit ruling 140 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 141 | 142 | // Claim fee. Capture the arbiters balance before and after. 143 | const startingBalance = await token.balanceOf(arbiterAlice); 144 | await delphiVoting.claimFee(delphiStake.address, claimNumber, VOTE, SALT, 145 | { from: arbiterAlice }); 146 | const finalBalance = await token.balanceOf(arbiterAlice); 147 | 148 | // The arbiter's final balance should be their starting balance plus the entire FEE_AMOUNT, 149 | // since they were the only voter and should get the whole amount 150 | assert.strictEqual(finalBalance.toString(10), 151 | startingBalance.add(new BN(FEE_AMOUNT, 10)).toString(10)); 152 | }); 153 | 154 | it('should not allow an arbiter to claim a fee twice', async () => { 155 | // Set constants 156 | const CLAIM_AMOUNT = '10000'; 157 | const FEE_AMOUNT = '1000'; 158 | const VOTE = '1'; 159 | const SALT = '420'; 160 | 161 | // Make a new claim and get its claimId 162 | const claimNumber = 163 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 164 | delphiStake); 165 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 166 | 167 | // Get the secret hash for the salted vote 168 | const secretHash = utils.getSecretHash(VOTE, SALT); 169 | 170 | // Commit vote 171 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 172 | { from: arbiterAlice }); 173 | 174 | // Increase time to get to the reveal phase 175 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 176 | 177 | // Reveal vote 178 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 179 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 180 | 181 | // Increase time to finish the reveal phase so we can submit 182 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 183 | 184 | // Submit ruling 185 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 186 | 187 | // Claim fee. Capture the arbiters balance before and after. 188 | await delphiVoting.claimFee(delphiStake.address, claimNumber, VOTE, SALT, 189 | { from: arbiterAlice }); 190 | 191 | // Add tokens to the delphiVoting contract so it doesn't fail on an insufficient balance 192 | // when we try to claim again 193 | await token.transfer(delphiVoting.address, FEE_AMOUNT); 194 | 195 | try { 196 | await delphiVoting.claimFee(delphiStake.address, claimNumber, VOTE, SALT, 197 | { from: arbiterAlice }); 198 | } catch (err) { 199 | assert(utils.isEVMRevert(err), err.toString()); 200 | 201 | return; 202 | } 203 | 204 | assert(false, 'an arbiter was able to claim a fee twice'); 205 | }); 206 | 207 | it('should not allow an arbiter to claim a fee when they voted out of the plurality', 208 | async () => { 209 | // Set constants 210 | const CLAIM_AMOUNT = '10000'; 211 | const FEE_AMOUNT = '1000'; 212 | const PLURALITY_VOTE = '1'; 213 | const NON_PLURALITY_VOTE = '0'; 214 | const SALT = '420'; 215 | 216 | // Compute secret hashes for the plurality and non-plurality vote options 217 | const pluralitySecretHash = utils.getSecretHash(PLURALITY_VOTE, SALT); 218 | const nonPluralitySecretHash = utils.getSecretHash(NON_PLURALITY_VOTE, SALT); 219 | 220 | // Make a new claim and compute its claim ID. 221 | const claimNumber = 222 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 223 | delphiStake); 224 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 225 | 226 | // Arbiters commit votes. Charlie commits the non-plurality vote. 227 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 228 | { from: arbiterAlice }); 229 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 230 | { from: arbiterBob }); 231 | await delphiVoting.commitVote(delphiStake.address, claimNumber, nonPluralitySecretHash, 232 | { from: arbiterCharlie }); 233 | 234 | // Increase time to get to the reveal phase 235 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 236 | 237 | // Arbiters reveal votes 238 | const insertPointAlice = 239 | await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, PLURALITY_VOTE); 240 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointAlice, 241 | { from: arbiterAlice }); 242 | const insertPointBob = 243 | await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, PLURALITY_VOTE); 244 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointBob, 245 | { from: arbiterBob }); 246 | const insertPointCharlie = 247 | await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, NON_PLURALITY_VOTE); 248 | await delphiVoting.revealVote(claimId, NON_PLURALITY_VOTE, SALT, insertPointCharlie, 249 | { from: arbiterCharlie }); 250 | 251 | // Increase time to finish the reveal phase so we can submit the ruling 252 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 253 | 254 | // Submit ruling 255 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 256 | 257 | // Capture Charlie's starting balance 258 | const startingBalance = await token.balanceOf(arbiterCharlie); 259 | try { 260 | // non-plurality arbiter, Charlie, attempts claim fee 261 | await delphiVoting.claimFee(delphiStake.address, claimNumber, NON_PLURALITY_VOTE, 262 | SALT, { from: arbiterCharlie }); 263 | } catch (err) { 264 | assert(utils.isEVMRevert(err), err.toString()); 265 | 266 | // Charlie's final balance should be equal to his starting balance 267 | const finalBalance = await token.balanceOf(arbiterCharlie); 268 | assert.strictEqual(finalBalance.toString(10), startingBalance.toString(10), 269 | 'An unnacountable state change occurred'); 270 | 271 | return; 272 | } 273 | 274 | assert(false, 'An arbiter who voted out of the plurality was able to claim fees'); 275 | }); 276 | 277 | it('should apportion the fee properly when multiple arbiters must claim', async () => { 278 | // Set constants 279 | const CLAIM_AMOUNT = '50000'; 280 | const FEE_AMOUNT = new BN('10000', 10); 281 | const PLURALITY_VOTE = '1'; 282 | const SALT = '420'; 283 | 284 | // Compute secret hashes for the plurality and non-plurality vote options 285 | const pluralitySecretHash = utils.getSecretHash(PLURALITY_VOTE, SALT); 286 | 287 | // Make a new claim and compute its claim ID. 288 | const claimNumber = 289 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 290 | delphiStake); 291 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 292 | 293 | // Arbiters commit votes. 294 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 295 | { from: arbiterAlice }); 296 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 297 | { from: arbiterBob }); 298 | 299 | // Increase time to get to the reveal phase 300 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 301 | 302 | // Arbiters reveal votes 303 | const insertPointAlice = 304 | await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, PLURALITY_VOTE); 305 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointAlice, 306 | { from: arbiterAlice }); 307 | const insertPointBob = 308 | await delphiVoting.getInsertPoint.call(claimId, arbiterBob, PLURALITY_VOTE); 309 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointBob, 310 | { from: arbiterBob }); 311 | 312 | // Increase time to finish the reveal phase so we can submit the ruling 313 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 314 | 315 | // Submit ruling 316 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 317 | 318 | // Claim the fee and get Alice's final balance 319 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 320 | { from: arbiterAlice }); 321 | const finalBalanceAlice = await token.balanceOf(arbiterAlice); 322 | 323 | // Alice's expected final balance is her starting balance plus (20 + 32)% of the fee 324 | const expectedFinalBalanceAlice = '105200'; 325 | assert.strictEqual(finalBalanceAlice.toString(10), expectedFinalBalanceAlice.toString(10), 326 | 'Alice did not get the proper fee allocation'); 327 | 328 | // Claim the fee and get Bob's final balance 329 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 330 | { from: arbiterBob }); 331 | const finalBalanceBob = await token.balanceOf(arbiterBob); 332 | 333 | // Bob's expected final balance is his starting balance plus (16 + 32)% of the fee 334 | const expectedFinalBalanceBob = '104800'; 335 | assert.strictEqual(finalBalanceBob.toString(10), expectedFinalBalanceBob.toString(10), 336 | 'Bob did not get the proper fee allocation'); 337 | }); 338 | 339 | it('should apportion the fee properly when a large number of arbiters claim in random ' + 340 | 'orders', async () => { 341 | // Set constants 342 | const CLAIM_AMOUNT = '50000'; 343 | const FEE_AMOUNT = new BN('10000', 10); 344 | const PLURALITY_VOTE = '1'; 345 | const SALT = '420'; 346 | 347 | // Compute secret hashes for the plurality and non-plurality vote options 348 | const pluralitySecretHash = utils.getSecretHash(PLURALITY_VOTE, SALT); 349 | 350 | // Make a new claim and compute its claim ID. 351 | const claimNumber = 352 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 353 | delphiStake); 354 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 355 | 356 | // Arbiters commit votes. 357 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 358 | { from: arbiterAlice }); 359 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 360 | { from: arbiterBob }); 361 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 362 | { from: arbiterCharlie }); 363 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 364 | { from: arbiterDanielle }); 365 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 366 | { from: arbiterEdwin }); 367 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 368 | { from: arbiterFederika }); 369 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 370 | { from: arbiterGale }); 371 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 372 | { from: arbiterHenry }); 373 | 374 | // Increase time to get to the reveal phase 375 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 376 | 377 | // Arbiters reveal votes 378 | const insertPointAlice = 379 | await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, PLURALITY_VOTE); 380 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointAlice, 381 | { from: arbiterAlice }); 382 | const insertPointBob = 383 | await delphiVoting.getInsertPoint.call(claimId, arbiterBob, PLURALITY_VOTE); 384 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointBob, 385 | { from: arbiterBob }); 386 | const insertPointCharlie = 387 | await delphiVoting.getInsertPoint.call(claimId, arbiterCharlie, PLURALITY_VOTE); 388 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointCharlie, 389 | { from: arbiterCharlie }); 390 | const insertPointDanielle = 391 | await delphiVoting.getInsertPoint.call(claimId, arbiterDanielle, PLURALITY_VOTE); 392 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointDanielle, 393 | { from: arbiterDanielle }); 394 | const insertPointEdwin = 395 | await delphiVoting.getInsertPoint.call(claimId, arbiterEdwin, PLURALITY_VOTE); 396 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointEdwin, 397 | { from: arbiterEdwin }); 398 | const insertPointFederika = 399 | await delphiVoting.getInsertPoint.call(claimId, arbiterFederika, PLURALITY_VOTE); 400 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointFederika, 401 | { from: arbiterFederika }); 402 | const insertPointGale = 403 | await delphiVoting.getInsertPoint.call(claimId, arbiterGale, PLURALITY_VOTE); 404 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointGale, 405 | { from: arbiterGale }); 406 | const insertPointHenry = 407 | await delphiVoting.getInsertPoint.call(claimId, arbiterHenry, PLURALITY_VOTE); 408 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointHenry, 409 | { from: arbiterHenry }); 410 | 411 | // Increase time to finish the reveal phase so we can submit the ruling 412 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 413 | 414 | // Submit ruling 415 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 416 | 417 | // Claim fees 418 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 419 | { from: arbiterFederika }); 420 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 421 | { from: arbiterAlice }); 422 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 423 | { from: arbiterBob }); 424 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 425 | { from: arbiterCharlie }); 426 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 427 | { from: arbiterDanielle }); 428 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 429 | { from: arbiterEdwin }); 430 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 431 | { from: arbiterGale }); 432 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 433 | { from: arbiterHenry }); 434 | 435 | // Alice's expected final balance is her starting balance plus (20 + 2.37)% of the fee 436 | const finalBalanceAlice = await token.balanceOf(arbiterAlice); 437 | const expectedFinalBalanceAlice = '102237'; 438 | assert.strictEqual(finalBalanceAlice.toString(10), expectedFinalBalanceAlice.toString(10), 439 | 'Alice did not get the proper fee allocation'); 440 | 441 | // Bob's expected final balance is his starting balance plus (16 + 2.37)% of the fee 442 | const finalBalanceBob = await token.balanceOf(arbiterBob); 443 | const expectedFinalBalanceBob = '101837'; 444 | assert.strictEqual(finalBalanceBob.toString(10), expectedFinalBalanceBob.toString(10), 445 | 'Bob did not get the proper fee allocation'); 446 | 447 | // Charlie's expected final balance is his starting balance plus (12 + 2.37)% of the fee 448 | const finalBalanceCharlie = await token.balanceOf(arbiterCharlie); 449 | const expectedFinalBalanceCharlie = '101437'; 450 | assert.strictEqual(finalBalanceCharlie.toString(10), 451 | expectedFinalBalanceCharlie.toString(10), 452 | 'Charlie did not get the proper fee allocation'); 453 | 454 | // Danielle's expected final balance is her starting balance plus (10 + 2.37)% of the fee 455 | const finalBalanceDanielle = await token.balanceOf(arbiterDanielle); 456 | const expectedFinalBalanceDanielle = '101237'; 457 | assert.strictEqual(finalBalanceDanielle.toString(10), 458 | expectedFinalBalanceDanielle.toString(10), 459 | 'Danielle did not get the proper fee allocation'); 460 | 461 | // Edwin's expected final balance is his starting balance plus (8 + 2.37)% of the fee 462 | const finalBalanceEdwin = await token.balanceOf(arbiterEdwin); 463 | const expectedFinalBalanceEdwin = '101037'; 464 | assert.strictEqual(finalBalanceEdwin.toString(10), expectedFinalBalanceEdwin.toString(10), 465 | 'Edwin did not get the proper fee allocation'); 466 | 467 | // Federika's expected final balance is her starting balance plus (6 + 2.37)% of the fee 468 | const finalBalanceFederika = await token.balanceOf(arbiterFederika); 469 | const expectedFinalBalanceFederika = '100837'; 470 | assert.strictEqual(finalBalanceFederika.toString(10), 471 | expectedFinalBalanceFederika.toString(10), 472 | 'Federika did not get the proper fee allocation'); 473 | 474 | // Gale's expected final balance is her starting balance plus (5 + 2.37)% of the fee 475 | const finalBalanceGale = await token.balanceOf(arbiterGale); 476 | const expectedFinalBalanceGale = '100737'; 477 | assert.strictEqual(finalBalanceGale.toString(10), expectedFinalBalanceGale.toString(10), 478 | 'Gale did not get the proper fee allocation'); 479 | 480 | // Henry's expected final balance is his starting balance plus (4 + 2.37)% of the fee 481 | const finalBalanceHenry = await token.balanceOf(arbiterHenry); 482 | const expectedFinalBalanceHenry = '100637'; 483 | assert.strictEqual(finalBalanceHenry.toString(10), expectedFinalBalanceHenry.toString(10), 484 | 'Henry did not get the proper fee allocation'); 485 | }); 486 | 487 | it('should revert if called by anyone but one of the arbiters', async () => { 488 | // Set constants 489 | const CLAIM_AMOUNT = '10000'; 490 | const FEE_AMOUNT = '1000'; 491 | const VOTE = '1'; 492 | const SALT = '420'; 493 | 494 | // Make a new claim and get its claimId 495 | const claimNumber = 496 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 497 | delphiStake); 498 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 499 | 500 | // Get the secret hash for the salted vote 501 | const secretHash = utils.getSecretHash(VOTE, SALT); 502 | 503 | // Commit vote 504 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 505 | { from: arbiterAlice }); 506 | 507 | // Increase time to get to the reveal phase 508 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 509 | 510 | // Reveal vote 511 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 512 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 513 | 514 | // Increase time to finish the reveal phase so we can submit 515 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 516 | 517 | // Submit ruling 518 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 519 | 520 | try { 521 | await delphiVoting.claimFee(delphiStake.address, claimNumber, VOTE, SALT, 522 | { from: claimant }); 523 | } catch (err) { 524 | assert(utils.isEVMRevert(err), err.toString()); 525 | 526 | return; 527 | } 528 | 529 | assert(false, 'Expected to revert if called by anyone but one of the arbiters'); 530 | }); 531 | 532 | it('should not allow an arbiter to claim a fee when they did not commit', async () => { 533 | // Set constants 534 | const CLAIM_AMOUNT = '10000'; 535 | const FEE_AMOUNT = '1000'; 536 | const VOTE = '1'; 537 | const SALT = '420'; 538 | 539 | // Make a new claim and get its claimId 540 | const claimNumber = 541 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 542 | delphiStake); 543 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 544 | 545 | // Get the secret hash for the salted vote 546 | const secretHash = utils.getSecretHash(VOTE, SALT); 547 | 548 | // Commit vote 549 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 550 | { from: arbiterAlice }); 551 | 552 | // Increase time to get to the reveal phase 553 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 554 | 555 | // Reveal vote 556 | const insertPoint = await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, VOTE); 557 | await delphiVoting.revealVote(claimId, VOTE, SALT, insertPoint, { from: arbiterAlice }); 558 | 559 | // Increase time to finish the reveal phase so we can submit 560 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 561 | 562 | // Submit ruling 563 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 564 | 565 | try { 566 | await delphiVoting.claimFee(delphiStake.address, claimNumber, VOTE, SALT, 567 | { from: arbiterBob }); 568 | } catch (err) { 569 | assert(utils.isEVMRevert(err), err.toString()); 570 | 571 | return; 572 | } 573 | 574 | assert(false, 'Expetected to not allow an arbiter to claim a fee when they did not commit'); 575 | }); 576 | 577 | it('should not allow an arbiter to claim a fee when they committed but did not reveal', async () => { 578 | // Set constants 579 | const CLAIM_AMOUNT = '10000'; 580 | const FEE_AMOUNT = '1000'; 581 | const VOTE = '1'; 582 | const SALT = '420'; 583 | 584 | // Make a new claim and get its claimId 585 | const claimNumber = 586 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 587 | delphiStake); 588 | 589 | // Get the secret hash for the salted vote 590 | const secretHash = utils.getSecretHash(VOTE, SALT); 591 | 592 | // Commit vote 593 | await delphiVoting.commitVote(delphiStake.address, claimNumber, secretHash, 594 | { from: arbiterAlice }); 595 | 596 | // Increase time to get to the reveal phase 597 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 598 | 599 | // DO NOT reveal vote 600 | 601 | // Increase time to finish the reveal phase so we can submit 602 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 603 | 604 | // Submit ruling 605 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 606 | 607 | try { 608 | await delphiVoting.claimFee(delphiStake.address, claimNumber, VOTE, SALT, 609 | { from: arbiterAlice }); 610 | } catch (err) { 611 | assert(utils.isEVMRevert(err), err.toString()); 612 | 613 | return; 614 | } 615 | 616 | assert(false, 'Expetected to not allow an arbiter to claim a fee when they committed but did not reveal'); 617 | }); 618 | 619 | it('should apportion the fee properly when arbiter consensus is under 100%', async () => { 620 | // Set constants 621 | const CLAIM_AMOUNT = '50000'; 622 | const FEE_AMOUNT = new BN('10000', 10); 623 | const PLURALITY_VOTE = '1'; 624 | const NON_PLURALITY_VOTE = '0'; 625 | const SALT = '420'; 626 | 627 | // Compute secret hashes for the plurality and non-plurality vote options 628 | const pluralitySecretHash = utils.getSecretHash(PLURALITY_VOTE, SALT); 629 | const nonPluralitySecretHash = utils.getSecretHash(NON_PLURALITY_VOTE, SALT); 630 | 631 | // Make a new claim and compute its claim ID. 632 | const claimNumber = 633 | await utils.makeNewClaim(staker, claimant, CLAIM_AMOUNT, FEE_AMOUNT, 'i love cats', 634 | delphiStake); 635 | const claimId = utils.getClaimId(delphiStake.address, claimNumber.toString(10)); 636 | 637 | // Arbiters commit votes. 638 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 639 | { from: arbiterAlice }); 640 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 641 | { from: arbiterBob }); 642 | await delphiVoting.commitVote(delphiStake.address, claimNumber, pluralitySecretHash, 643 | { from: arbiterCharlie }); 644 | await delphiVoting.commitVote(delphiStake.address, claimNumber, nonPluralitySecretHash, 645 | { from: arbiterDanielle }); 646 | 647 | // Increase time to get to the reveal phase 648 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [101] }); 649 | 650 | // Arbiters reveal votes 651 | const insertPointAlice = 652 | await delphiVoting.getInsertPoint.call(claimId, arbiterAlice, PLURALITY_VOTE); 653 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointAlice, 654 | { from: arbiterAlice }); 655 | const insertPointBob = 656 | await delphiVoting.getInsertPoint.call(claimId, arbiterBob, PLURALITY_VOTE); 657 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointBob, 658 | { from: arbiterBob }); 659 | const insertPointCharlie = 660 | await delphiVoting.getInsertPoint.call(claimId, arbiterCharlie, PLURALITY_VOTE); 661 | await delphiVoting.revealVote(claimId, PLURALITY_VOTE, SALT, insertPointCharlie, 662 | { from: arbiterCharlie }); 663 | const insertPointDanielle = 664 | await delphiVoting.getInsertPoint.call(claimId, arbiterDanielle, NON_PLURALITY_VOTE); 665 | await delphiVoting.revealVote(claimId, NON_PLURALITY_VOTE, SALT, insertPointDanielle, 666 | { from: arbiterDanielle }); 667 | 668 | // Increase time to finish the reveal phase so we can submit the ruling 669 | await rpc.sendAsync({ method: 'evm_increaseTime', params: [100] }); 670 | 671 | // Submit ruling 672 | await delphiVoting.submitRuling(delphiStake.address, claimNumber, { from: arbiterAlice }); 673 | 674 | // Claim the fee and get Alice's final balance 675 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 676 | { from: arbiterAlice }); 677 | const finalBalanceAlice = await token.balanceOf(arbiterAlice); 678 | 679 | // Alice's expected final balance is her starting balance plus (20 + 13)% of the fee 680 | const expectedFinalBalanceAlice = '103300'; 681 | assert.strictEqual(finalBalanceAlice.toString(10), expectedFinalBalanceAlice.toString(10), 682 | 'Alice did not get the proper fee allocation'); 683 | 684 | // Claim the fee and get Bob's final balance 685 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 686 | { from: arbiterBob }); 687 | const finalBalanceBob = await token.balanceOf(arbiterBob); 688 | 689 | // Bob's expected final balance is his starting balance plus (16 + 13)% of the fee 690 | const expectedFinalBalanceBob = '102900'; 691 | assert.strictEqual(finalBalanceBob.toString(10), expectedFinalBalanceBob.toString(10), 692 | 'Bob did not get the proper fee allocation'); 693 | 694 | // Claim the fee and get Charlie's final balance 695 | await delphiVoting.claimFee(delphiStake.address, claimNumber, PLURALITY_VOTE, SALT, 696 | { from: arbiterCharlie }); 697 | const finalBalanceCharlie = await token.balanceOf(arbiterCharlie); 698 | 699 | // Charlie's expected final balance is his starting balance plus (12 + 13)% of the fee 700 | const expectedFinalBalanceCharlie = '102500'; 701 | assert.strictEqual(finalBalanceCharlie.toString(10), expectedFinalBalanceCharlie.toString(10), 702 | 'Charlie did not get the proper fee allocation'); 703 | }); 704 | }); 705 | }); 706 | --------------------------------------------------------------------------------