├── .eslintignore ├── .gitattributes ├── .eslintrc.json ├── .gitignore ├── migrations ├── 1_initial_migration.js ├── 2_libs.js ├── 3_optional_plcr_factory.js ├── 5_registry_factory.js └── 4_parameterizer_factory.js ├── test ├── registry │ ├── canBeWhitelisted.js │ ├── challengeCanBeResolved.js │ ├── isWhitelisted.js │ ├── Registry.js │ ├── tokenClaims.js │ ├── determineReward.js │ ├── appWasMade.js │ ├── userStories.js │ ├── deposit.js │ ├── updateStatuses.js │ ├── updateStatus.js │ ├── withdraw.js │ ├── claimRewards.js │ ├── initExit.js │ ├── apply.js │ ├── claimReward.js │ ├── challenge.js │ └── finalizeExit.js ├── parameterizer │ ├── get.js │ ├── propExists.js │ ├── challengeCanBeResolved.js │ ├── canBeSet.js │ ├── voterReward.js │ ├── tokenClaims.js │ ├── proposeReparameterization.js │ ├── claimRewards.js │ ├── challengeReparameterization.js │ ├── processProposal.js │ └── claimReward.js ├── voting.js ├── ParameterizerFactory │ ├── newParameterizerWithToken.js │ └── newParameterizerBYOToken.js ├── RegistryFactory │ ├── newRegistryWithToken.js │ └── newRegistryBYOToken.js └── utils.js ├── .solcover.js ├── ethpm.json ├── contracts ├── Migrations.sol ├── RegistryFactory.sol ├── ParameterizerFactory.sol └── Parameterizer.sol ├── conf └── config.json ├── truffle.js ├── package.json ├── README.md ├── scripts └── deploy_proxies.js └── LICENSE.txt /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/**/*.js 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | build/* 3 | builds/* 4 | truffle.js 5 | secrets.json 6 | *.swp 7 | installed_contracts/ 8 | conf/ 9 | coverage/ 10 | coverage.json 11 | notes.md -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /migrations/2_libs.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const DLL = artifacts.require('dll/DLL.sol'); 4 | const AttributeStore = artifacts.require('attrstore/AttributeStore.sol'); 5 | 6 | module.exports = (deployer) => { 7 | // deploy libraries 8 | deployer.deploy(DLL); 9 | return deployer.deploy(AttributeStore); 10 | }; 11 | -------------------------------------------------------------------------------- /test/registry/canBeWhitelisted.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract */ 3 | 4 | contract('Registry', () => { 5 | describe('Function: canBeWhitelisted', () => { 6 | it('should return true for a listing that has passed all tests'); 7 | it('should return false for a listing that failes any one of the tests'); 8 | }); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /test/registry/challengeCanBeResolved.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract */ 3 | 4 | contract('Registry', () => { 5 | describe('Function: challengeCanBeResolved', () => { 6 | it('should return true for a poll that has ended'); 7 | it('should return false if the poll either doesnt exist, or its still in contention'); 8 | }); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /migrations/3_optional_plcr_factory.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const PLCRFactory = artifacts.require('plcr-revival/PLCRFactory.sol'); 4 | const DLL = artifacts.require('dll/DLL.sol'); 5 | const AttributeStore = artifacts.require('attrstore/AttributeStore.sol'); 6 | 7 | module.exports = (deployer, network) => { 8 | // link libraries 9 | deployer.link(DLL, PLCRFactory); 10 | deployer.link(AttributeStore, PLCRFactory); 11 | 12 | if (network === 'mainnet') { 13 | return deployer; 14 | } 15 | 16 | return deployer.deploy(PLCRFactory); 17 | }; 18 | -------------------------------------------------------------------------------- /ethpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "package_name": "tcr", 3 | "version": "2.0.0", 4 | "description": "A generic, hash-keyed TCR", 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 | "zeppelin": "1.3.0", 24 | "plcr-revival": "2.0.0" 25 | }, 26 | "license": "Apache 2.0" 27 | } 28 | -------------------------------------------------------------------------------- /migrations/5_registry_factory.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const RegistryFactory = artifacts.require('./RegistryFactory.sol'); 4 | const DLL = artifacts.require('dll/DLL.sol'); 5 | const AttributeStore = artifacts.require('attrstore/AttributeStore.sol'); 6 | const ParameterizerFactory = artifacts.require('./ParameterizerFactory.sol'); 7 | 8 | module.exports = (deployer) => { 9 | // link libraries 10 | deployer.link(DLL, RegistryFactory); 11 | deployer.link(AttributeStore, RegistryFactory); 12 | 13 | return deployer.deploy(RegistryFactory, ParameterizerFactory.address); 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/4_parameterizer_factory.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const ParameterizerFactory = artifacts.require('./ParameterizerFactory.sol'); 4 | const DLL = artifacts.require('dll/DLL.sol'); 5 | const AttributeStore = artifacts.require('attrstore/AttributeStore.sol'); 6 | const PLCRFactory = artifacts.require('plcr-revival/PLCRFactory.sol'); 7 | 8 | module.exports = (deployer, network) => { 9 | // link libraries 10 | deployer.link(DLL, ParameterizerFactory); 11 | deployer.link(AttributeStore, ParameterizerFactory); 12 | 13 | if (network === 'mainnet') { 14 | return deployer.deploy(ParameterizerFactory, '0xdf9c10e2e9bb8968b908261d38860b1a038cc2ef'); 15 | } 16 | 17 | return deployer.deploy(ParameterizerFactory, PLCRFactory.address); 18 | }; 19 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | // These are useless imports, but they force these contracts to be compiled. We need their build 4 | // files for the test pipeline. 5 | import "tokens/eip20/EIP20.sol"; 6 | import "plcr-revival/PLCRFactory.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) public restricted { 21 | last_completed_migration = completed; 22 | } 23 | 24 | function upgrade(address new_address) public restricted { 25 | Migrations upgraded = Migrations(new_address); 26 | upgraded.setCompleted(last_completed_migration); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/parameterizer/get.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const utils = require('../utils'); 4 | const fs = require('fs'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | contract('Parameterizer', (accounts) => { 10 | describe('Function: get', () => { 11 | let token; 12 | let voting; 13 | let parameterizer; 14 | 15 | beforeEach(async () => { 16 | const { votingProxy, paramProxy, tokenInstance } = await utils.getProxies(token); 17 | voting = votingProxy; 18 | parameterizer = paramProxy; 19 | token = tokenInstance; 20 | 21 | await utils.approveProxies(accounts, token, voting, parameterizer, false); 22 | }); 23 | it('should get a parameter', async () => { 24 | const result = await parameterizer.get.call('minDeposit'); 25 | assert.equal(result, paramConfig.minDeposit, 'minDeposit param has wrong value'); 26 | }); 27 | }); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /test/parameterizer/propExists.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const utils = require('../utils'); 4 | 5 | contract('Parameterizer', (accounts) => { 6 | describe('Function: propExists', () => { 7 | const [proposer] = accounts; 8 | 9 | let token; 10 | let parameterizer; 11 | 12 | beforeEach(async () => { 13 | const { paramProxy, tokenInstance } = await utils.getProxies(); 14 | parameterizer = paramProxy; 15 | token = tokenInstance; 16 | 17 | await utils.approveProxies(accounts, token, false, parameterizer, false); 18 | }); 19 | 20 | it('should true if a proposal exists for the provided propID', async () => { 21 | const propID = await utils.proposeReparamAndGetPropID('voteQuorum', '51', proposer, parameterizer); 22 | const result = await parameterizer.propExists(propID); 23 | assert.strictEqual(result, true, 'should have been true cause I literally just made the proposal'); 24 | }); 25 | 26 | it('should false if no proposal exists for the provided propID', async () => { 27 | const result = await parameterizer.propExists('666'); 28 | assert.strictEqual(result, false, 'should have been false cause i just made it up!'); 29 | }); 30 | }); 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /conf/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "paramDefaults": { 3 | "minDeposit": 10000000000000000000, 4 | "pMinDeposit": 100000000000000000000, 5 | "applyStageLength": 600, 6 | "pApplyStageLength": 1200, 7 | "commitStageLength": 600, 8 | "pCommitStageLength": 1200, 9 | "revealStageLength": 600, 10 | "pRevealStageLength": 1200, 11 | "dispensationPct": 50, 12 | "pDispensationPct": 50, 13 | "voteQuorum": 50, 14 | "pVoteQuorum": 50, 15 | "exitTimeDelay": 600, 16 | "exitPeriodLen": 600 17 | }, 18 | "name": "The TestChain Registry", 19 | "token": { 20 | "supply": "1000000000000000000000000000", 21 | "name": "TestCoin", 22 | "decimals": "18", 23 | "symbol": "TEST", 24 | "tokenHolders": [ 25 | "0xd09cc3bc67e4294c4a446d8e4a2934a921410ed7", 26 | "0x8FEa967fF3E8B14498BC17D8d43E8cdd614613c7", 27 | "0x874Fec5f1D5045B85f46fBD2Be6264867c3D7cE5", 28 | "0xf978cC95Eba88cC8869e580d282620e3b4382016", 29 | "0x4fB9dEA18CDf6f737afDfc3f680Fb5500F8F893A", 30 | "0xf17f52151EbEF6C7334FAD080c5704D77216b732", 31 | "0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef", 32 | "0x821aEa9a577a9b44299B9c15c88cf3087F3b5544", 33 | "0x627306090abaB3A6e1400e9345bC60c78a8BEf57" 34 | ] 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /test/registry/isWhitelisted.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | 5 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 6 | const paramConfig = config.paramDefaults; 7 | 8 | const utils = require('../utils.js'); 9 | 10 | contract('Registry', (accounts) => { 11 | describe('Function: isWhitelisted', () => { 12 | const [applicant] = accounts; 13 | 14 | let token; 15 | let registry; 16 | 17 | beforeEach(async () => { 18 | const { registryProxy, tokenInstance } = await utils.getProxies(); 19 | registry = registryProxy; 20 | token = tokenInstance; 21 | 22 | await utils.approveProxies(accounts, token, false, false, registry); 23 | }); 24 | 25 | it('should verify a listing is not in the whitelist', async () => { 26 | const listing = utils.getListingHash('eth.eth'); // the listing to be tested 27 | const result = await registry.isWhitelisted.call(listing); 28 | assert.strictEqual(result, false, 'Listing should not be whitelisted'); 29 | }); 30 | 31 | it('should verify a listing is in the whitelist', async () => { 32 | const listing = utils.getListingHash('eth.eth'); 33 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 34 | const result = await registry.isWhitelisted.call(listing); 35 | assert.strictEqual(result, true, 'Listing should have been whitelisted'); 36 | }); 37 | }); 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | const HDWalletProvider = require('truffle-hdwallet-provider'); 2 | const fs = require('fs'); 3 | 4 | let mnemonic = ''; 5 | 6 | if (fs.existsSync('secrets.json')) { 7 | const secrets = JSON.parse(fs.readFileSync('secrets.json', 'utf8')); 8 | ({ mnemonic } = secrets); 9 | } 10 | 11 | if (process.env.MNEMONIC) { 12 | mnemonic = process.env.MNEMONIC; 13 | } 14 | 15 | module.exports = { 16 | networks: { 17 | mainnet: { 18 | provider: () => new HDWalletProvider(mnemonic, 'https://mainnet.infura.io'), 19 | network_id: '1', 20 | gas: 4500000, 21 | gasPrice: 10000000000, 22 | }, 23 | ganache: { 24 | provider: () => new HDWalletProvider(mnemonic, 'http://localhost:8545'), 25 | network_id: '*', 26 | gas: 6000000, 27 | gasPrice: 25000000000, 28 | }, 29 | rinkeby: { 30 | provider: () => new HDWalletProvider(mnemonic, 'https://rinkeby.infura.io'), 31 | network_id: '*', 32 | gas: 4500000, 33 | gasPrice: 25000000000, 34 | }, 35 | ropsten: { 36 | provider: () => new HDWalletProvider(mnemonic, 'https://ropsten.infura.io'), 37 | network_id: '*', 38 | gas: 4500000, 39 | gasPrice: 25000000000, 40 | }, 41 | // config for solidity-coverage 42 | coverage: { 43 | host: 'localhost', 44 | network_id: '*', 45 | port: 7545, // <-- If you change this, also set the port option in .solcover.js. 46 | gas: 0xfffffffffff, // <-- Use this high gas value 47 | gasPrice: 0x01, // <-- Use this low gas price 48 | }, 49 | }, 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /test/registry/Registry.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | 5 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 6 | const utils = require('../utils.js'); 7 | 8 | contract('Registry', (accounts) => { 9 | describe('Function: Registry (constructor)', () => { 10 | let token; 11 | let voting; 12 | let parameterizer; 13 | let registry; 14 | 15 | beforeEach(async () => { 16 | const { 17 | votingProxy, paramProxy, registryProxy, tokenInstance, 18 | } = await utils.getProxies(); 19 | voting = votingProxy; 20 | parameterizer = paramProxy; 21 | registry = registryProxy; 22 | token = tokenInstance; 23 | 24 | await utils.approveProxies(accounts, token, voting, parameterizer, registry); 25 | }); 26 | 27 | it('should instantiate storage variables with the values in the config file', async () => { 28 | assert.strictEqual((await registry.token.call()), token.address, 'The token storage ' + 29 | 'variable is improperly initialized'); 30 | assert.strictEqual( 31 | (await registry.parameterizer.call()), parameterizer.address, 32 | 'The parameterizer storage variable is improperly initialized', 33 | ); 34 | assert.strictEqual( 35 | (await registry.voting.call()), voting.address, 36 | 'The voting storage variable is improperly initialized', 37 | ); 38 | assert.strictEqual( 39 | (await registry.name.call()), config.name, 40 | 'The name storage variable is improperly initialized', 41 | ); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sol-tcr", 3 | "version": "0.0.1", 4 | "description": "A generic, string-keyed TCR in Solidity", 5 | "scripts": { 6 | "install": "truffle install", 7 | "compile": "truffle compile", 8 | "publish": "truffle publish", 9 | "coverage": "solidity-coverage", 10 | "deploy-rinkeby": "truffle migrate --network rinkeby", 11 | "deploy-ganache": "truffle migrate --network ganache", 12 | "deploy-mainnet": "truffle migrate --network mainnet", 13 | "deploy-proxies:rinkeby": "truffle exec --network rinkeby ./scripts/deploy_proxies.js", 14 | "deploy-proxies:ganache": "truffle exec --network ganache ./scripts/deploy_proxies.js", 15 | "deploy-proxies:mainnet": "truffle exec --network mainnet ./scripts/deploy_proxies.js", 16 | "test": "npm run lint ./ && truffle test", 17 | "fix": "eslint --fix", 18 | "lint": "eslint" 19 | }, 20 | "author": "Irene Lin , Mira Zeitlin , Yorke Rhodes ", 21 | "license": "ISC", 22 | "dependencies": { 23 | "bignumber.js": "5.0.0", 24 | "bip39": "2.5.0", 25 | "bn.js": "4.11.8", 26 | "ethereumjs-abi": "0.6.5", 27 | "ethereumjs-wallet": "0.6.0", 28 | "ethjs": "0.3.1", 29 | "ethjs-provider-http": "0.1.6", 30 | "ethjs-query": "0.3.2", 31 | "ethjs-rpc": "0.1.8", 32 | "ethjs-util": "0.1.4", 33 | "truffle": "4.1.12", 34 | "truffle-hdwallet-provider": "0.0.5" 35 | }, 36 | "devDependencies": { 37 | "eslint": "4.15.0", 38 | "eslint-config-airbnb-base": "12.1.0", 39 | "eslint-plugin-import": "2.8.0", 40 | "solidity-coverage": "0.5.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/parameterizer/challengeCanBeResolved.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../utils'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | contract('Parameterizer', (accounts) => { 10 | describe('Function: challengeCanBeResolved', () => { 11 | const [proposer, challenger] = accounts; 12 | 13 | let token; 14 | let parameterizer; 15 | 16 | beforeEach(async () => { 17 | const { paramProxy, tokenInstance } = await utils.getProxies(); 18 | parameterizer = paramProxy; 19 | token = tokenInstance; 20 | 21 | await utils.approveProxies(accounts, token, false, parameterizer, false); 22 | }); 23 | 24 | it('should true if a challenge is ready to be resolved', async () => { 25 | const propID = await utils.proposeReparamAndGetPropID('voteQuorum', '51', proposer, parameterizer); 26 | 27 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 28 | await utils.increaseTime(paramConfig.pCommitStageLength); 29 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 30 | 31 | const result = await parameterizer.challengeCanBeResolved(propID); 32 | assert.strictEqual(result, true, 'should have been true cause enough time has passed'); 33 | }); 34 | 35 | it('should false if a challenge is not ready to be resolved', async () => { 36 | const propID = await utils.proposeReparamAndGetPropID('voteQuorum', '59', proposer, parameterizer); 37 | 38 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 39 | await utils.increaseTime(paramConfig.pCommitStageLength); 40 | 41 | const result = await parameterizer.challengeCanBeResolved(propID); 42 | assert.strictEqual(result, false, 'should have been false because not enough time has passed'); 43 | }); 44 | }); 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /test/parameterizer/canBeSet.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../utils'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | contract('Parameterizer', (accounts) => { 10 | describe('Function: canBeSet', () => { 11 | const [proposer] = accounts; 12 | 13 | let token; 14 | let parameterizer; 15 | 16 | beforeEach(async () => { 17 | const { paramProxy, tokenInstance } = await utils.getProxies(); 18 | parameterizer = paramProxy; 19 | token = tokenInstance; 20 | 21 | await utils.approveProxies(accounts, token, false, parameterizer, false); 22 | }); 23 | 24 | it('should true if a proposal passed its application stage with no challenge', async () => { 25 | const propID = await utils.proposeReparamAndGetPropID('voteQuorum', '51', proposer, parameterizer); 26 | 27 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 28 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 29 | 30 | const result = await parameterizer.canBeSet(propID); 31 | assert.strictEqual(result, true, 'should have returned true because enough time has passed'); 32 | }); 33 | 34 | it('should false if a proposal did not pass its application stage with no challenge', async () => { 35 | const propID = await utils.proposeReparamAndGetPropID('dispensationPct', '58', proposer, parameterizer); 36 | 37 | const betterBeFalse = await parameterizer.canBeSet(propID); 38 | assert.strictEqual(betterBeFalse, false, 'should have returned false because not enough time has passed'); 39 | 40 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 41 | 42 | const result = await parameterizer.canBeSet(propID); 43 | assert.strictEqual(result, true, 'should have been able to set because commit period is done'); 44 | }); 45 | }); 46 | }); 47 | 48 | -------------------------------------------------------------------------------- /test/voting.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const utils = require('./utils.js'); 4 | 5 | const fs = require('fs'); 6 | const BN = require('bignumber.js'); 7 | 8 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 9 | const paramConfig = config.paramDefaults; 10 | 11 | contract('PLCRVoting', (accounts) => { 12 | describe('Function: commitVote', () => { 13 | const [applicant, challenger, voter] = accounts; 14 | 15 | let token; 16 | let voting; 17 | let parameterizer; 18 | let registry; 19 | 20 | beforeEach(async () => { 21 | const { 22 | votingProxy, paramProxy, registryProxy, tokenInstance, 23 | } = await utils.getProxies(); 24 | voting = votingProxy; 25 | parameterizer = paramProxy; 26 | registry = registryProxy; 27 | token = tokenInstance; 28 | 29 | 30 | await utils.approveProxies(accounts, token, voting, parameterizer, registry); 31 | }); 32 | 33 | it('should correctly update DLL state', async () => { 34 | const firstDomain = 'first.net'; 35 | const secondDomain = 'second.net'; 36 | const minDeposit = new BN(paramConfig.minDeposit, 10); 37 | 38 | await utils.as(applicant, registry.apply, firstDomain, minDeposit, ''); 39 | await utils.as(applicant, registry.apply, secondDomain, minDeposit, ''); 40 | const firstPollID = await utils.challengeAndGetPollID(firstDomain, challenger, registry); 41 | const secondPollID = await utils.challengeAndGetPollID(secondDomain, challenger, registry); 42 | await utils.commitVote(firstPollID, 1, 7, 420, voter, voting); 43 | await utils.commitVote(secondPollID, 1, 8, 420, voter, voting); 44 | await utils.commitVote(firstPollID, 1, 9, 420, voter, voting); 45 | const insertPoint = await voting.getInsertPointForNumTokens.call(voter, 6, firstPollID); 46 | const expectedInsertPoint = 0; 47 | 48 | assert.strictEqual( 49 | insertPoint.toString(10), expectedInsertPoint.toString(10), 50 | 'The insert point was not correct', 51 | ); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/registry/tokenClaims.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: tokenClaims', () => { 15 | const minDeposit = bigTen(paramConfig.minDeposit); 16 | const [applicant, challenger, voter] = accounts; 17 | 18 | let token; 19 | let voting; 20 | let registry; 21 | 22 | beforeEach(async () => { 23 | const { votingProxy, registryProxy, tokenInstance } = await utils.getProxies(); 24 | voting = votingProxy; 25 | registry = registryProxy; 26 | token = tokenInstance; 27 | 28 | await utils.approveProxies(accounts, token, voting, false, registry); 29 | }); 30 | 31 | it('should report properly whether a voter has claimed tokens', async () => { 32 | const listing = utils.getListingHash('claims.com'); 33 | 34 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 35 | 36 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 37 | 38 | await utils.commitVote(pollID, '0', '10', '420', voter, voting); 39 | await utils.increaseTime(paramConfig.commitStageLength + 1); 40 | 41 | await utils.as(voter, voting.revealVote, pollID, '0', '420'); 42 | await utils.increaseTime(paramConfig.revealStageLength + 1); 43 | 44 | await utils.as(challenger, registry.updateStatus, listing); 45 | 46 | const initialHasClaimed = await registry.tokenClaims.call(pollID, voter); 47 | assert.strictEqual(initialHasClaimed, false, 'The voter is purported to have claimed ' + 48 | 'their reward, when in fact they have not'); 49 | 50 | await utils.as(voter, registry.claimReward, pollID); 51 | 52 | const finalHasClaimed = await registry.tokenClaims.call(pollID, voter); 53 | assert.strictEqual(finalHasClaimed, true, 'The voter is purported to not have claimed ' + 54 | 'their reward, when in fact they have'); 55 | }); 56 | }); 57 | }); 58 | 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Token-Curated Registry 2 | 3 | [ ![Codeship Status for skmgoldin/tcr](https://app.codeship.com/projects/b140cce0-ac77-0135-0738-52e8b96e2dec/status?branch=master)](https://app.codeship.com/projects/257003) 4 | 5 | A hash-keyed [token-curated registry (TCR)](https://medium.com/@ilovebagels/token-curated-registries-1-0-61a232f8dac7). [Owner's Manual available](https://github.com/skmgoldin/tcr/blob/master/owners_manual.md). 6 | 7 | Mainnet factory: [0x74bd1d07a158e8a9eecfbd2267766f5919e2b21c](https://etherscan.io/address/0x74bd1d07a158e8a9eecfbd2267766f5919e2b21c#code) 8 | 9 | Rinkeby factory: [0x2bddfc0c506a00ea3a6ccea5fbbda8843377dcb1](https://rinkeby.etherscan.io/address/0x2bddfc0c506a00ea3a6ccea5fbbda8843377dcb1#code) 10 | 11 | EPM: [tcr](https://www.ethpm.com/registry/packages/44) 12 | 13 | ## Initialize 14 | The only environmental dependency you need is Node. Presently we can guarantee this all works with Node 8. 15 | ``` 16 | npm install 17 | npm run compile 18 | ``` 19 | 20 | ## Tests 21 | The repo has a comprehensive test suite. You can run it with `npm run test`. To run the tests with the RPC logs, use `npm run test gas`. 22 | 23 | ## Composition of the repo 24 | The repo is composed as a Truffle project, and is largely idiomatic to Truffle's conventions. The tests are in the `test` directory, the contracts are in the `contracts` directory and the migrations (deployment scripts) are in the `migrations` directory. Furthermore there is a `conf` directory containing json files where deployments can be parameterized. 25 | 26 | ## Deploying your own TCR 27 | Since [v1.1.0](https://github.com/skmgoldin/tcr/releases/tag/v1.1.0), only the factory contracts are deployed during `truffle migrate`. To deploy a RegistryFactory to any network you can use the NPM scripts in the `package.json`. To deploy to a local Ganache instance, set an environment variable `MNEMONIC` to the mnemonic exposed by Ganache. To spawn proxy contracts using a deployed RegistryFactory, execute the snippet in [/scripts](./scripts) by running: 28 | 29 | ``` 30 | npm run deploy-proxies:[network] 31 | ``` 32 | 33 | ## Packages 34 | The repo consumes several EPM packages. `dll` and `attrstore` are libraries used in PLCRVoting's doubly-linked list abstraction. `tokens` provides an ERC20-comaptible token implementation. `plcr-revival` features batched executions for some transactions. All packages are installed automatically when running `npm install`. 35 | 36 | -------------------------------------------------------------------------------- /test/ParameterizerFactory/newParameterizerWithToken.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract assert artifacts */ 3 | 4 | const ParameterizerFactory = artifacts.require('./ParameterizerFactory.sol'); 5 | const Token = artifacts.require('tokens/eip20/EIP20.sol'); 6 | const fs = require('fs'); 7 | 8 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 9 | const paramConfig = config.paramDefaults; 10 | 11 | contract('ParameterizerFactory', (accounts) => { 12 | describe('Function: newParameterizerWithToken', () => { 13 | let parameterizerFactory; 14 | 15 | beforeEach(async () => { 16 | parameterizerFactory = await ParameterizerFactory.deployed(); 17 | }); 18 | 19 | it('should deploy and initialize a new Parameterizer contract', async () => { 20 | const tokenParams = { 21 | supply: '1000', 22 | name: 'TEST', 23 | decimals: '2', 24 | symbol: 'TST', 25 | }; 26 | 27 | // new parameterizer using factory/proxy 28 | const parameters = [ 29 | paramConfig.minDeposit, 30 | paramConfig.pMinDeposit, 31 | paramConfig.applyStageLength, 32 | paramConfig.pApplyStageLength, 33 | paramConfig.commitStageLength, 34 | paramConfig.pCommitStageLength, 35 | paramConfig.revealStageLength, 36 | paramConfig.pRevealStageLength, 37 | paramConfig.dispensationPct, 38 | paramConfig.pDispensationPct, 39 | paramConfig.voteQuorum, 40 | paramConfig.pVoteQuorum, 41 | paramConfig.exitTimeDelay, 42 | paramConfig.exitPeriodLen, 43 | ]; 44 | const parameterizerReceipt = await parameterizerFactory.newParameterizerWithToken( 45 | tokenParams.supply, 46 | tokenParams.name, 47 | tokenParams.decimals, 48 | tokenParams.symbol, 49 | parameters, 50 | { from: accounts[0] }, 51 | ); 52 | const { creator, token } = parameterizerReceipt.logs[0].args; 53 | 54 | const tokenInstance = await Token.at(token); 55 | const actualName = await tokenInstance.name.call(); 56 | assert.strictEqual(actualName, tokenParams.name, 'token.name is incorrect'); 57 | 58 | // verify: parameterizer's creator 59 | assert.strictEqual(creator, accounts[0], 'the creator emitted in the newParameterizer event ' + 60 | 'not correspond to the one which sent the creation transaction'); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/parameterizer/voterReward.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../utils'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | contract('Parameterizer', (accounts) => { 10 | describe('Function: voterReward', () => { 11 | const [proposer, challenger, voterAlice] = accounts; 12 | 13 | let token; 14 | let voting; 15 | let parameterizer; 16 | 17 | beforeEach(async () => { 18 | const { votingProxy, paramProxy, tokenInstance } = await utils.getProxies(token); 19 | voting = votingProxy; 20 | parameterizer = paramProxy; 21 | token = tokenInstance; 22 | 23 | await utils.approveProxies(accounts, token, voting, parameterizer, false); 24 | }); 25 | 26 | it('should return the correct number of tokens to voter on the winning side.', async () => { 27 | const propID = await utils.proposeReparamAndGetPropID('voteQuorum', '51', proposer, parameterizer); 28 | const challengeID = await utils 29 | .challengeReparamAndGetChallengeID(propID, challenger, parameterizer); 30 | 31 | // Alice commits a vote: FOR, 10 tokens, 420 salt 32 | await utils.commitVote(challengeID, '1', '10', '420', voterAlice, voting); 33 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 34 | 35 | // Alice reveals her vote: FOR, 420 salt 36 | await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); 37 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 38 | 39 | await parameterizer.processProposal(propID); 40 | 41 | // Grab the challenge struct after the proposal has been processed 42 | const challenge = await parameterizer.challenges.call(challengeID); 43 | const voterTokens = await voting.getNumPassingTokens(voterAlice, challengeID); // 10 44 | const rewardPool = challenge[0]; // 250,000 45 | const totalTokens = challenge[4]; // 10 46 | 47 | const expectedVoterReward = (voterTokens.mul(rewardPool)).div(totalTokens); // 250,000 48 | const voterReward = await parameterizer.voterReward(voterAlice, challengeID); 49 | 50 | assert.strictEqual( 51 | expectedVoterReward.toString(10), voterReward.toString(10), 52 | 'voterReward should have equaled tokens * pool / total', 53 | ); 54 | }); 55 | it('should return zero tokens to a voter who cannot reveal a vote on the winning side.'); 56 | }); 57 | }); 58 | 59 | -------------------------------------------------------------------------------- /test/registry/determineReward.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | 5 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 6 | const paramConfig = config.paramDefaults; 7 | 8 | const utils = require('../utils.js'); 9 | 10 | contract('Registry', (accounts) => { 11 | describe('Function: determineReward', () => { 12 | const [applicant, challenger] = accounts; 13 | 14 | let token; 15 | let registry; 16 | 17 | beforeEach(async () => { 18 | const { registryProxy, tokenInstance } = await utils.getProxies(); 19 | registry = registryProxy; 20 | token = tokenInstance; 21 | 22 | await utils.approveProxies(accounts, token, false, false, registry); 23 | }); 24 | 25 | it('should revert if the challenge has already been resolved', async () => { 26 | const listing = utils.getListingHash('failure.net'); 27 | 28 | // Apply 29 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 30 | // Challenge 31 | const challengeID = await utils.challengeAndGetPollID(listing, challenger, registry); 32 | // Resolve challenge 33 | await utils.increaseTime(paramConfig.commitStageLength + paramConfig.revealStageLength + 1); 34 | await registry.updateStatus(listing); 35 | 36 | // Verify that the challenge has been resolved 37 | const challenge = await registry.challenges.call(challengeID); 38 | const resolved = challenge[2]; 39 | assert.strictEqual(resolved, true, 'Challenge has not been resolved'); 40 | 41 | // Try to determine reward 42 | try { 43 | await utils.as(challenger, registry.determineReward, challengeID); 44 | } catch (err) { 45 | assert(utils.isEVMException(err), err.toString()); 46 | return; 47 | } 48 | assert(false, 'determined reward after challenge already resolved'); 49 | }); 50 | 51 | it('should revert if the poll has not ended yet', async () => { 52 | const listing = utils.getListingHash('failure.net'); 53 | 54 | // Apply 55 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 56 | // Challenge 57 | const challengeID = await utils.challengeAndGetPollID(listing, challenger, registry); 58 | 59 | try { 60 | await utils.as(challenger, registry.determineReward, challengeID); 61 | } catch (err) { 62 | assert(utils.isEVMException(err), err.toString()); 63 | return; 64 | } 65 | assert(false, 'determined reward before poll has ended'); 66 | }); 67 | }); 68 | }); 69 | 70 | -------------------------------------------------------------------------------- /test/ParameterizerFactory/newParameterizerBYOToken.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract assert artifacts */ 3 | 4 | const EIP20 = artifacts.require('tokens/eip20/EIP20.sol'); 5 | const ParameterizerFactory = artifacts.require('./ParameterizerFactory.sol'); 6 | const Parameterizer = artifacts.require('./Parameterizer.sol'); 7 | const fs = require('fs'); 8 | 9 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 10 | const paramConfig = config.paramDefaults; 11 | 12 | contract('ParameterizerFactory', (accounts) => { 13 | describe('Function: newParameterizerBYOToken', () => { 14 | let parameterizerFactory; 15 | 16 | beforeEach(async () => { 17 | parameterizerFactory = await ParameterizerFactory.deployed(); 18 | }); 19 | 20 | it('should deploy and initialize a new Parameterizer contract', async () => { 21 | const tokenParams = { 22 | supply: '1000', 23 | name: 'TEST', 24 | decimals: '2', 25 | symbol: 'TST', 26 | }; 27 | // new EIP20 token 28 | const token = await EIP20.new( 29 | tokenParams.supply, 30 | tokenParams.name, 31 | tokenParams.decimals, 32 | tokenParams.symbol, 33 | ); 34 | 35 | // new parameterizer using factory/proxy 36 | const parameters = [ 37 | paramConfig.minDeposit, 38 | paramConfig.pMinDeposit, 39 | paramConfig.applyStageLength, 40 | paramConfig.pApplyStageLength, 41 | paramConfig.commitStageLength, 42 | paramConfig.pCommitStageLength, 43 | paramConfig.revealStageLength, 44 | paramConfig.pRevealStageLength, 45 | paramConfig.dispensationPct, 46 | paramConfig.pDispensationPct, 47 | paramConfig.voteQuorum, 48 | paramConfig.pVoteQuorum, 49 | paramConfig.exitTimeDelay, 50 | paramConfig.exitPeriodLen, 51 | ]; 52 | const parameterizerReceipt = await parameterizerFactory 53 | .newParameterizerBYOToken(token.address, parameters, { from: accounts[0] }); 54 | const parameterizer = Parameterizer.at(parameterizerReceipt.logs[0].args.parameterizer); 55 | const { creator } = parameterizerReceipt.logs[0].args; 56 | 57 | // verify: parameterizer's token 58 | const parameterizerToken = await parameterizer.token.call(); 59 | assert.strictEqual( 60 | parameterizerToken, 61 | token.address, 62 | 'the token connected to parameterizer is incorrect', 63 | ); 64 | // verify: parameterizer's creator 65 | assert.strictEqual(creator, accounts[0], 'the creator emitted in the newParameterizer event ' + 66 | 'not correspond to the one which sent the creation transaction'); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/registry/appWasMade.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: appWasMade', () => { 15 | const [applicant] = accounts; 16 | const minDeposit = bigTen(paramConfig.minDeposit); 17 | 18 | let token; 19 | let registry; 20 | 21 | beforeEach(async () => { 22 | const { registryProxy, tokenInstance } = await utils.getProxies(); 23 | registry = registryProxy; 24 | token = tokenInstance; 25 | 26 | await utils.approveProxies(accounts, token, false, false, registry); 27 | }); 28 | 29 | it('should return true if applicationExpiry was previously initialized', async () => { 30 | const listing = utils.getListingHash('wasthismade.net'); 31 | 32 | // Apply 33 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 34 | const result = await registry.appWasMade(listing); 35 | assert.strictEqual(result, true, 'should have returned true for the applied listing'); 36 | 37 | // Commit stage complete 38 | await utils.increaseTime(paramConfig.commitStageLength + 1); 39 | const resultTwo = await registry.appWasMade(listing); 40 | assert.strictEqual(resultTwo, true, 'should have returned true because app is still not expired'); 41 | 42 | // Reveal stage complete, update status (whitelist it) 43 | await utils.increaseTime(paramConfig.revealStageLength + 1); 44 | await utils.as(applicant, registry.updateStatus, listing); 45 | const isWhitelisted = await registry.isWhitelisted.call(listing); 46 | assert.strictEqual(isWhitelisted, true, 'should have been whitelisted'); 47 | const resultThree = await registry.appWasMade(listing); 48 | assert.strictEqual(resultThree, true, 'should have returned true because its whitelisted'); 49 | 50 | // Exit 51 | await registry.initExit(listing, { from: applicant }); 52 | await utils.increaseTime(paramConfig.exitTimeDelay + 1); 53 | await registry.finalizeExit(listing, { from: applicant }); 54 | // await utils.as(applicant, registry.exit, listing); 55 | const resultFour = await registry.appWasMade(listing); 56 | assert.strictEqual(resultFour, false, 'should have returned false because exit'); 57 | }); 58 | 59 | it('should return false if applicationExpiry was uninitialized', async () => { 60 | const listing = utils.getListingHash('falseapp.net'); 61 | 62 | const result = await registry.appWasMade(listing); 63 | assert.strictEqual(result, false, 'should have returned false because listing was never applied'); 64 | }); 65 | }); 66 | }); 67 | 68 | -------------------------------------------------------------------------------- /test/RegistryFactory/newRegistryWithToken.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract assert artifacts */ 3 | 4 | const EIP20 = artifacts.require('tokens/eip20/EIP20.sol'); 5 | const RegistryFactory = artifacts.require('./RegistryFactory.sol'); 6 | const Registry = artifacts.require('./Registry.sol'); 7 | const fs = require('fs'); 8 | 9 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 10 | const paramConfig = config.paramDefaults; 11 | 12 | contract('RegistryFactory', (accounts) => { 13 | describe('Function: newRegistryWithToken', () => { 14 | let registryFactory; 15 | 16 | beforeEach(async () => { 17 | registryFactory = await RegistryFactory.deployed(); 18 | }); 19 | 20 | it('should deploy and initialize a new Registry contract', async () => { 21 | const tokenParams = { 22 | supply: '1000', 23 | name: 'TEST', 24 | decimals: '2', 25 | symbol: 'TST', 26 | }; 27 | 28 | // new parameterizer using factory/proxy 29 | const parameters = [ 30 | paramConfig.minDeposit, 31 | paramConfig.pMinDeposit, 32 | paramConfig.applyStageLength, 33 | paramConfig.pApplyStageLength, 34 | paramConfig.commitStageLength, 35 | paramConfig.pCommitStageLength, 36 | paramConfig.revealStageLength, 37 | paramConfig.pRevealStageLength, 38 | paramConfig.dispensationPct, 39 | paramConfig.pDispensationPct, 40 | paramConfig.voteQuorum, 41 | paramConfig.pVoteQuorum, 42 | paramConfig.exitTimeDelay, 43 | paramConfig.exitPeriodLen, 44 | ]; 45 | 46 | // new registry using factory/proxy 47 | const registryReceipt = await registryFactory.newRegistryWithToken( 48 | tokenParams.supply, 49 | tokenParams.name, 50 | tokenParams.decimals, 51 | tokenParams.symbol, 52 | parameters, 53 | 'NEW TCR', 54 | { from: accounts[0] }, 55 | ); 56 | const { creator } = registryReceipt.logs[0].args; 57 | const registry = Registry.at(registryReceipt.logs[0].args.registry); 58 | 59 | // verify: registry's token 60 | const registryToken = EIP20.at(await registry.token.call()); 61 | const tokenName = await registryToken.name.call(); 62 | assert.strictEqual( 63 | tokenName, 64 | tokenParams.name, 65 | 'the token attached to the Registry contract does not correspond to the one emitted in the newRegistry event', 66 | ); 67 | // verify: registry's name 68 | const registryName = await registry.name.call(); 69 | assert.strictEqual( 70 | registryName, 71 | 'NEW TCR', 72 | 'the registry\'s name is incorrect', 73 | ); 74 | // verify: registry's creator 75 | assert.strictEqual(creator, accounts[0], 'the creator emitted in the newRegistry event ' + 76 | 'not correspond to the one which sent the creation transaction'); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/RegistryFactory/newRegistryBYOToken.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global contract assert artifacts */ 3 | 4 | const EIP20 = artifacts.require('tokens/eip20/EIP20.sol'); 5 | const RegistryFactory = artifacts.require('./RegistryFactory.sol'); 6 | const Registry = artifacts.require('./Registry.sol'); 7 | const fs = require('fs'); 8 | 9 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 10 | const paramConfig = config.paramDefaults; 11 | 12 | contract('RegistryFactory', (accounts) => { 13 | describe('Function: newRegistryBYOToken', () => { 14 | let registryFactory; 15 | 16 | beforeEach(async () => { 17 | registryFactory = await RegistryFactory.deployed(); 18 | }); 19 | 20 | it('should deploy and initialize a new Registry contract', async () => { 21 | const tokenParams = { 22 | supply: '1000', 23 | name: 'TEST', 24 | decimals: '2', 25 | symbol: 'TST', 26 | }; 27 | // new EIP20 token 28 | const token = await EIP20.new( 29 | tokenParams.supply, 30 | tokenParams.name, 31 | tokenParams.decimals, 32 | tokenParams.symbol, 33 | ); 34 | 35 | // new parameterizer using factory/proxy 36 | const parameters = [ 37 | paramConfig.minDeposit, 38 | paramConfig.pMinDeposit, 39 | paramConfig.applyStageLength, 40 | paramConfig.pApplyStageLength, 41 | paramConfig.commitStageLength, 42 | paramConfig.pCommitStageLength, 43 | paramConfig.revealStageLength, 44 | paramConfig.pRevealStageLength, 45 | paramConfig.dispensationPct, 46 | paramConfig.pDispensationPct, 47 | paramConfig.voteQuorum, 48 | paramConfig.pVoteQuorum, 49 | paramConfig.exitTimeDelay, 50 | paramConfig.exitPeriodLen, 51 | ]; 52 | 53 | // new registry using factory/proxy 54 | const registryReceipt = await registryFactory.newRegistryBYOToken( 55 | token.address, 56 | parameters, 57 | 'NEW TCR', 58 | { from: accounts[0] }, 59 | ); 60 | const { creator } = registryReceipt.logs[0].args; 61 | const registry = Registry.at(registryReceipt.logs[0].args.registry); 62 | 63 | // verify: registry's token 64 | const registryToken = await registry.token.call(); 65 | assert.strictEqual( 66 | registryToken, 67 | token.address, 68 | 'the token attached to the Registry contract does not correspond to the one emitted in the newRegistry event', 69 | ); 70 | // verify: registry's name 71 | const registryName = await registry.name.call(); 72 | assert.strictEqual( 73 | registryName, 74 | 'NEW TCR', 75 | 'the registry\'s name is incorrect', 76 | ); 77 | // verify: registry's creator 78 | assert.strictEqual(creator, accounts[0], 'the creator emitted in the newRegistry event ' + 79 | 'not correspond to the one which sent the creation transaction'); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /contracts/RegistryFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.20; 2 | 3 | import "tokens/eip20/EIP20.sol"; 4 | import "./ParameterizerFactory.sol"; 5 | import "./Registry.sol"; 6 | import "plcr-revival/PLCRVoting.sol"; 7 | import "./Parameterizer.sol"; 8 | 9 | contract RegistryFactory { 10 | 11 | event NewRegistry(address creator, EIP20 token, PLCRVoting plcr, Parameterizer parameterizer, Registry registry); 12 | 13 | ParameterizerFactory public parameterizerFactory; 14 | ProxyFactory public proxyFactory; 15 | Registry public canonizedRegistry; 16 | 17 | /// @dev constructor deploys a new proxyFactory. 18 | constructor(ParameterizerFactory _parameterizerFactory) public { 19 | parameterizerFactory = _parameterizerFactory; 20 | proxyFactory = parameterizerFactory.proxyFactory(); 21 | canonizedRegistry = new Registry(); 22 | } 23 | 24 | /* 25 | @dev deploys and initializes a new Registry 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 Registry contract 28 | */ 29 | function newRegistryBYOToken( 30 | EIP20 _token, 31 | uint[] _parameters, 32 | string _name 33 | ) public returns (Registry) { 34 | Parameterizer parameterizer = parameterizerFactory.newParameterizerBYOToken(_token, _parameters); 35 | PLCRVoting plcr = parameterizer.voting(); 36 | 37 | Registry registry = Registry(proxyFactory.createProxy(canonizedRegistry, "")); 38 | registry.init(_token, plcr, parameterizer, _name); 39 | 40 | emit NewRegistry(msg.sender, _token, plcr, parameterizer, registry); 41 | return registry; 42 | } 43 | 44 | /* 45 | @dev deploys and initializes a new Registry contract, an EIP20, a PLCRVoting, and Parameterizer 46 | to be consumed by the Registry's initializer. 47 | @param _supply the total number of tokens to mint in the EIP20 contract 48 | @param _name the name of the new EIP20 token 49 | @param _decimals the decimal precision to be used in rendering balances in the EIP20 token 50 | @param _symbol the symbol of the new EIP20 token 51 | */ 52 | function newRegistryWithToken( 53 | uint _supply, 54 | string _tokenName, 55 | uint8 _decimals, 56 | string _symbol, 57 | uint[] _parameters, 58 | string _registryName 59 | ) public returns (Registry) { 60 | // Creates a new EIP20 token & transfers the supply to creator (msg.sender) 61 | // Deploys & initializes (1) PLCRVoting contract & (2) Parameterizer contract 62 | Parameterizer parameterizer = parameterizerFactory.newParameterizerWithToken(_supply, _tokenName, _decimals, _symbol, _parameters); 63 | EIP20 token = EIP20(parameterizer.token()); 64 | token.transfer(msg.sender, _supply); 65 | PLCRVoting plcr = parameterizer.voting(); 66 | 67 | // Create & initialize a new Registry contract 68 | Registry registry = Registry(proxyFactory.createProxy(canonizedRegistry, "")); 69 | registry.init(token, plcr, parameterizer, _registryName); 70 | 71 | emit NewRegistry(msg.sender, token, plcr, parameterizer, registry); 72 | return registry; 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /contracts/ParameterizerFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.20; 2 | 3 | import "plcr-revival/PLCRFactory.sol"; 4 | import "plcr-revival/PLCRVoting.sol"; 5 | import "./Parameterizer.sol"; 6 | import "tokens/eip20/EIP20.sol"; 7 | 8 | contract ParameterizerFactory { 9 | 10 | event NewParameterizer(address creator, address token, address plcr, Parameterizer parameterizer); 11 | 12 | PLCRFactory public plcrFactory; 13 | ProxyFactory public proxyFactory; 14 | Parameterizer public canonizedParameterizer; 15 | 16 | /// @dev constructor deploys a new canonical Parameterizer contract and a proxyFactory. 17 | constructor(PLCRFactory _plcrFactory) public { 18 | plcrFactory = _plcrFactory; 19 | proxyFactory = plcrFactory.proxyFactory(); 20 | canonizedParameterizer = new Parameterizer(); 21 | } 22 | 23 | /* 24 | @dev deploys and initializes a new Parameterizer contract that consumes a token at an address 25 | supplied by the user. 26 | @param _token an EIP20 token to be consumed by the new Parameterizer contract 27 | @param _plcr a PLCR voting contract to be consumed by the new Parameterizer contract 28 | @param _parameters array of canonical parameters 29 | */ 30 | function newParameterizerBYOToken( 31 | EIP20 _token, 32 | uint[] _parameters 33 | ) public returns (Parameterizer) { 34 | PLCRVoting plcr = plcrFactory.newPLCRBYOToken(_token); 35 | Parameterizer parameterizer = Parameterizer(proxyFactory.createProxy(canonizedParameterizer, "")); 36 | 37 | parameterizer.init( 38 | _token, 39 | plcr, 40 | _parameters 41 | ); 42 | emit NewParameterizer(msg.sender, _token, plcr, parameterizer); 43 | return parameterizer; 44 | } 45 | 46 | /* 47 | @dev deploys and initializes new EIP20, PLCRVoting, and Parameterizer contracts 48 | @param _supply the total number of tokens to mint in the EIP20 contract 49 | @param _name the name of the new EIP20 token 50 | @param _decimals the decimal precision to be used in rendering balances in the EIP20 token 51 | @param _symbol the symbol of the new EIP20 token 52 | @param _parameters array of canonical parameters 53 | */ 54 | function newParameterizerWithToken( 55 | uint _supply, 56 | string _name, 57 | uint8 _decimals, 58 | string _symbol, 59 | uint[] _parameters 60 | ) public returns (Parameterizer) { 61 | // Creates a new EIP20 token & transfers the supply to creator (msg.sender) 62 | // Deploys & initializes a new PLCRVoting contract 63 | PLCRVoting plcr = plcrFactory.newPLCRWithToken(_supply, _name, _decimals, _symbol); 64 | EIP20 token = EIP20(plcr.token()); 65 | token.transfer(msg.sender, _supply); 66 | 67 | // Create & initialize a new Parameterizer contract 68 | Parameterizer parameterizer = Parameterizer(proxyFactory.createProxy(canonizedParameterizer, "")); 69 | parameterizer.init( 70 | token, 71 | plcr, 72 | _parameters 73 | ); 74 | 75 | emit NewParameterizer(msg.sender, token, plcr, parameterizer); 76 | return parameterizer; 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /test/parameterizer/tokenClaims.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../utils'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | contract('Parameterizer', (accounts) => { 10 | describe('Function: tokenClaims', () => { 11 | const [proposer, challenger, alice] = accounts; 12 | 13 | let token; 14 | let voting; 15 | let parameterizer; 16 | 17 | beforeEach(async () => { 18 | const { votingProxy, paramProxy, tokenInstance } = await utils.getProxies(token); 19 | voting = votingProxy; 20 | parameterizer = paramProxy; 21 | token = tokenInstance; 22 | 23 | await utils.approveProxies(accounts, token, voting, parameterizer, false); 24 | }); 25 | 26 | it('should return false if voter tokens have not been claimed yet.', async () => { 27 | // Make a proposal to change the voteQuorum param to 51, and grab the proposal ID 28 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 29 | const { propID } = proposalReceipt.logs[0].args; 30 | 31 | // Challenge the proposal, and grab the challenge ID 32 | const challengeReceipt = 33 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 34 | const { challengeID } = challengeReceipt.logs[0].args; 35 | 36 | // Commit 10 tokens in support of the proposal, and finish the commit stage 37 | await utils.commitVote(challengeID, '1', '10', '420', alice, voting); 38 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 39 | 40 | // Reveal the supporting vote, and finish the reveal stage 41 | await utils.as(alice, voting.revealVote, challengeID, '1', '420'); 42 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 43 | 44 | // Process the proposal 45 | await parameterizer.processProposal(propID); 46 | 47 | const result = await parameterizer.tokenClaims.call(challengeID, alice); 48 | assert.strictEqual( 49 | result, false, 50 | 'tokenClaims returned true for a voter who has not claimed tokens yet', 51 | ); 52 | }); 53 | 54 | it('should return true if voter tokens have been claimed.', async () => { 55 | // Make a proposal to change the voteQuorum param to 52, and grab the proposal ID 56 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '52'); 57 | const { propID } = proposalReceipt.logs[0].args; 58 | 59 | // Challenge the proposal, and grab the challenge ID 60 | const challengeReceipt = 61 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 62 | const { challengeID } = challengeReceipt.logs[0].args; 63 | 64 | // Commit 10 tokens in support of the proposal, and finish the commit stage 65 | await utils.commitVote(challengeID, '1', '10', '420', alice, voting); 66 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 67 | 68 | // Reveal the suypporting vote, and finish the reveal stage 69 | await utils.as(alice, voting.revealVote, challengeID, '1', '420'); 70 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 71 | 72 | // Process the proposal and claim a reward 73 | await parameterizer.processProposal(propID); 74 | await utils.as(alice, parameterizer.claimReward, challengeID); 75 | 76 | const result = await parameterizer.tokenClaims.call(challengeID, alice); 77 | assert.strictEqual( 78 | result, true, 79 | 'tokenClaims returned false for a voter who has claimed tokens already', 80 | ); 81 | }); 82 | }); 83 | }); 84 | 85 | -------------------------------------------------------------------------------- /scripts/deploy_proxies.js: -------------------------------------------------------------------------------- 1 | /* global artifacts web3 */ 2 | const fs = require('fs'); 3 | const BN = require('bignumber.js'); 4 | 5 | const RegistryFactory = artifacts.require('RegistryFactory.sol'); 6 | const Registry = artifacts.require('Registry.sol'); 7 | const Token = artifacts.require('EIP20.sol'); 8 | 9 | const config = JSON.parse(fs.readFileSync('../conf/config.json')); 10 | const paramConfig = config.paramDefaults; 11 | 12 | module.exports = (done) => { 13 | async function deployProxies(networkID) { 14 | let registryFactoryAddress; 15 | if (networkID === '1') { 16 | registryFactoryAddress = '0xcc0df91b86795f21c3d43dbeb3ede0dfcf8dccaf'; // mainnet 17 | } else if (networkID === '4') { 18 | registryFactoryAddress = '0x2bddfc0c506a00ea3a6ccea5fbbda8843377dcb1'; // rinkeby 19 | } else { 20 | registryFactoryAddress = RegistryFactory.address; // development 21 | } 22 | 23 | /* eslint-disable no-console */ 24 | console.log('Using RegistryFactory at:'); 25 | console.log(` ${registryFactoryAddress}`); 26 | console.log(''); 27 | console.log('Deploying proxy contracts...'); 28 | console.log('...'); 29 | /* eslint-enable no-console */ 30 | 31 | const registryFactory = await RegistryFactory.at(registryFactoryAddress); 32 | const registryReceipt = await registryFactory.newRegistryWithToken( 33 | config.token.supply, 34 | config.token.name, 35 | config.token.decimals, 36 | config.token.symbol, 37 | [ 38 | paramConfig.minDeposit, 39 | paramConfig.pMinDeposit, 40 | paramConfig.applyStageLength, 41 | paramConfig.pApplyStageLength, 42 | paramConfig.commitStageLength, 43 | paramConfig.pCommitStageLength, 44 | paramConfig.revealStageLength, 45 | paramConfig.pRevealStageLength, 46 | paramConfig.dispensationPct, 47 | paramConfig.pDispensationPct, 48 | paramConfig.voteQuorum, 49 | paramConfig.pVoteQuorum, 50 | paramConfig.exitTimeDelay, 51 | paramConfig.exitPeriodLen, 52 | ], 53 | config.name, 54 | ); 55 | 56 | const { 57 | token, 58 | plcr, 59 | parameterizer, 60 | registry, 61 | } = registryReceipt.logs[0].args; 62 | 63 | const registryProxy = await Registry.at(registry); 64 | const tokenProxy = await Token.at(token); 65 | const registryName = await registryProxy.name.call(); 66 | 67 | /* eslint-disable no-console */ 68 | console.log(`Proxy contracts successfully migrated to network_id: ${networkID}`); 69 | console.log(''); 70 | console.log(`${config.token.name} (EIP20):`); 71 | console.log(` ${token}`); 72 | console.log('PLCRVoting:'); 73 | console.log(` ${plcr}`); 74 | console.log('Parameterizer:'); 75 | console.log(` ${parameterizer}`); 76 | console.log(`${registryName} (Registry):`); 77 | console.log(` ${registry}`); 78 | console.log(''); 79 | 80 | const evenTokenDispensation = 81 | new BN(config.token.supply).div(config.token.tokenHolders.length).toString(); 82 | console.log(`Dispensing ${config.token.supply} tokens evenly to ${config.token.tokenHolders.length} addresses:`); 83 | console.log(''); 84 | 85 | await Promise.all(config.token.tokenHolders.map(async (account) => { 86 | console.log(`Transferring tokens to address: ${account}`); 87 | return tokenProxy.transfer(account, evenTokenDispensation); 88 | })); 89 | /* eslint-enable no-console */ 90 | 91 | return true; 92 | } 93 | 94 | // web3 requires callback syntax. silly! 95 | web3.version.getNetwork((err, network) => { 96 | if (err) { 97 | return done(err); // truffle exec exits if an error gets returned 98 | } 99 | return deployProxies(network).then(() => done()); 100 | }); 101 | }; 102 | -------------------------------------------------------------------------------- /test/registry/userStories.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('User stories', () => { 15 | const [applicant, challenger, voter] = accounts; 16 | const minDeposit = bigTen(paramConfig.minDeposit); 17 | 18 | let token; 19 | let voting; 20 | let registry; 21 | 22 | beforeEach(async () => { 23 | const { votingProxy, registryProxy, tokenInstance } = await utils.getProxies(); 24 | voting = votingProxy; 25 | registry = registryProxy; 26 | token = tokenInstance; 27 | 28 | await utils.approveProxies(accounts, token, voting, false, registry); 29 | }); 30 | 31 | it('should apply, fail challenge, and reject listing', async () => { 32 | const listing = utils.getListingHash('failChallenge.net'); // listing to apply with 33 | await registry.apply(listing, paramConfig.minDeposit, '', { from: applicant }); 34 | await registry.challenge(listing, '', { from: challenger }); 35 | 36 | await utils.increaseTime(paramConfig.revealStageLength + paramConfig.commitStageLength + 1); 37 | await registry.updateStatus(listing); 38 | 39 | // should not have been added to whitelist 40 | const result = await registry.isWhitelisted(listing); 41 | assert.strictEqual(result, false, 'listing should not be whitelisted'); 42 | }); 43 | 44 | it('should apply, pass challenge, and whitelist listing', async () => { 45 | const listing = utils.getListingHash('passChallenge.net'); 46 | 47 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 48 | 49 | // Challenge and get back the pollID 50 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 51 | 52 | // Make sure it's cool to commit 53 | const cpa = await voting.commitPeriodActive.call(pollID); 54 | assert.strictEqual(cpa, true, 'Commit period should be active'); 55 | 56 | // Virgin commit 57 | const tokensArg = 10; 58 | const salt = 420; 59 | const voteOption = 1; 60 | await utils.commitVote(pollID, voteOption, tokensArg, salt, voter, voting); 61 | 62 | const numTokens = await voting.getNumTokens.call(voter, pollID); 63 | assert.strictEqual(numTokens.toString(10), tokensArg.toString(10), 'Should have committed the correct number of tokens'); 64 | 65 | // Reveal 66 | await utils.increaseTime(paramConfig.commitStageLength + 1); 67 | // Make sure commit period is inactive 68 | const commitPeriodActive = await voting.commitPeriodActive.call(pollID); 69 | assert.strictEqual(commitPeriodActive, false, 'Commit period should be inactive'); 70 | // Make sure reveal period is active 71 | let rpa = await voting.revealPeriodActive.call(pollID); 72 | assert.strictEqual(rpa, true, 'Reveal period should be active'); 73 | 74 | await voting.revealVote(pollID, voteOption, salt, { from: voter }); 75 | 76 | // End reveal period 77 | await utils.increaseTime(paramConfig.revealStageLength + 1); 78 | rpa = await voting.revealPeriodActive.call(pollID); 79 | assert.strictEqual(rpa, false, 'Reveal period should not be active'); 80 | 81 | // updateStatus 82 | const pollResult = await voting.isPassed.call(pollID); 83 | assert.strictEqual(pollResult, true, 'Poll should have passed'); 84 | 85 | // Add to whitelist 86 | await registry.updateStatus(listing); 87 | const result = await registry.isWhitelisted(listing); 88 | assert.strictEqual(result, true, 'Listing should be whitelisted'); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/registry/deposit.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: deposit', () => { 15 | const minDeposit = bigTen(paramConfig.minDeposit); 16 | const incAmount = minDeposit.div(bigTen(2)); 17 | const [applicant, challenger] = accounts; 18 | 19 | let token; 20 | let registry; 21 | 22 | beforeEach(async () => { 23 | const { registryProxy, tokenInstance } = await utils.getProxies(); 24 | registry = registryProxy; 25 | token = tokenInstance; 26 | 27 | await utils.approveProxies(accounts, token, false, false, registry); 28 | }); 29 | 30 | it('should increase the deposit for a specific listing in the listing', async () => { 31 | const listing = utils.getListingHash('specificlisting.net'); 32 | 33 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 34 | await utils.as(applicant, registry.deposit, listing, incAmount); 35 | 36 | const unstakedDeposit = await utils.getUnstakedDeposit(listing, registry); 37 | const expectedAmount = incAmount.add(minDeposit); 38 | assert.strictEqual( 39 | unstakedDeposit, expectedAmount.toString(10), 40 | 'Unstaked deposit should be equal to the sum of the original + increase amount', 41 | ); 42 | }); 43 | 44 | it('should increase a deposit for a pending application', async () => { 45 | const listing = utils.getListingHash('pendinglisting.net'); 46 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 47 | 48 | try { 49 | await utils.as(applicant, registry.deposit, listing, incAmount); 50 | 51 | const unstakedDeposit = await utils.getUnstakedDeposit(listing, registry); 52 | const expectedAmount = incAmount.add(minDeposit); 53 | assert.strictEqual(unstakedDeposit, expectedAmount.toString(10), 'Deposit should have increased for pending application'); 54 | } catch (err) { 55 | const errMsg = err.toString(); 56 | assert(utils.isEVMException(err), errMsg); 57 | } 58 | }); 59 | 60 | it('should increase deposit for a whitelisted, challenged listing', async () => { 61 | const listing = utils.getListingHash('challengelisting.net'); 62 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 63 | const originalDeposit = await utils.getUnstakedDeposit(listing, registry); 64 | 65 | // challenge, then increase deposit 66 | await utils.as(challenger, registry.challenge, listing, ''); 67 | await utils.as(applicant, registry.deposit, listing, incAmount); 68 | 69 | const afterIncDeposit = await utils.getUnstakedDeposit(listing, registry); 70 | 71 | const expectedAmount = ( 72 | bigTen(originalDeposit).add(bigTen(incAmount)) 73 | ).sub(bigTen(minDeposit)); 74 | 75 | assert.strictEqual(afterIncDeposit, expectedAmount.toString(10), 'Deposit should have increased for whitelisted, challenged listing'); 76 | }); 77 | 78 | it('should not increase deposit for a listing not owned by the msg.sender', async () => { 79 | const listing = utils.getListingHash('notowner.com'); 80 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 81 | 82 | try { 83 | await utils.as(challenger, registry.deposit, listing, incAmount); 84 | assert(false, 'Deposit should not have increased when sent by the wrong msg.sender'); 85 | } catch (err) { 86 | assert(utils.isEVMException(err), err.toString()); 87 | } 88 | }); 89 | 90 | it('should revert if token transfer from user fails', async () => { 91 | const listing = utils.getListingHash('notEnoughTokens.net'); 92 | 93 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 94 | 95 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 96 | await token.approve(registry.address, '0', { from: applicant }); 97 | 98 | try { 99 | await utils.as(applicant, registry.deposit, listing, '1'); 100 | } catch (err) { 101 | assert(utils.isEVMException(err), err.toString()); 102 | return; 103 | } 104 | assert(false, 'allowed deposit with not enough tokens'); 105 | }); 106 | }); 107 | }); 108 | 109 | -------------------------------------------------------------------------------- /test/registry/updateStatuses.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: updateStatuses', () => { 15 | const [applicant, challenger] = accounts; 16 | const minDeposit = bigTen(paramConfig.minDeposit); 17 | 18 | let token; 19 | let registry; 20 | 21 | beforeEach(async () => { 22 | const { registryProxy, tokenInstance } = await utils.getProxies(); 23 | registry = registryProxy; 24 | token = tokenInstance; 25 | 26 | await utils.approveProxies(accounts, token, false, false, registry); 27 | }); 28 | 29 | it('should whitelist an array of 1 listing', async () => { 30 | const listing = utils.getListingHash('whitelistmepls.io'); 31 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 32 | 33 | await utils.increaseTime(paramConfig.applyStageLength + 1); 34 | 35 | const listings = [listing]; 36 | await utils.as(applicant, registry.updateStatuses, listings); 37 | const wl = await registry.listings.call(listing); 38 | assert.strictEqual(wl[1], true, 'should have been whitelisted'); 39 | }); 40 | 41 | it('should whitelist an array of 2 listings', async () => { 42 | const listing1 = utils.getListingHash('whitelistus1.io'); 43 | const listing2 = utils.getListingHash('whitelistus2.io'); 44 | await utils.as(applicant, registry.apply, listing1, minDeposit, ''); 45 | await utils.as(applicant, registry.apply, listing2, minDeposit, ''); 46 | 47 | await utils.increaseTime(paramConfig.applyStageLength + 1); 48 | 49 | const listings = [listing1, listing2]; 50 | await utils.as(applicant, registry.updateStatuses, listings); 51 | const wl1 = await registry.listings.call(listing1); 52 | assert.strictEqual(wl1[1], true, 'listing 1 should have been whitelisted'); 53 | const wl2 = await registry.listings.call(listing2); 54 | assert.strictEqual(wl2[1], true, 'listing 2 should have been whitelisted'); 55 | }); 56 | 57 | it('should not whitelist an array of 1 listing that is still pending an application', async () => { 58 | const listing = utils.getListingHash('tooearlybuddy.io'); 59 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 60 | 61 | const listings = [listing]; 62 | try { 63 | await utils.as(applicant, registry.updateStatuses, listings); 64 | } catch (err) { 65 | assert(utils.isEVMException(err), err.toString()); 66 | return; 67 | } 68 | assert(false, 'Listing should not have been whitelisted'); 69 | }); 70 | 71 | it('should not whitelist a listing that is currently being challenged', async () => { 72 | const listing = utils.getListingHash('dontwhitelist.io'); 73 | 74 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 75 | await utils.as(challenger, registry.challenge, listing, ''); 76 | 77 | const listings = [listing]; 78 | try { 79 | await registry.updateStatuses(listings); 80 | } catch (err) { 81 | assert(utils.isEVMException(err), err.toString()); 82 | return; 83 | } 84 | assert(false, 'Listing should not have been whitelisted'); 85 | }); 86 | 87 | it('should not whitelist an array of 1 listing that failed a challenge', async () => { 88 | const listing = utils.getListingHash('dontwhitelist.net'); 89 | 90 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 91 | await utils.as(challenger, registry.challenge, listing, ''); 92 | 93 | const plcrComplete = paramConfig.revealStageLength + paramConfig.commitStageLength + 1; 94 | await utils.increaseTime(plcrComplete); 95 | 96 | const listings = [listing]; 97 | await registry.updateStatuses(listings); 98 | const result = await registry.isWhitelisted(listing); 99 | assert.strictEqual(result, false, 'Listing should not have been whitelisted'); 100 | }); 101 | 102 | it('should not be possible to add an array of 1 listing to the whitelist just by calling updateStatuses', async () => { 103 | const listing = utils.getListingHash('updatemenow.net'); 104 | 105 | try { 106 | const listings = [listing]; 107 | await utils.as(applicant, registry.updateStatuses, listings); 108 | } catch (err) { 109 | assert(utils.isEVMException(err), err.toString()); 110 | return; 111 | } 112 | assert(false, 'Listing should not have been whitelisted'); 113 | }); 114 | }); 115 | }); 116 | 117 | -------------------------------------------------------------------------------- /test/registry/updateStatus.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: updateStatus', () => { 15 | const [applicant, challenger] = accounts; 16 | const minDeposit = bigTen(paramConfig.minDeposit); 17 | 18 | let token; 19 | let registry; 20 | 21 | beforeEach(async () => { 22 | const { registryProxy, tokenInstance } = await utils.getProxies(); 23 | registry = registryProxy; 24 | token = tokenInstance; 25 | 26 | await utils.approveProxies(accounts, token, false, false, registry); 27 | }); 28 | 29 | it('should whitelist listing if apply stage ended without a challenge', async () => { 30 | const listing = utils.getListingHash('whitelist.io'); 31 | // note: this function calls registry.updateStatus at the end 32 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 33 | 34 | const result = await registry.isWhitelisted.call(listing); 35 | assert.strictEqual(result, true, 'Listing should have been whitelisted'); 36 | }); 37 | 38 | it('should not whitelist a listing that is still pending an application', async () => { 39 | const listing = utils.getListingHash('tooearlybuddy.io'); 40 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 41 | 42 | try { 43 | await utils.as(applicant, registry.updateStatus, listing); 44 | } catch (err) { 45 | assert(utils.isEVMException(err), err.toString()); 46 | return; 47 | } 48 | assert(false, 'Listing should not have been whitelisted'); 49 | }); 50 | 51 | it('should not whitelist a listing that is currently being challenged', async () => { 52 | const listing = utils.getListingHash('dontwhitelist.io'); 53 | 54 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 55 | await utils.as(challenger, registry.challenge, listing, ''); 56 | 57 | try { 58 | await registry.updateStatus(listing); 59 | } catch (err) { 60 | assert(utils.isEVMException(err), err.toString()); 61 | return; 62 | } 63 | assert(false, 'Listing should not have been whitelisted'); 64 | }); 65 | 66 | it('should not whitelist a listing that failed a challenge', async () => { 67 | const listing = utils.getListingHash('dontwhitelist.net'); 68 | 69 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 70 | await utils.as(challenger, registry.challenge, listing, ''); 71 | 72 | const plcrComplete = paramConfig.revealStageLength + paramConfig.commitStageLength + 1; 73 | await utils.increaseTime(plcrComplete); 74 | 75 | await registry.updateStatus(listing); 76 | const result = await registry.isWhitelisted(listing); 77 | assert.strictEqual(result, false, 'Listing should not have been whitelisted'); 78 | }); 79 | 80 | it('should not be possible to add a listing to the whitelist just by calling updateStatus', async () => { 81 | const listing = utils.getListingHash('updatemenow.net'); 82 | 83 | try { 84 | await utils.as(applicant, registry.updateStatus, listing); 85 | } catch (err) { 86 | assert(utils.isEVMException(err), err.toString()); 87 | return; 88 | } 89 | assert(false, 'Listing should not have been whitelisted'); 90 | }); 91 | 92 | it('should not be possible to add a listing to the whitelist just by calling updateStatus after it has been previously removed', async () => { 93 | const listing = utils.getListingHash('somanypossibilities.net'); 94 | 95 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 96 | const resultOne = await registry.isWhitelisted(listing); 97 | assert.strictEqual(resultOne, true, 'Listing should have been whitelisted'); 98 | await registry.initExit(listing, { from: applicant }); 99 | await utils.increaseTime(paramConfig.exitTimeDelay + 1); 100 | await registry.finalizeExit(listing, { from: applicant }); 101 | // await utils.as(applicant, registry.exit, listing); 102 | const resultTwo = await registry.isWhitelisted(listing); 103 | assert.strictEqual(resultTwo, false, 'Listing should not be in the whitelist'); 104 | 105 | try { 106 | await utils.as(applicant, registry.updateStatus, listing); 107 | } catch (err) { 108 | assert(utils.isEVMException(err), err.toString()); 109 | return; 110 | } 111 | assert(false, 'Listing should not have been whitelisted'); 112 | }); 113 | }); 114 | }); 115 | 116 | -------------------------------------------------------------------------------- /test/registry/withdraw.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: withdraw', () => { 15 | const minDeposit = bigTen(paramConfig.minDeposit); 16 | const withdrawAmount = minDeposit.div(bigTen(2)); 17 | const [applicant, challenger] = accounts; 18 | 19 | let token; 20 | let registry; 21 | 22 | beforeEach(async () => { 23 | const { registryProxy, tokenInstance } = await utils.getProxies(); 24 | registry = registryProxy; 25 | token = tokenInstance; 26 | 27 | await utils.approveProxies(accounts, token, false, false, registry); 28 | }); 29 | 30 | it('should not withdraw tokens from a listing that has a deposit === minDeposit', async () => { 31 | const dontChallengeListing = 'dontchallenge.net'; 32 | const errMsg = 'applicant was able to withdraw tokens'; 33 | 34 | await utils.addToWhitelist(dontChallengeListing, minDeposit, applicant, registry); 35 | const origDeposit = await utils.getUnstakedDeposit(dontChallengeListing, registry); 36 | 37 | try { 38 | await utils.as(applicant, registry.withdraw, dontChallengeListing, withdrawAmount); 39 | assert(false, errMsg); 40 | } catch (err) { 41 | assert(utils.isEVMException(err), err.toString()); 42 | } 43 | 44 | const afterWithdrawDeposit = await utils.getUnstakedDeposit(dontChallengeListing, registry); 45 | 46 | assert.strictEqual(afterWithdrawDeposit.toString(10), origDeposit.toString(10), errMsg); 47 | }); 48 | 49 | it('should not withdraw tokens where the amount is less than twice the minDeposit and the listing is locked in ' + 50 | 'a challenge', async () => { 51 | const listing = utils.getListingHash('shouldntwithdraw.net'); 52 | 53 | const deposit = minDeposit.plus(bigTen(1)); 54 | 55 | // Whitelist, then challenge 56 | await utils.addToWhitelist(listing, deposit, applicant, registry); 57 | await utils.as(challenger, registry.challenge, listing, ''); 58 | 59 | try { 60 | // Attempt to withdraw; should fail 61 | await utils.as(applicant, registry.withdraw, listing, '1'); 62 | } catch (err) { 63 | assert(utils.isEVMException(err), err.toString()); 64 | return; 65 | } 66 | assert.strictEqual(false, 'Applicant should not have been able to withdraw from a challenged, locked listing'); 67 | // TODO: check balance 68 | // TODO: apply, gets challenged, and then minDeposit lowers during challenge. 69 | // still shouldn't be able to withdraw anything. 70 | // when challenge ends, should be able to withdraw origDeposit - new minDeposit 71 | }); 72 | 73 | it('should revert if the message sender is not the owner of the application/listing', async () => { 74 | const listing = utils.getListingHash('challengerWithdraw.net'); 75 | 76 | const deposit = minDeposit.plus(bigTen(1)); 77 | 78 | // Whitelist 79 | await utils.addToWhitelist(listing, deposit, applicant, registry); 80 | 81 | try { 82 | // Attempt to withdraw; should fail 83 | await utils.as(challenger, registry.withdraw, listing, '1'); 84 | } catch (err) { 85 | assert(utils.isEVMException(err), err.toString()); 86 | return; 87 | } 88 | assert(false, 'non-owner should not be able to withdraw from listing.'); 89 | }); 90 | 91 | it('should allow listing owner to withdraw and decrease the UnstakedDeposit while there is not a challenge', async () => { 92 | const listing = utils.getListingHash('ITWORKS.net'); 93 | 94 | const deposit = minDeposit.plus(bigTen(1)); 95 | 96 | // Whitelist 97 | await utils.addToWhitelist(listing, deposit, applicant, registry); 98 | 99 | await utils.as(applicant, registry.withdraw, listing, '1'); 100 | 101 | const afterWithdrawDeposit = await utils.getUnstakedDeposit(listing, registry); 102 | 103 | assert.strictEqual(minDeposit.toString(), afterWithdrawDeposit.toString(), `UnstakedDeposit should be ${minDeposit.toString()}`); 104 | }); 105 | 106 | it('should not allow withdrawal greater than UnstakedDeposit', async () => { 107 | const listing = utils.getListingHash('moreThanIOwn.net'); 108 | 109 | // calculate the amount to withdraw: greater than the unstaked deposit 110 | const unstakedDeposit = await utils.getUnstakedDeposit(listing, registry); 111 | const withdrawGreaterAmount = new BN(unstakedDeposit, 10).plus('1'); 112 | 113 | // Whitelist 114 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 115 | try { 116 | await utils.as(applicant, registry.withdraw, listing, withdrawGreaterAmount.toString()); 117 | } catch (err) { 118 | assert(utils.isEVMException(err), err.toString()); 119 | return; 120 | } 121 | assert(false, 'withdrew more than the UnstakedDeposit'); 122 | }); 123 | }); 124 | }); 125 | 126 | -------------------------------------------------------------------------------- /test/parameterizer/proposeReparameterization.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bn.js'); 5 | const utils = require('../utils'); 6 | 7 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 8 | const paramConfig = config.paramDefaults; 9 | 10 | const bigTen = number => new BN(number.toString(10), 10); 11 | 12 | contract('Parameterizer', (accounts) => { 13 | describe('Function: proposeReparameterization', () => { 14 | const [proposer, secondProposer] = accounts; 15 | const pMinDeposit = bigTen(paramConfig.pMinDeposit); 16 | 17 | let token; 18 | let parameterizer; 19 | 20 | beforeEach(async () => { 21 | const { paramProxy, tokenInstance } = await utils.getProxies(); 22 | parameterizer = paramProxy; 23 | token = tokenInstance; 24 | 25 | await utils.approveProxies(accounts, token, false, parameterizer, false); 26 | }); 27 | 28 | // Put this first to ensure test does not conflict with proposals already made. 29 | it('should not allow a NOOP reparameterization', async () => { 30 | // Get value to be reparameterized. 31 | const voteQuorum = await parameterizer.get.call('voteQuorum'); 32 | 33 | try { 34 | await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', voteQuorum.toString()); 35 | } catch (err) { 36 | assert(utils.isEVMException(err), err.toString()); 37 | return; 38 | } 39 | assert(false, 'Performed NOOP reparameterization'); 40 | }); 41 | 42 | 43 | it('should revert on proposals for dispensationPct and pDispensationPct with values greater ' + 44 | 'than 100', async () => { 45 | const BAD_VALUE = '101'; 46 | 47 | // Try to propose bad values for both dispensationPct and pDispensationPct. Expect both to 48 | // revert. 49 | try { 50 | await utils.as( 51 | proposer, parameterizer.proposeReparameterization, 'dispensationPct', 52 | BAD_VALUE, 53 | ); 54 | } catch (errOne) { 55 | assert(utils.isEVMException(errOne), errOne.toString()); 56 | 57 | try { 58 | await utils.as( 59 | proposer, parameterizer.proposeReparameterization, 'pDispensationPct', 60 | BAD_VALUE, 61 | ); 62 | } catch (errTwo) { 63 | assert(utils.isEVMException(errTwo), errTwo.toString()); 64 | 65 | return; 66 | } 67 | } 68 | 69 | assert(false, 'One of the bad proposals was accepted'); 70 | }); 71 | 72 | it('should add a new reparameterization proposal', async () => { 73 | const applicantStartingBalance = await token.balanceOf.call(proposer); 74 | 75 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 76 | 77 | const propID = utils.getReceiptValue(receipt, 'propID'); 78 | const paramProposal = await parameterizer.proposals.call(propID); 79 | 80 | assert.strictEqual(paramProposal[6].toString(10), '51', 'The reparameterization proposal ' + 81 | 'was not created, or not created correctly.'); 82 | 83 | const applicantFinalBalance = await token.balanceOf.call(proposer); 84 | const expected = applicantStartingBalance.sub(pMinDeposit); 85 | assert.strictEqual( 86 | applicantFinalBalance.toString(10), expected.toString(10), 87 | 'tokens were not properly transferred from proposer', 88 | ); 89 | }); 90 | 91 | it('should not allow a reparameterization for a proposal that already exists', async () => { 92 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 93 | 94 | const propID = utils.getReceiptValue(receipt, 'propID'); 95 | const paramProposal = await parameterizer.proposals.call(propID); 96 | 97 | assert.strictEqual(paramProposal[6].toString(10), '51', 'The reparameterization proposal ' + 98 | 'was not created, or not created correctly.'); 99 | 100 | const applicantStartingBalance = await token.balanceOf.call(secondProposer); 101 | 102 | try { 103 | await utils.as(secondProposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 104 | assert(false, 'should not have been able to make duplicate proposal'); 105 | } catch (err) { 106 | assert(utils.isEVMException(err), err.toString()); 107 | } 108 | 109 | const applicantEndingBalance = await token.balanceOf.call(secondProposer); 110 | 111 | assert.strictEqual(applicantEndingBalance.toString(10), applicantStartingBalance.toString(10), 'starting balance and ' 112 | + 'ending balance should have been equal'); 113 | }); 114 | 115 | it('should revert if token transfer from user fails', async () => { 116 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 117 | await token.approve(parameterizer.address, '0', { from: secondProposer }); 118 | 119 | try { 120 | await utils.as(secondProposer, parameterizer.proposeReparameterization, 'voteQuorum', '89'); 121 | } catch (err) { 122 | assert(utils.isEVMException(err), err.toString()); 123 | return; 124 | } 125 | assert(false, 'allowed proposal with fewer tokens than minDeposit'); 126 | }); 127 | }); 128 | }); 129 | 130 | -------------------------------------------------------------------------------- /test/registry/claimRewards.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: claimRewards', () => { 15 | const [applicant, challenger, voterAlice] = accounts; 16 | const minDeposit = bigTen(paramConfig.minDeposit); 17 | 18 | let token; 19 | let voting; 20 | let registry; 21 | 22 | beforeEach(async () => { 23 | const { votingProxy, registryProxy, tokenInstance } = await utils.getProxies(); 24 | voting = votingProxy; 25 | registry = registryProxy; 26 | token = tokenInstance; 27 | 28 | await utils.approveProxies(accounts, token, voting, false, registry); 29 | }); 30 | 31 | it('should transfer the correct number of tokens once a challenge has been resolved', async () => { 32 | const listing = utils.getListingHash('claimthis.net'); 33 | 34 | // Apply 35 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 36 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 37 | 38 | // Challenge 39 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 40 | 41 | // Alice is so committed 42 | await utils.commitVote(pollID, '0', 500, '420', voterAlice, voting); 43 | await utils.increaseTime(paramConfig.commitStageLength + 1); 44 | 45 | // Alice is so revealing 46 | await utils.as(voterAlice, voting.revealVote, pollID, '0', '420'); 47 | await utils.increaseTime(paramConfig.revealStageLength + 1); 48 | 49 | // Update status 50 | await utils.as(applicant, registry.updateStatus, listing); 51 | 52 | const aliceVoterReward = await registry.voterReward.call(voterAlice, pollID); 53 | 54 | // Alice claims reward 55 | const pollIDs = [pollID]; 56 | await utils.as(voterAlice, registry.claimRewards, pollIDs); 57 | 58 | // Alice withdraws her voting rights 59 | await utils.as(voterAlice, voting.withdrawVotingRights, '500'); 60 | 61 | const aliceExpected = aliceStartingBalance.add(aliceVoterReward); 62 | const aliceFinalBalance = await token.balanceOf.call(voterAlice); 63 | 64 | assert.strictEqual( 65 | aliceFinalBalance.toString(10), aliceExpected.toString(10), 66 | 'alice should have more tokens than what she started with', 67 | ); 68 | }); 69 | 70 | it('should transfer an array of 3 rewards once a challenge has been resolved', async () => { 71 | const listing1 = utils.getListingHash('claimthis1.net'); 72 | const listing2 = utils.getListingHash('claimthis2.net'); 73 | const listing3 = utils.getListingHash('claimthis3.net'); 74 | 75 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 76 | 77 | // Apply 78 | await utils.as(applicant, registry.apply, listing1, minDeposit, ''); 79 | await utils.as(applicant, registry.apply, listing2, minDeposit, ''); 80 | await utils.as(applicant, registry.apply, listing3, minDeposit, ''); 81 | 82 | // Challenge 83 | const pollID1 = await utils.challengeAndGetPollID(listing1, challenger, registry); 84 | const pollID2 = await utils.challengeAndGetPollID(listing2, challenger, registry); 85 | const pollID3 = await utils.challengeAndGetPollID(listing3, challenger, registry); 86 | 87 | // Alice is so committed 88 | await utils.commitVote(pollID1, '0', 500, '420', voterAlice, voting); 89 | await utils.commitVote(pollID2, '0', 500, '420', voterAlice, voting); 90 | await utils.commitVote(pollID3, '0', 500, '420', voterAlice, voting); 91 | await utils.increaseTime(paramConfig.commitStageLength + 1); 92 | 93 | // Alice is so revealing 94 | await utils.as(voterAlice, voting.revealVote, pollID1, '0', '420'); 95 | await utils.as(voterAlice, voting.revealVote, pollID2, '0', '420'); 96 | await utils.as(voterAlice, voting.revealVote, pollID3, '0', '420'); 97 | await utils.increaseTime(paramConfig.revealStageLength + 1); 98 | 99 | // Update status 100 | await utils.as(applicant, registry.updateStatus, listing1); 101 | await utils.as(applicant, registry.updateStatus, listing2); 102 | await utils.as(applicant, registry.updateStatus, listing3); 103 | 104 | const aliceVoterReward1 = await registry.voterReward(voterAlice, pollID1); 105 | const aliceVoterReward2 = await registry.voterReward(voterAlice, pollID2); 106 | const aliceVoterReward3 = await registry.voterReward(voterAlice, pollID3); 107 | 108 | // Alice claims reward 109 | const pollIDs = [pollID1, pollID2, pollID3]; 110 | await utils.as(voterAlice, registry.claimRewards, pollIDs); 111 | 112 | // Alice withdraws her voting rights 113 | await utils.as(voterAlice, voting.withdrawVotingRights, '1500'); 114 | 115 | const aliceExpected = aliceStartingBalance 116 | .add(aliceVoterReward1).add(aliceVoterReward2).add(aliceVoterReward3); 117 | const aliceFinalBalance = await token.balanceOf.call(voterAlice); 118 | 119 | assert.strictEqual( 120 | aliceFinalBalance.toString(10), aliceExpected.toString(10), 121 | 'alice should have more tokens than what she started with', 122 | ); 123 | }); 124 | }); 125 | }); 126 | 127 | -------------------------------------------------------------------------------- /test/parameterizer/claimRewards.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../utils'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | contract('Parameterizer', (accounts) => { 10 | describe('Function: claimRewards', () => { 11 | const [proposer, challenger, voterAlice] = accounts; 12 | 13 | let token; 14 | let voting; 15 | let parameterizer; 16 | 17 | beforeEach(async () => { 18 | const { 19 | votingProxy, paramProxy, tokenInstance, 20 | } = await utils.getProxies(token); 21 | voting = votingProxy; 22 | parameterizer = paramProxy; 23 | token = tokenInstance; 24 | 25 | await utils.approveProxies(accounts, token, voting, parameterizer, false); 26 | }); 27 | 28 | it('should give the correct number of tokens to a voter on the winning side.', async () => { 29 | const voterAliceStartingBalance = await token.balanceOf.call(voterAlice); 30 | 31 | // propose reparam 32 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 33 | const { propID } = proposalReceipt.logs[0].args; 34 | 35 | // challenge reparam 36 | const challengeReceipt = 37 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 38 | const { challengeID } = challengeReceipt.logs[0].args; 39 | 40 | // commit vote 41 | await utils.commitVote(challengeID, '1', '10', '420', voterAlice, voting); 42 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 43 | 44 | // reveal vote 45 | await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); 46 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 47 | 48 | // process reparam 49 | await parameterizer.processProposal(propID); 50 | 51 | // array args 52 | const challengeIDs = [challengeID]; 53 | 54 | const aliceVoterReward = await parameterizer.voterReward.call(voterAlice, challengeID); 55 | 56 | // multi claimRewards, arrays as inputs 57 | await utils.as(voterAlice, parameterizer.claimRewards, challengeIDs); 58 | await utils.as(voterAlice, voting.withdrawVotingRights, '10'); 59 | 60 | // state assertion 61 | const voterAliceFinalBalance = await token.balanceOf.call(voterAlice); 62 | // expected = starting balance + voterReward 63 | const voterAliceExpected = voterAliceStartingBalance.add(aliceVoterReward); 64 | assert.strictEqual( 65 | voterAliceFinalBalance.toString(10), voterAliceExpected.toString(10), 66 | 'A voterAlice\'s token balance is not as expected after claiming a reward', 67 | ); 68 | }); 69 | 70 | it('should transfer an array of 3 rewards once a challenge has been resolved', async () => { 71 | const voterAliceStartingBalance = await token.balanceOf.call(voterAlice); 72 | 73 | // propose reparams 74 | const proposalReceipt1 = await utils.as(proposer, parameterizer.proposeReparameterization, 'pVoteQuorum', '51'); 75 | const proposalReceipt2 = await utils.as(proposer, parameterizer.proposeReparameterization, 'commitStageLen', '601'); 76 | const proposalReceipt3 = await utils.as(proposer, parameterizer.proposeReparameterization, 'applyStageLen', '601'); 77 | 78 | const propID1 = proposalReceipt1.logs[0].args.propID; 79 | const propID2 = proposalReceipt2.logs[0].args.propID; 80 | const propID3 = proposalReceipt3.logs[0].args.propID; 81 | 82 | // challenge reparams 83 | const challengeReceipt1 = 84 | await utils.as(challenger, parameterizer.challengeReparameterization, propID1); 85 | const challengeReceipt2 = 86 | await utils.as(challenger, parameterizer.challengeReparameterization, propID2); 87 | const challengeReceipt3 = 88 | await utils.as(challenger, parameterizer.challengeReparameterization, propID3); 89 | 90 | const challengeID1 = challengeReceipt1.logs[0].args.challengeID; 91 | const challengeID2 = challengeReceipt2.logs[0].args.challengeID; 92 | const challengeID3 = challengeReceipt3.logs[0].args.challengeID; 93 | 94 | // commit votes 95 | await utils.commitVote(challengeID1, '1', '10', '420', voterAlice, voting); 96 | await utils.commitVote(challengeID2, '1', '10', '420', voterAlice, voting); 97 | await utils.commitVote(challengeID3, '1', '10', '420', voterAlice, voting); 98 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 99 | 100 | // reveal votes 101 | await utils.as(voterAlice, voting.revealVote, challengeID1, '1', '420'); 102 | await utils.as(voterAlice, voting.revealVote, challengeID2, '1', '420'); 103 | await utils.as(voterAlice, voting.revealVote, challengeID3, '1', '420'); 104 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 105 | 106 | // process reparams 107 | await parameterizer.processProposal(propID1); 108 | await parameterizer.processProposal(propID2); 109 | await parameterizer.processProposal(propID3); 110 | 111 | // array args 112 | const challengeIDs = [challengeID1, challengeID2, challengeID3]; 113 | 114 | const aliceVoterReward1 = await parameterizer.voterReward.call(voterAlice, challengeID1); 115 | const aliceVoterReward2 = await parameterizer.voterReward.call(voterAlice, challengeID2); 116 | const aliceVoterReward3 = await parameterizer.voterReward.call(voterAlice, challengeID3); 117 | 118 | // multi claimRewards, arrays as inputs 119 | await utils.as(voterAlice, parameterizer.claimRewards, challengeIDs); 120 | await utils.as(voterAlice, voting.withdrawVotingRights, '30'); 121 | 122 | // state assertion 123 | const voterAliceFinalBalance = await token.balanceOf.call(voterAlice); 124 | // expected = starting balance + voterReward x3 125 | const voterAliceExpected = voterAliceStartingBalance 126 | .add(aliceVoterReward1).add(aliceVoterReward2).add(aliceVoterReward3); 127 | assert.strictEqual( 128 | voterAliceFinalBalance.toString(10), voterAliceExpected.toString(10), 129 | 'A voterAlice\'s token balance is not as expected after claiming a reward', 130 | ); 131 | }); 132 | }); 133 | }); 134 | 135 | -------------------------------------------------------------------------------- /test/registry/initExit.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | 5 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 6 | const paramConfig = config.paramDefaults; 7 | 8 | const utils = require('../utils.js'); 9 | const BigNumber = require('bignumber.js'); 10 | 11 | contract('Registry', (accounts) => { 12 | describe('Function: initExit', () => { 13 | const [applicant, challenger] = accounts; 14 | 15 | let token; 16 | let registry; 17 | 18 | beforeEach(async () => { 19 | const { registryProxy, tokenInstance } = await utils.getProxies(); 20 | registry = registryProxy; 21 | token = tokenInstance; 22 | 23 | await utils.approveProxies(accounts, token, false, false, registry); 24 | }); 25 | 26 | it('exitTimeDelay should be correctly set', async () => { 27 | // Adding an application to the whitelist 28 | const listing = utils.getListingHash('hangoutz.com'); 29 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 30 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 31 | const isWhitelisted = await registry.isWhitelisted.call(listing); 32 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 33 | 34 | await registry.initExit(listing, { from: applicant }); 35 | // blockTimestamp is used to calculate when the applicant's exit time is up 36 | const blockTimestamp = new BigNumber(await utils.getBlockTimestamp()); 37 | 38 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 39 | assert.strictEqual( 40 | finalApplicantTokenHoldings.toString(), 41 | initialApplicantTokenHoldings.sub(paramConfig.minDeposit).toString(), 42 | 'the applicant\'s tokens were returned in spite of failing to exit', 43 | ); 44 | // Make sure exitTimeDelay was correctly set 45 | const listingStruct = await registry.listings.call(listing); 46 | const exitTime = blockTimestamp.add(paramConfig.exitTimeDelay); 47 | assert.strictEqual(listingStruct[5].toString(), exitTime.toString(), 'exitTime was not set correctly'); 48 | 49 | // Make sure exitTimeExpiry was correctly set 50 | const exitTimeExpiry = exitTime.add(paramConfig.exitPeriodLen); 51 | assert.strictEqual(listingStruct[6].toString(), exitTimeExpiry.toString(), 'exitTimeExpiry was not initialized'); 52 | }); 53 | 54 | it('should not allow a listing to initialize an exit when a challenge exists', async () => { 55 | // Adding an application to the whitelist 56 | const listing = utils.getListingHash('420.com'); 57 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 58 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 59 | const isWhitelisted = await registry.isWhitelisted.call(listing); 60 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 61 | 62 | // Challenge a listing and then fail to initialize exit because of the challenge 63 | await registry.challenge(listing, '', { from: challenger }); 64 | try { 65 | await registry.initExit(listing, { from: applicant }); 66 | assert(false, 'exit succeeded when it should have failed due to an existing challenge'); 67 | } catch (err) { 68 | const errMsg = err.toString(); 69 | assert(utils.isEVMException(err), errMsg); 70 | } 71 | 72 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 73 | assert.strictEqual( 74 | isWhitelistedAfterExit, 75 | true, 76 | 'the listing was able to exit while a challenge was active', 77 | ); 78 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 79 | assert.strictEqual( 80 | finalApplicantTokenHoldings.toString(), 81 | initialApplicantTokenHoldings.sub(paramConfig.minDeposit).toString(), 82 | 'the applicant\'s tokens were returned in spite of failing to exit', 83 | ); 84 | // Make sure the listing did not successfully initialize exit 85 | const listingStruct = await registry.listings.call(listing); 86 | assert.strictEqual(listingStruct[5].toString(), '0', 'exitTimeDelay was initialized'); 87 | // Make sure exitTimeExpiry was correctly set 88 | assert.strictEqual(listingStruct[6].toString(), '0', 'exitTimeExpiry was initialized'); 89 | }); 90 | 91 | it('should not initialize an exit by someone who doesn\'t own the listing', async () => { 92 | // Adding an application to the whitelist 93 | const listing = utils.getListingHash('chilling.com'); 94 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 95 | 96 | try { 97 | // Another user (challenger) is trying initialize the applicant's exit 98 | await registry.initExit(listing, { from: challenger }); 99 | assert(false, 'exit initialized when the listing owner did not call initExit()'); 100 | } catch (err) { 101 | const errMsg = err.toString(); 102 | assert(utils.isEVMException(err), errMsg); 103 | } 104 | 105 | // Make sure the listing did not successfully initialize exit 106 | const listingStruct = await registry.listings.call(listing); 107 | assert.strictEqual(listingStruct[5].toString(), '0', 'exit time should not have been initialized'); 108 | }); 109 | 110 | it('should revert if listing is in application stage', async () => { 111 | const listing = utils.getListingHash('nogoodnames.com'); 112 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 113 | 114 | const initialListingStruct = await registry.listings.call(listing); 115 | // Getting blockTimestamp to prove the listing is indeed in application stage 116 | const blockTimestamp = new BigNumber(await utils.getBlockTimestamp()); 117 | // Checking to see if the listing is still in application stage 118 | assert((initialListingStruct[0] > 0 && initialListingStruct[0] > blockTimestamp), 'Listing is not in application stage '); 119 | try { 120 | await registry.initExit(listing, { from: applicant }); 121 | } catch (err) { 122 | const errMsg = err.toString(); 123 | assert(utils.isEVMException(err), errMsg); 124 | return; 125 | } 126 | 127 | assert(false, 'exit succeeded for non-whitelisted listing'); 128 | }); 129 | }); 130 | }); 131 | 132 | -------------------------------------------------------------------------------- /test/registry/apply.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | contract('Registry', (accounts) => { 12 | describe('Function: apply', () => { 13 | const [applicant, proposer] = accounts; 14 | let token; 15 | let parameterizer; 16 | let registry; 17 | 18 | beforeEach(async () => { 19 | const { paramProxy, registryProxy, tokenInstance } = await utils.getProxies(); 20 | parameterizer = paramProxy; 21 | registry = registryProxy; 22 | token = tokenInstance; 23 | 24 | await utils.approveProxies(accounts, token, false, parameterizer, registry); 25 | }); 26 | 27 | it('should allow a new listing to apply', async () => { 28 | const listing = utils.getListingHash('nochallenge.net'); 29 | 30 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 31 | 32 | // get the struct in the mapping 33 | const result = await registry.listings.call(listing); 34 | // check that Application is initialized correctly 35 | assert.strictEqual(result[0].gt(0), true, 'challenge time < now'); 36 | assert.strictEqual(result[1], false, 'whitelisted != false'); 37 | assert.strictEqual(result[2], applicant, 'owner of application != address that applied'); 38 | assert.strictEqual( 39 | result[3].toString(10), 40 | paramConfig.minDeposit.toString(10), 41 | 'incorrect unstakedDeposit', 42 | ); 43 | }); 44 | 45 | it('should not allow a listing to apply which has a pending application', async () => { 46 | const listing = utils.getListingHash('nochallenge.net'); 47 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 48 | 49 | // Verify that the application exists. 50 | const result = await registry.listings.call(listing); 51 | assert.strictEqual(result[2], applicant, 'owner of application != address that applied'); 52 | 53 | try { 54 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 55 | } catch (err) { 56 | assert(utils.isEVMException(err), err.toString()); 57 | return; 58 | } 59 | assert(false, 'application was made for listing with an already pending application'); 60 | }); 61 | 62 | it( 63 | 'should add a listing to the whitelist which went unchallenged in its application period', 64 | async () => { 65 | const listing = utils.getListingHash('nochallenge.net'); 66 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 67 | await utils.increaseTime(paramConfig.applyStageLength + 1); 68 | await registry.updateStatus(listing); 69 | const result = await registry.isWhitelisted.call(listing); 70 | assert.strictEqual(result, true, "listing didn't get whitelisted"); 71 | }, 72 | ); 73 | 74 | it('should not allow a listing to apply which is already listed', async () => { 75 | const listing = utils.getListingHash('nochallenge.net'); 76 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 77 | 78 | // Verify that the listing is whitelisted. 79 | const result = await registry.isWhitelisted.call(listing); 80 | assert.strictEqual(result, true, 'listing was not already whitelisted.'); 81 | 82 | try { 83 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 84 | } catch (err) { 85 | // TODO: Check if EVM error 86 | const errMsg = err.toString(); 87 | assert(utils.isEVMException(err), errMsg); 88 | return; 89 | } 90 | assert(false, 'application was made for an already-listed entry'); 91 | }); 92 | 93 | describe('token transfer', async () => { 94 | it('should revert if token transfer from user fails', async () => { 95 | const listing = utils.getListingHash('toFewTokens.net'); 96 | 97 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 98 | await token.approve(registry.address, '0', { from: applicant }); 99 | 100 | try { 101 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 102 | } catch (err) { 103 | assert(utils.isEVMException(err), err.toString()); 104 | return; 105 | } 106 | assert(false, 'allowed application with not enough tokens'); 107 | }); 108 | 109 | after(async () => { 110 | const balanceOfUser = await token.balanceOf(applicant); 111 | await token.approve(registry.address, balanceOfUser, { from: applicant }); 112 | }); 113 | }); 114 | 115 | it('should revert if the listing\'s applicationExpiry would overflow', async () => { 116 | // calculate an applyStageLen which when added to the current block time will be greater 117 | // than 2^256 - 1 118 | const blockTimestamp = await utils.getBlockTimestamp(); 119 | const maxEVMuint = new BN('2').pow('256').minus('1'); 120 | const applyStageLen = maxEVMuint.minus(blockTimestamp).plus('1'); 121 | 122 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'applyStageLen', applyStageLen.toString(10)); 123 | const { propID } = receipt.logs[0].args; 124 | 125 | // wait until the apply stage has elapsed and process the proposal 126 | await utils.increaseTime(paramConfig.pApplyStageLength + 1); 127 | await parameterizer.processProposal(propID); 128 | 129 | // make sure that the reparameterization proposal was processed as expected 130 | const actualApplyStageLen = await parameterizer.get.call('applyStageLen'); 131 | assert.strictEqual(actualApplyStageLen.toString(), applyStageLen.toString(), 'the applyStageLen should have been the proposed value'); 132 | 133 | const listing = utils.getListingHash('overflow.net'); 134 | 135 | try { 136 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 137 | } catch (err) { 138 | assert(utils.isEVMException(err), err.toString()); 139 | return; 140 | } 141 | assert(false, 'app expiry was allowed to overflow!'); 142 | }); 143 | 144 | it('should revert if the deposit amount is less than the minDeposit', async () => { 145 | const listing = utils.getListingHash('smallDeposit.net'); 146 | 147 | const minDeposit = await parameterizer.get.call('minDeposit'); 148 | const deposit = minDeposit.sub(10); 149 | 150 | try { 151 | await utils.as(applicant, registry.apply, listing, deposit.toString(), ''); 152 | } catch (err) { 153 | assert(utils.isEVMException(err), err.toString()); 154 | return; 155 | } 156 | assert(false, 'allowed an application with deposit less than minDeposit'); 157 | }); 158 | }); 159 | }); 160 | 161 | -------------------------------------------------------------------------------- /test/registry/claimReward.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: claimReward', () => { 15 | const [applicant, challenger, voterAlice] = accounts; 16 | const minDeposit = bigTen(paramConfig.minDeposit); 17 | 18 | let token; 19 | let voting; 20 | let registry; 21 | 22 | beforeEach(async () => { 23 | const { votingProxy, registryProxy, tokenInstance } = await utils.getProxies(); 24 | voting = votingProxy; 25 | registry = registryProxy; 26 | token = tokenInstance; 27 | 28 | await utils.approveProxies(accounts, token, voting, false, registry); 29 | }); 30 | 31 | it('should transfer the correct number of tokens once a challenge has been resolved', async () => { 32 | const listing = utils.getListingHash('claimthis.net'); 33 | 34 | // Apply 35 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 36 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 37 | 38 | // Challenge 39 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 40 | 41 | // Alice is so committed 42 | await utils.commitVote(pollID, '0', 500, '420', voterAlice, voting); 43 | await utils.increaseTime(paramConfig.commitStageLength + 1); 44 | 45 | // Alice is so revealing 46 | await utils.as(voterAlice, voting.revealVote, pollID, '0', '420'); 47 | await utils.increaseTime(paramConfig.revealStageLength + 1); 48 | 49 | // Update status 50 | await utils.as(applicant, registry.updateStatus, listing); 51 | 52 | // Alice claims reward 53 | const aliceVoterReward = await registry.voterReward(voterAlice, pollID); 54 | await utils.as(voterAlice, registry.claimReward, pollID); 55 | 56 | // Alice withdraws her voting rights 57 | await utils.as(voterAlice, voting.withdrawVotingRights, '500'); 58 | 59 | const aliceExpected = aliceStartingBalance.add(aliceVoterReward); 60 | const aliceFinalBalance = await token.balanceOf.call(voterAlice); 61 | 62 | assert.strictEqual( 63 | aliceFinalBalance.toString(10), aliceExpected.toString(10), 64 | 'alice should have the same balance as she started', 65 | ); 66 | }); 67 | 68 | it('should revert if challenge does not exist', async () => { 69 | const listing = utils.getListingHash('reversion.net'); 70 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 71 | 72 | try { 73 | const nonPollID = '666'; 74 | await utils.as(voterAlice, registry.claimReward, nonPollID); 75 | assert(false, 'should not have been able to claimReward for non-existant challengeID'); 76 | } catch (err) { 77 | assert(utils.isEVMException(err), err.toString()); 78 | } 79 | }); 80 | 81 | it('should not transfer tokens if msg.sender has already claimed tokens for a challenge', async () => { 82 | const listing = utils.getListingHash('sugar.net'); 83 | 84 | const applicantStartingBalance = await token.balanceOf.call(applicant); 85 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 86 | 87 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 88 | 89 | // Challenge 90 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 91 | 92 | // Alice is so committed 93 | await utils.commitVote(pollID, '0', 500, '420', voterAlice, voting); 94 | await utils.increaseTime(paramConfig.commitStageLength + 1); 95 | 96 | // Alice is so revealing 97 | await utils.as(voterAlice, voting.revealVote, pollID, '0', '420'); 98 | await utils.increaseTime(paramConfig.revealStageLength + 1); 99 | 100 | // Update status 101 | await utils.as(applicant, registry.updateStatus, listing); 102 | 103 | // Claim reward 104 | await utils.as(voterAlice, registry.claimReward, pollID); 105 | 106 | try { 107 | await utils.as(voterAlice, registry.claimReward, pollID); 108 | assert(false, 'should not have been able to call claimReward twice'); 109 | } catch (err) { 110 | assert(utils.isEVMException(err), err.toString()); 111 | } 112 | 113 | const applicantEndingBalance = await token.balanceOf.call(applicant); 114 | const appExpected = applicantStartingBalance.sub(minDeposit); 115 | 116 | const aliceEndingBalance = await token.balanceOf.call(voterAlice); 117 | const aliceExpected = aliceStartingBalance.add(minDeposit.div(bigTen(2))).sub(bigTen(500)); 118 | 119 | assert.strictEqual( 120 | applicantEndingBalance.toString(10), appExpected.toString(10), 121 | 'applicants ending balance is incorrect', 122 | ); 123 | assert.strictEqual( 124 | aliceEndingBalance.toString(10), aliceExpected.toString(10), 125 | 'alices ending balance is incorrect', 126 | ); 127 | }); 128 | 129 | it('should not transfer tokens for an unresolved challenge', async () => { 130 | const listing = utils.getListingHash('unresolved.net'); 131 | 132 | const applicantStartingBalance = await token.balanceOf.call(applicant); 133 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 134 | 135 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 136 | 137 | // Challenge 138 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 139 | 140 | // Alice is so committed 141 | await utils.commitVote(pollID, '0', 500, '420', voterAlice, voting); 142 | await utils.increaseTime(paramConfig.commitStageLength + 1); 143 | 144 | // Alice is so revealing 145 | await utils.as(voterAlice, voting.revealVote, pollID, '0', '420'); 146 | await utils.increaseTime(paramConfig.revealStageLength + 1); 147 | 148 | try { 149 | await utils.as(voterAlice, registry.claimReward, pollID); 150 | assert(false, 'should not have been able to claimReward for unresolved challenge'); 151 | } catch (err) { 152 | assert(utils.isEVMException(err), err.toString()); 153 | } 154 | 155 | const applicantEndingBalance = await token.balanceOf.call(applicant); 156 | const appExpected = applicantStartingBalance.sub(minDeposit); 157 | 158 | const aliceEndingBalance = await token.balanceOf.call(voterAlice); 159 | const aliceExpected = aliceStartingBalance.sub(bigTen(500)); 160 | 161 | assert.strictEqual( 162 | applicantEndingBalance.toString(10), appExpected.toString(10), 163 | 'applicants ending balance is incorrect', 164 | ); 165 | assert.strictEqual( 166 | aliceEndingBalance.toString(10), aliceExpected.toString(10), 167 | 'alices ending balance is incorrect', 168 | ); 169 | }); 170 | }); 171 | }); 172 | 173 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global artifacts */ 3 | 4 | const Eth = require('ethjs'); 5 | const HttpProvider = require('ethjs-provider-http'); 6 | const EthRPC = require('ethjs-rpc'); 7 | const abi = require('ethereumjs-abi'); 8 | const fs = require('fs'); 9 | 10 | const ethRPC = new EthRPC(new HttpProvider('http://localhost:7545')); 11 | const ethQuery = new Eth(new HttpProvider('http://localhost:7545')); 12 | 13 | const PLCRVoting = artifacts.require('PLCRVoting.sol'); 14 | const Parameterizer = artifacts.require('Parameterizer.sol'); 15 | const Registry = artifacts.require('Registry.sol'); 16 | const Token = artifacts.require('EIP20.sol'); 17 | 18 | const RegistryFactory = artifacts.require('RegistryFactory.sol'); 19 | 20 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 21 | const paramConfig = config.paramDefaults; 22 | 23 | const BN = small => new Eth.BN(small.toString(10), 10); 24 | 25 | const utils = { 26 | getProxies: async () => { 27 | const registryFactory = await RegistryFactory.deployed(); 28 | const registryReceipt = await registryFactory.newRegistryWithToken( 29 | config.token.supply, 30 | config.token.name, 31 | config.token.decimals, 32 | config.token.symbol, 33 | [ 34 | paramConfig.minDeposit, 35 | paramConfig.pMinDeposit, 36 | paramConfig.applyStageLength, 37 | paramConfig.pApplyStageLength, 38 | paramConfig.commitStageLength, 39 | paramConfig.pCommitStageLength, 40 | paramConfig.revealStageLength, 41 | paramConfig.pRevealStageLength, 42 | paramConfig.dispensationPct, 43 | paramConfig.pDispensationPct, 44 | paramConfig.voteQuorum, 45 | paramConfig.pVoteQuorum, 46 | paramConfig.exitTimeDelay, 47 | paramConfig.exitPeriodLen, 48 | ], 49 | 'The TestChain Registry', 50 | ); 51 | 52 | const { 53 | token, 54 | plcr, 55 | parameterizer, 56 | registry, 57 | } = registryReceipt.logs[0].args; 58 | 59 | const tokenInstance = Token.at(token); 60 | const votingProxy = PLCRVoting.at(plcr); 61 | const paramProxy = Parameterizer.at(parameterizer); 62 | const registryProxy = Registry.at(registry); 63 | 64 | const proxies = { 65 | tokenInstance, 66 | votingProxy, 67 | paramProxy, 68 | registryProxy, 69 | }; 70 | return proxies; 71 | }, 72 | 73 | approveProxies: async (accounts, token, plcr, parameterizer, registry) => ( 74 | Promise.all(accounts.map(async (user) => { 75 | await token.transfer(user, 10000000000000000000000000); 76 | if (plcr) { 77 | await token.approve(plcr.address, 10000000000000000000000000, { from: user }); 78 | } 79 | if (parameterizer) { 80 | await token.approve(parameterizer.address, 10000000000000000000000000, { from: user }); 81 | } 82 | if (registry) { 83 | await token.approve(registry.address, 10000000000000000000000000, { from: user }); 84 | } 85 | })) 86 | ), 87 | 88 | increaseTime: async seconds => 89 | new Promise((resolve, reject) => ethRPC.sendAsync({ 90 | method: 'evm_increaseTime', 91 | params: [seconds], 92 | }, (err) => { 93 | if (err) reject(err); 94 | resolve(); 95 | })) 96 | .then(() => new Promise((resolve, reject) => ethRPC.sendAsync({ 97 | method: 'evm_mine', 98 | params: [], 99 | }, (err) => { 100 | if (err) reject(err); 101 | resolve(); 102 | }))), 103 | 104 | getVoteSaltHash: (vote, salt) => ( 105 | `0x${abi.soliditySHA3(['uint', 'uint'], [vote, salt]).toString('hex')}` 106 | ), 107 | 108 | getListingHash: domain => ( 109 | `0x${abi.soliditySHA3(['string'], [domain]).toString('hex')}` 110 | ), 111 | 112 | approvePLCR: async (address, adtAmount) => { 113 | const registry = await Registry.deployed(); 114 | const plcrAddr = await registry.voting.call(); 115 | const token = await Token.deployed(); 116 | await token.approve(plcrAddr, adtAmount, { from: address }); 117 | }, 118 | 119 | addToWhitelist: async (domain, deposit, actor, registry) => { 120 | await utils.as(actor, registry.apply, domain, deposit, ''); 121 | await utils.increaseTime(paramConfig.applyStageLength + 1); 122 | await utils.as(actor, registry.updateStatus, domain); 123 | }, 124 | 125 | as: (actor, fn, ...args) => { 126 | function detectSendObject(potentialSendObj) { 127 | function hasOwnProperty(obj, prop) { 128 | const proto = obj.constructor.prototype; 129 | return (prop in obj) && 130 | (!(prop in proto) || proto[prop] !== obj[prop]); 131 | } 132 | if (typeof potentialSendObj !== 'object') { return undefined; } 133 | if ( 134 | hasOwnProperty(potentialSendObj, 'from') || 135 | hasOwnProperty(potentialSendObj, 'to') || 136 | hasOwnProperty(potentialSendObj, 'gas') || 137 | hasOwnProperty(potentialSendObj, 'gasPrice') || 138 | hasOwnProperty(potentialSendObj, 'value') 139 | ) { 140 | throw new Error('It is unsafe to use "as" with custom send objects'); 141 | } 142 | return undefined; 143 | } 144 | detectSendObject(args[args.length - 1]); 145 | const sendObject = { from: actor }; 146 | return fn(...args, sendObject); 147 | }, 148 | 149 | isEVMException: err => ( 150 | err.toString().includes('revert') 151 | ), 152 | 153 | // returns block timestamp 154 | getBlockTimestamp: () => ethQuery.blockNumber() 155 | .then(num => ethQuery.getBlockByNumber(num, true)) 156 | .then(block => block.timestamp.toString(10)), 157 | 158 | getUnstakedDeposit: async (domain, registry) => { 159 | // get the struct in the mapping 160 | const listing = await registry.listings.call(domain); 161 | // get the unstaked deposit amount from the listing struct 162 | const unstakedDeposit = await listing[3]; 163 | return unstakedDeposit.toString(); 164 | }, 165 | 166 | challengeAndGetPollID: async (domain, actor, registry) => { 167 | const receipt = await utils.as(actor, registry.challenge, domain, ''); 168 | return receipt.logs[0].args.challengeID; 169 | }, 170 | 171 | commitVote: async (pollID, voteOption, tokensArg, salt, voter, voting) => { 172 | const hash = utils.getVoteSaltHash(voteOption, salt); 173 | await utils.as(voter, voting.requestVotingRights, tokensArg); 174 | 175 | const prevPollID = await voting.getInsertPointForNumTokens.call(voter, tokensArg, pollID); 176 | await utils.as(voter, voting.commitVote, pollID, hash, tokensArg, prevPollID); 177 | }, 178 | 179 | getReceiptValue: (receipt, arg) => receipt.logs[0].args[arg], 180 | 181 | proposeReparamAndGetPropID: async (reParam, value, actor, parameterizer) => { 182 | const receipt = await utils.as(actor, parameterizer.proposeReparameterization, reParam, value); 183 | return receipt.logs[0].args.propID; 184 | }, 185 | 186 | challengeReparamAndGetChallengeID: async (propID, actor, parameterizer) => { 187 | const receipt = await utils.as(actor, parameterizer.challengeReparameterization, propID); 188 | return receipt.logs[0].args.challengeID; 189 | }, 190 | 191 | divideAndGetWei: (numerator, denominator) => { 192 | const weiNumerator = Eth.toWei(BN(numerator), 'gwei'); 193 | return weiNumerator.div(BN(denominator)); 194 | }, 195 | 196 | multiplyFromWei: (x, weiBN) => { 197 | if (!Eth.BN.isBN(weiBN)) { 198 | return false; 199 | } 200 | const weiProduct = BN(x).mul(weiBN); 201 | return BN(Eth.fromWei(weiProduct, 'gwei')); 202 | }, 203 | 204 | multiplyByPercentage: (x, y, z = 100) => { 205 | const weiQuotient = utils.divideAndGetWei(y, z); 206 | return utils.multiplyFromWei(x, weiQuotient); 207 | }, 208 | }; 209 | 210 | module.exports = utils; 211 | -------------------------------------------------------------------------------- /test/parameterizer/challengeReparameterization.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bn.js'); 5 | const utils = require('../utils'); 6 | 7 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 8 | const paramConfig = config.paramDefaults; 9 | 10 | contract('Parameterizer', (accounts) => { 11 | describe('Function: challengeReparameterization', () => { 12 | const [proposer, challenger, voter] = accounts; 13 | 14 | let token; 15 | let voting; 16 | let parameterizer; 17 | 18 | beforeEach(async () => { 19 | const { votingProxy, paramProxy, tokenInstance } = await utils.getProxies(token); 20 | voting = votingProxy; 21 | parameterizer = paramProxy; 22 | token = tokenInstance; 23 | 24 | await utils.approveProxies(accounts, token, voting, parameterizer, false); 25 | }); 26 | 27 | it('should leave parameters unchanged if a proposal loses a challenge', async () => { 28 | const proposerStartingBalance = await token.balanceOf.call(proposer); 29 | const challengerStartingBalance = await token.balanceOf.call(challenger); 30 | 31 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 32 | 33 | const { propID } = receipt.logs[0].args; 34 | 35 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 36 | 37 | await utils.increaseTime(paramConfig.pCommitStageLength + paramConfig.pRevealStageLength + 1); 38 | 39 | await parameterizer.processProposal(propID); 40 | 41 | const voteQuorum = await parameterizer.get('voteQuorum'); 42 | assert.strictEqual(voteQuorum.toString(10), '50', 'The proposal succeeded which ' + 43 | 'should have been successfully challenged'); 44 | 45 | const proposerFinalBalance = await token.balanceOf.call(proposer); 46 | const proposerExpected = proposerStartingBalance 47 | .sub(new BN(paramConfig.pMinDeposit.toString(), 10)); 48 | assert.strictEqual( 49 | proposerFinalBalance.toString(10), proposerExpected.toString(10), 50 | 'The challenge loser\'s token balance is not as expected', 51 | ); 52 | 53 | // Edge case, challenger gets both deposits back because there were no voters 54 | const challengerFinalBalance = await token.balanceOf.call(challenger); 55 | const challengerExpected = challengerStartingBalance 56 | .add(new BN(paramConfig.pMinDeposit.toString(), 10)); 57 | assert.strictEqual( 58 | challengerFinalBalance.toString(10), challengerExpected.toString(10), 59 | 'The challenge winner\'s token balance is not as expected', 60 | ); 61 | }); 62 | 63 | it('should set new parameters if a proposal wins a challenge', async () => { 64 | const proposerStartingBalance = await token.balanceOf.call(proposer); 65 | const challengerStartingBalance = await token.balanceOf.call(challenger); 66 | 67 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 68 | 69 | const { propID } = proposalReceipt.logs[0].args; 70 | 71 | const challengeReceipt = 72 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 73 | 74 | const { challengeID } = challengeReceipt.logs[0].args; 75 | 76 | await utils.commitVote(challengeID, '1', '10', '420', voter, voting); 77 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 78 | 79 | await utils.as(voter, voting.revealVote, challengeID, '1', '420'); 80 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 81 | 82 | await parameterizer.processProposal(propID); 83 | 84 | const voteQuorum = await parameterizer.get('voteQuorum'); 85 | assert.strictEqual(voteQuorum.toString(10), '51', 'The proposal failed which ' + 86 | 'should have succeeded'); 87 | 88 | const proposerFinalBalance = await token.balanceOf.call(proposer); 89 | const winnings = 90 | utils.multiplyByPercentage(paramConfig.pMinDeposit, paramConfig.pDispensationPct); 91 | const proposerExpected = proposerStartingBalance.add(winnings); 92 | assert.strictEqual( 93 | proposerFinalBalance.toString(10), proposerExpected.toString(10), 94 | 'The challenge winner\'s token balance is not as expected', 95 | ); 96 | 97 | const challengerFinalBalance = await token.balanceOf.call(challenger); 98 | const challengerExpected = challengerStartingBalance 99 | .sub(new BN(paramConfig.pMinDeposit.toString(), 10)); 100 | assert.strictEqual( 101 | challengerFinalBalance.toString(10), challengerExpected.toString(10), 102 | 'The challenge loser\'s token balance is not as expected', 103 | ); 104 | }); 105 | 106 | it( 107 | 'should have deposits of equal size if a challenge is opened & the pMinDeposit has changed since the proposal was initiated', 108 | async () => { 109 | // make proposal to change pMinDeposit 110 | // this is to induce an error where: 111 | // a challenge could have a different stake than the proposal being challenged 112 | const proposalReceiptOne = await utils.as(proposer, parameterizer.proposeReparameterization, 'pMinDeposit', paramConfig.pMinDeposit * 10); 113 | const propIDOne = proposalReceiptOne.logs[0].args.propID; 114 | 115 | // increase time 116 | // we want the second proposal to get the deposit 117 | // from the original pMinDeposit and NOT the pMinDeposit from the first proposal 118 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 119 | 120 | // open a proposal to change commitDuration 121 | // this is the proposal that we will test against 122 | const proposalReceiptTwo = await utils.as(proposer, parameterizer.proposeReparameterization, 'commitStageLen', paramConfig.commitStageLength + 1); 123 | const propIDTwo = proposalReceiptTwo.logs[0].args.propID; 124 | 125 | // increase time & update pMinDeposit 126 | // process the first proposal 127 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 128 | await parameterizer.processProposal(propIDOne); 129 | 130 | // challenge the second proposal 131 | const challengeReceipt = 132 | await utils.as(challenger, parameterizer.challengeReparameterization, propIDTwo); 133 | const { challengeID } = challengeReceipt.logs[0].args; 134 | 135 | // assert that the prop.deposit and the challenge.stake are equal 136 | const challenge = await parameterizer.challenges.call(challengeID.toString()); 137 | const challengeStake = challenge[3]; 138 | const proposal = await parameterizer.proposals.call(propIDTwo.toString()); 139 | const proposalDeposit = proposal[2]; 140 | assert.strictEqual(challengeStake.toString(), proposalDeposit.toString(), 'parties to the challenge have different deposits'); 141 | }, 142 | ); 143 | 144 | it('should not allow a challenges for non-existent proposals', async () => { 145 | try { 146 | await utils.as(challenger, parameterizer.challengeReparameterization, new BN(0).toString()); 147 | } catch (err) { 148 | assert(utils.isEVMException(err), err.toString()); 149 | return; 150 | } 151 | assert(false, 'challenge was made on non-existent poll'); 152 | }); 153 | 154 | it('should revert if token transfer from user fails', async () => { 155 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '55'); 156 | 157 | const { propID } = proposalReceipt.logs[0].args; 158 | 159 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 160 | await token.approve(parameterizer.address, '0', { from: challenger }); 161 | 162 | try { 163 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 164 | } catch (err) { 165 | assert(utils.isEVMException(err), err.toString()); 166 | return; 167 | } 168 | assert(false, 'allowed challenge with not enough tokens'); 169 | }); 170 | }); 171 | }); 172 | 173 | -------------------------------------------------------------------------------- /test/parameterizer/processProposal.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | const utils = require('../utils'); 6 | 7 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 8 | const paramConfig = config.paramDefaults; 9 | 10 | contract('Parameterizer', (accounts) => { 11 | describe('Function: processProposal', () => { 12 | const [proposer, challenger, voter] = accounts; 13 | 14 | let token; 15 | let voting; 16 | let parameterizer; 17 | let registry; 18 | 19 | beforeEach(async () => { 20 | const { 21 | votingProxy, paramProxy, registryProxy, tokenInstance, 22 | } = await utils.getProxies(token); 23 | voting = votingProxy; 24 | parameterizer = paramProxy; 25 | registry = registryProxy; 26 | token = tokenInstance; 27 | 28 | await utils.approveProxies(accounts, token, voting, parameterizer, registry); 29 | }); 30 | 31 | it('should revert if block timestamp + pApplyStageLen is greater than 2^256 - 1', async () => { 32 | // calculate an applyStageLen which when added to the current block time will be greater 33 | // than 2^256 - 1 34 | const blockTimestamp = await utils.getBlockTimestamp(); 35 | const maxEVMuint = new BN('2').pow('256').minus('1'); 36 | const applyStageLen = maxEVMuint.minus(blockTimestamp).plus('1'); 37 | 38 | // propose the malicious applyStageLen 39 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'pApplyStageLen', applyStageLen.toString(10)); 40 | const { propID } = receipt.logs[0].args; 41 | 42 | // wait until the apply stage has elapsed 43 | await utils.increaseTime(paramConfig.pApplyStageLength + 1); 44 | 45 | // process the bad proposal, expecting an invalid opcode 46 | try { 47 | await parameterizer.processProposal(propID); 48 | } catch (err) { 49 | assert(utils.isEVMException(err), err.toString()); 50 | return; 51 | } 52 | 53 | assert(false, 'An overflow occurred'); 54 | }); 55 | 56 | it('should set new parameters if a proposal went unchallenged', async () => { 57 | const proposerInitialBalance = await token.balanceOf.call(proposer); 58 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 59 | 60 | await utils.increaseTime(paramConfig.pApplyStageLength + 1); 61 | 62 | const { propID } = receipt.logs[0].args; 63 | await parameterizer.processProposal(propID); 64 | 65 | const proposerFinalBalance = await token.balanceOf.call(proposer); 66 | 67 | const voteQuorum = await parameterizer.get.call('voteQuorum'); 68 | assert.strictEqual( 69 | voteQuorum.toString(10), '51', 70 | 'A proposal which went unchallenged failed to update its parameter', 71 | ); 72 | 73 | assert.strictEqual( 74 | proposerFinalBalance.toString(10), proposerInitialBalance.toString(10), 75 | 'The proposer\'s tokens were not returned after setting their parameter', 76 | ); 77 | }); 78 | 79 | it('should not set new parameters if a proposal\'s processBy date has passed', async () => { 80 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '69'); 81 | 82 | const { propID } = receipt.logs[0].args; 83 | const paramProp = await parameterizer.proposals.call(propID); 84 | const processBy = paramProp[5]; 85 | await utils.increaseTime(processBy.toNumber() + 1); 86 | 87 | await parameterizer.processProposal(propID); 88 | 89 | const voteQuorum = await parameterizer.get.call('voteQuorum'); 90 | assert.strictEqual( 91 | voteQuorum.toString(10), '50', 92 | 'A proposal whose processBy date passed was able to update the parameterizer', 93 | ); 94 | }); 95 | 96 | it('should not set new parameters if a proposal\'s processBy date has passed, ' + 97 | 'but should resolve any challenges against the domain', async () => { 98 | const proposerStartingBalance = await token.balanceOf.call(proposer); 99 | const challengerStartingBalance = await token.balanceOf.call(challenger); 100 | 101 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '79'); 102 | 103 | const { propID } = receipt.logs[0].args; 104 | 105 | const challengeReceipt = 106 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 107 | 108 | const { challengeID } = challengeReceipt.logs[0].args; 109 | await utils.commitVote(challengeID, '0', '10', '420', voter, voting); 110 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 111 | 112 | await utils.as(voter, voting.revealVote, challengeID, '0', '420'); 113 | 114 | const paramProp = await parameterizer.proposals.call(propID); 115 | const processBy = paramProp[5]; 116 | await utils.increaseTime(processBy.toNumber() + 1); 117 | 118 | await parameterizer.processProposal(propID); 119 | 120 | // verify that the challenge has been resolved 121 | const challenge = await parameterizer.challenges.call(challengeID); 122 | const resolved = challenge[2]; 123 | assert.strictEqual(resolved, true, 'Challenge has not been resolved'); 124 | 125 | // check parameters 126 | const voteQuorum = await parameterizer.get.call('voteQuorum'); 127 | assert.strictEqual( 128 | voteQuorum.toString(10), '50', 129 | 'A proposal whose processBy date passed was able to update the parameterizer', 130 | ); 131 | 132 | const proposerFinalBalance = await token.balanceOf.call(proposer); 133 | const proposerExpected = proposerStartingBalance.sub(new BN(paramConfig.pMinDeposit, 10)); 134 | assert.strictEqual( 135 | proposerFinalBalance.toString(10), proposerExpected.toString(10), 136 | 'The challenge loser\'s token balance is not as expected', 137 | ); 138 | 139 | const challengerFinalBalance = await token.balanceOf.call(challenger); 140 | const winnings = 141 | utils.multiplyByPercentage(paramConfig.pMinDeposit, paramConfig.pDispensationPct); 142 | const challengerExpected = challengerStartingBalance.add(winnings); 143 | assert.strictEqual( 144 | challengerFinalBalance.toString(10), challengerExpected.toString(10), 145 | 'The challenge winner\'s token balance is not as expected', 146 | ); 147 | }); 148 | 149 | it('should not set new parameters if a proposal\'s processBy date has passed, ' + 150 | 'but challenge failed', async () => { 151 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '78'); 152 | 153 | const { propID } = receipt.logs[0].args; 154 | 155 | const challengeReceipt = 156 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 157 | 158 | const { challengeID } = challengeReceipt.logs[0].args; 159 | await utils.commitVote(challengeID, '1', '10', '420', voter, voting); 160 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 161 | 162 | await utils.as(voter, voting.revealVote, challengeID, '1', '420'); 163 | 164 | const paramProp = await parameterizer.proposals.call(propID); 165 | const processBy = paramProp[5]; 166 | await utils.increaseTime(processBy.toNumber() + 1); 167 | 168 | await parameterizer.processProposal(propID); 169 | 170 | const voteQuorum = await parameterizer.get.call('voteQuorum'); 171 | assert.strictEqual( 172 | voteQuorum.toString(10), '50', 173 | 'A proposal whose processBy date passed was able to update the parameterizer', 174 | ); 175 | }); 176 | 177 | it('should revert if processProposal is called before appExpiry', async () => { 178 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '70'); 179 | 180 | const { propID } = receipt.logs[0].args; 181 | 182 | try { 183 | await parameterizer.processProposal(propID); 184 | } catch (err) { 185 | assert(utils.isEVMException(err), err.toString()); 186 | return; 187 | } 188 | assert(false, 'proposal was processed without a challenge and before appExpiry and processBy date'); 189 | }); 190 | }); 191 | }); 192 | 193 | -------------------------------------------------------------------------------- /test/registry/challenge.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | 6 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 7 | const paramConfig = config.paramDefaults; 8 | 9 | const utils = require('../utils.js'); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: challenge', () => { 15 | const [applicant, challenger, voter, proposer] = accounts; 16 | 17 | let token; 18 | let voting; 19 | let parameterizer; 20 | let registry; 21 | 22 | beforeEach(async () => { 23 | const { 24 | votingProxy, paramProxy, registryProxy, tokenInstance, 25 | } = await utils.getProxies(); 26 | voting = votingProxy; 27 | parameterizer = paramProxy; 28 | registry = registryProxy; 29 | token = tokenInstance; 30 | 31 | await utils.approveProxies(accounts, token, voting, parameterizer, registry); 32 | }); 33 | 34 | it('should successfully challenge an application', async () => { 35 | const listing = utils.getListingHash('failure.net'); 36 | 37 | const challengerStartingBalance = await token.balanceOf.call(challenger); 38 | 39 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 40 | await utils.challengeAndGetPollID(listing, challenger, registry); 41 | await utils.increaseTime(paramConfig.commitStageLength + paramConfig.revealStageLength + 1); 42 | await registry.updateStatus(listing); 43 | 44 | const isWhitelisted = await registry.isWhitelisted.call(listing); 45 | assert.strictEqual(isWhitelisted, false, 'An application which should have failed succeeded'); 46 | 47 | const challengerFinalBalance = await token.balanceOf.call(challenger); 48 | // Note edge case: no voters, so challenger gets entire stake 49 | const expectedFinalBalance = 50 | challengerStartingBalance.add(new BN(paramConfig.minDeposit, 10)); 51 | assert.strictEqual( 52 | challengerFinalBalance.toString(10), expectedFinalBalance.toString(10), 53 | 'Reward not properly disbursed to challenger', 54 | ); 55 | }); 56 | 57 | it('should successfully challenge a listing', async () => { 58 | const listing = utils.getListingHash('failure.net'); 59 | 60 | const challengerStartingBalance = await token.balanceOf.call(challenger); 61 | 62 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 63 | 64 | await utils.challengeAndGetPollID(listing, challenger, registry); 65 | await utils.increaseTime(paramConfig.commitStageLength + paramConfig.revealStageLength + 1); 66 | await registry.updateStatus(listing); 67 | 68 | const isWhitelisted = await registry.isWhitelisted.call(listing); 69 | assert.strictEqual(isWhitelisted, false, 'An application which should have failed succeeded'); 70 | 71 | const challengerFinalBalance = await token.balanceOf.call(challenger); 72 | // Note edge case: no voters, so challenger gets entire stake 73 | const expectedFinalBalance = 74 | challengerStartingBalance.add(new BN(paramConfig.minDeposit, 10)); 75 | assert.strictEqual( 76 | challengerFinalBalance.toString(10), expectedFinalBalance.toString(10), 77 | 'Reward not properly disbursed to challenger', 78 | ); 79 | }); 80 | 81 | it('should unsuccessfully challenge an application', async () => { 82 | const listing = utils.getListingHash('winner.net'); 83 | const minDeposit = new BN(paramConfig.minDeposit, 10); 84 | 85 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 86 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 87 | await utils.commitVote(pollID, 1, 10, 420, voter, voting); 88 | await utils.increaseTime(paramConfig.commitStageLength + 1); 89 | await utils.as(voter, voting.revealVote, pollID, 1, 420); 90 | await utils.increaseTime(paramConfig.revealStageLength + 1); 91 | await registry.updateStatus(listing); 92 | 93 | const isWhitelisted = await registry.isWhitelisted.call(listing); 94 | assert.strictEqual( 95 | isWhitelisted, true, 96 | 'An application which should have succeeded failed', 97 | ); 98 | 99 | const unstakedDeposit = await utils.getUnstakedDeposit(listing, registry); 100 | const expectedUnstakedDeposit = 101 | minDeposit.add(minDeposit.mul(bigTen(paramConfig.dispensationPct).div(bigTen(100)))); 102 | 103 | assert.strictEqual( 104 | unstakedDeposit.toString(10), expectedUnstakedDeposit.toString(10), 105 | 'The challenge winner was not properly disbursed their tokens', 106 | ); 107 | }); 108 | 109 | it('should unsuccessfully challenge a listing', async () => { 110 | const listing = utils.getListingHash('winner2.net'); 111 | const minDeposit = new BN(paramConfig.minDeposit, 10); 112 | 113 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 114 | 115 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 116 | await utils.commitVote(pollID, 1, 10, 420, voter, voting); 117 | await utils.increaseTime(paramConfig.commitStageLength + 1); 118 | await utils.as(voter, voting.revealVote, pollID, 1, 420); 119 | await utils.increaseTime(paramConfig.revealStageLength + 1); 120 | await registry.updateStatus(listing); 121 | 122 | const isWhitelisted = await registry.isWhitelisted.call(listing); 123 | assert.strictEqual(isWhitelisted, true, 'An application which should have succeeded failed'); 124 | 125 | const unstakedDeposit = await utils.getUnstakedDeposit(listing, registry); 126 | const expectedUnstakedDeposit = minDeposit.add(minDeposit.mul(new BN(paramConfig.dispensationPct, 10).div(new BN('100', 10)))); 127 | assert.strictEqual( 128 | unstakedDeposit.toString(10), expectedUnstakedDeposit.toString(10), 129 | 'The challenge winner was not properly disbursed their tokens', 130 | ); 131 | }); 132 | 133 | it('should touch-and-remove a listing with a depost below the current minimum', async () => { 134 | const listing = utils.getListingHash('touchandremove.net'); 135 | const minDeposit = new BN(paramConfig.minDeposit, 10); 136 | const newMinDeposit = minDeposit.add(new BN('1', 10)); 137 | 138 | const applicantStartingBal = await token.balanceOf.call(applicant); 139 | 140 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 141 | 142 | const receipt = await utils.as( 143 | proposer, parameterizer.proposeReparameterization, 144 | 'minDeposit', newMinDeposit, 145 | ); 146 | const propID = utils.getReceiptValue(receipt, 'propID'); 147 | 148 | await utils.increaseTime(paramConfig.pApplyStageLength + 1); 149 | 150 | await parameterizer.processProposal(propID); 151 | 152 | const challengerStartingBal = await token.balanceOf.call(challenger); 153 | utils.as(challenger, registry.challenge, listing, ''); 154 | const challengerFinalBal = await token.balanceOf.call(challenger); 155 | 156 | assert( 157 | challengerStartingBal.eq(challengerFinalBal), 158 | 'Tokens were not returned to challenger', 159 | ); 160 | 161 | const applicantFinalBal = await token.balanceOf.call(applicant); 162 | 163 | assert( 164 | applicantStartingBal.eq(applicantFinalBal), 165 | 'Tokens were not returned to applicant', 166 | ); 167 | 168 | assert(!await registry.isWhitelisted.call(listing), 'Listing was not removed'); 169 | }); 170 | 171 | it('should not be able to challenge a listing hash that doesn\'t exist', async () => { 172 | const listing = utils.getListingHash('doesNotExist.net'); 173 | 174 | try { 175 | await utils.challengeAndGetPollID(listing, challenger, registry); 176 | } catch (err) { 177 | assert(utils.isEVMException(err), err.toString()); 178 | return; 179 | } 180 | assert(false, 'challenge succeeded when listing does not exist'); 181 | }); 182 | 183 | it('should revert if challenge occurs on a listing with an open challenge', async () => { 184 | const listing = utils.getListingHash('doubleChallenge.net'); 185 | const minDeposit = new BN(await parameterizer.get.call('minDeposit'), 10); 186 | 187 | await utils.addToWhitelist(listing, minDeposit.toString(), applicant, registry); 188 | 189 | await utils.challengeAndGetPollID(listing, challenger, registry); 190 | 191 | try { 192 | await utils.as(challenger, registry.challenge, listing, ''); 193 | } catch (err) { 194 | assert(utils.isEVMException(err), err.toString()); 195 | return; 196 | } 197 | assert(false, 'challenge succeeded when challenge is already open'); 198 | }); 199 | 200 | it('should revert if token transfer from user fails', async () => { 201 | const listing = utils.getListingHash('challengerNeedsTokens.net'); 202 | 203 | const minDeposit = new BN(await parameterizer.get.call('minDeposit'), 10); 204 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 205 | 206 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 207 | await token.approve(registry.address, '0', { from: challenger }); 208 | 209 | try { 210 | await utils.as(challenger, registry.challenge, listing, ''); 211 | } catch (err) { 212 | assert(utils.isEVMException(err), err.toString()); 213 | return; 214 | } 215 | assert(false, 'allowed challenge with not enough tokens'); 216 | }); 217 | }); 218 | }); 219 | 220 | -------------------------------------------------------------------------------- /test/parameterizer/claimReward.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bn.js'); 5 | const utils = require('../utils'); 6 | 7 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 8 | const paramConfig = config.paramDefaults; 9 | 10 | const bigTen = number => new BN(number.toString(10), 10); 11 | 12 | contract('Parameterizer', (accounts) => { 13 | describe('Function: claimReward', () => { 14 | const [proposer, challenger, voterAlice, voterBob] = accounts; 15 | 16 | let token; 17 | let voting; 18 | let parameterizer; 19 | let registry; 20 | 21 | beforeEach(async () => { 22 | const { 23 | votingProxy, paramProxy, registryProxy, tokenInstance, 24 | } = await utils.getProxies(token); 25 | voting = votingProxy; 26 | parameterizer = paramProxy; 27 | registry = registryProxy; 28 | token = tokenInstance; 29 | 30 | await utils.approveProxies(accounts, token, voting, parameterizer, registry); 31 | }); 32 | 33 | it('should give the correct number of tokens to a voter on the winning side.', async () => { 34 | const voterAliceStartingBalance = await token.balanceOf.call(voterAlice); 35 | 36 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 37 | 38 | const { propID } = proposalReceipt.logs[0].args; 39 | 40 | const challengeReceipt = 41 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 42 | 43 | const { challengeID } = challengeReceipt.logs[0].args; 44 | 45 | await utils.commitVote(challengeID, '1', '10', '420', voterAlice, voting); 46 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 47 | 48 | await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); 49 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 50 | 51 | await parameterizer.processProposal(propID); 52 | 53 | await utils.as(voterAlice, parameterizer.claimReward, challengeID); 54 | await utils.as(voterAlice, voting.withdrawVotingRights, '10'); 55 | 56 | const voterAliceFinalBalance = await token.balanceOf.call(voterAlice); 57 | const voterAliceExpected = voterAliceStartingBalance.add(utils.multiplyByPercentage( 58 | paramConfig.pMinDeposit, 59 | bigTen(100).sub(bigTen(paramConfig.pDispensationPct)), 60 | )); 61 | assert.strictEqual( 62 | voterAliceFinalBalance.toString(10), voterAliceExpected.toString(10), 63 | 'A voterAlice\'s token balance is not as expected after claiming a reward', 64 | ); 65 | }); 66 | 67 | it( 68 | 'should give the correct number of tokens to multiple voters on the winning side.', 69 | async () => { 70 | // const voterAliceStartingBalance = await token.balanceOf.call(voterAlice); 71 | // const voterBobStartingBalance = await token.balanceOf.call(voterBob); 72 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '52'); 73 | 74 | const { propID } = proposalReceipt.logs[0].args; 75 | 76 | const challengeReceipt = 77 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 78 | 79 | const { challengeID } = challengeReceipt.logs[0].args; 80 | 81 | await utils.commitVote(challengeID, '1', '10', '420', voterAlice, voting); 82 | await utils.commitVote(challengeID, '1', '20', '420', voterBob, voting); 83 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 84 | 85 | await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); 86 | await utils.as(voterBob, voting.revealVote, challengeID, '1', '420'); 87 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 88 | 89 | await parameterizer.processProposal(propID); 90 | 91 | const voterAliceReward = await parameterizer.voterReward.call( 92 | voterAlice, 93 | challengeID, 94 | ); 95 | await utils.as(voterAlice, parameterizer.claimReward, challengeID); 96 | await utils.as(voterAlice, voting.withdrawVotingRights, '10'); 97 | 98 | const voterBobReward = await parameterizer.voterReward.call( 99 | voterBob, 100 | challengeID, 101 | ); 102 | await utils.as(voterBob, parameterizer.claimReward, challengeID); 103 | await utils.as(voterBob, voting.withdrawVotingRights, '20'); 104 | 105 | // TODO: do better than approximately. 106 | assert.approximately( 107 | voterBobReward.toNumber(10), 108 | voterAliceReward.mul(new BN('2', 10)).toNumber(10), 109 | 2, 110 | 'Rewards were not properly distributed between voters', 111 | ); 112 | // TODO: add asserts for final balances 113 | }, 114 | ); 115 | 116 | it('should not transfer tokens for an unresolved challenge', async () => { 117 | const proposerStartingBalance = await token.balanceOf.call(proposer); 118 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 119 | 120 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'pMinDeposit', '5000'); 121 | 122 | const { propID } = proposalReceipt.logs[0].args; 123 | 124 | const challengeReceipt = 125 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 126 | 127 | const { challengeID } = challengeReceipt.logs[0].args; 128 | 129 | await utils.commitVote(challengeID, '1', '10', '420', voterAlice, voting); 130 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 131 | 132 | await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); 133 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 134 | 135 | try { 136 | await utils.as(voterAlice, parameterizer.claimReward, challengeID); 137 | assert(false, 'should not have been able to claimReward for unresolved challenge'); 138 | } catch (err) { 139 | assert(utils.isEVMException(err), err.toString()); 140 | } 141 | 142 | const proposerEndingBalance = await token.balanceOf.call(proposer); 143 | const proposerExpected = proposerStartingBalance.sub(bigTen(paramConfig.pMinDeposit)); 144 | const aliceEndingBalance = await token.balanceOf.call(voterAlice); 145 | const aliceExpected = aliceStartingBalance.sub(bigTen(10)); 146 | 147 | assert.strictEqual( 148 | proposerEndingBalance.toString(10), proposerExpected.toString(10), 149 | 'proposers ending balance is incorrect', 150 | ); 151 | assert.strictEqual( 152 | aliceEndingBalance.toString(10), aliceExpected.toString(10), 153 | 'alices ending balance is incorrect', 154 | ); 155 | }); 156 | 157 | it('should revert if voter tries to claim reward more than once.', async () => { 158 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '80'); 159 | 160 | const { propID } = proposalReceipt.logs[0].args; 161 | 162 | const challengeReceipt = 163 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 164 | 165 | const { challengeID } = challengeReceipt.logs[0].args; 166 | 167 | await utils.commitVote(challengeID, '1', '10', '420', voterAlice, voting); 168 | 169 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 170 | 171 | await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); 172 | 173 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 174 | 175 | await parameterizer.processProposal(propID); 176 | 177 | await utils.as(voterAlice, parameterizer.claimReward, challengeID); 178 | 179 | try { 180 | await utils.as(voterAlice, parameterizer.claimReward, challengeID); 181 | } catch (err) { 182 | assert(utils.isEVMException(err), err.toString()); 183 | return; 184 | } 185 | assert(false, 'voter claimed reward more than once'); 186 | }); 187 | 188 | it('should revert if a voter on the losing side tries to claim a reward.', async () => { 189 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '49'); 190 | 191 | const { propID } = proposalReceipt.logs[0].args; 192 | 193 | const challengeReceipt = 194 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 195 | 196 | const { challengeID } = challengeReceipt.logs[0].args; 197 | 198 | // Vote so Bob is on the losing side 199 | await utils.commitVote(challengeID, '1', '10', '420', voterAlice, voting); 200 | await utils.commitVote(challengeID, '0', '1', '420', voterBob, voting); 201 | 202 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 203 | 204 | // Reveal votes and process proposal before claiming reward 205 | await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); 206 | await utils.as(voterBob, voting.revealVote, challengeID, '0', '420'); 207 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 208 | await parameterizer.processProposal(propID); 209 | 210 | // Verify that Bob has not claimed a reward yet 211 | const claimed = await parameterizer.tokenClaims.call(challengeID, voterBob); 212 | assert.strictEqual(claimed, false, 'Bob has already claimed the reward'); 213 | // Verify that the challenge has been resolved 214 | const resolved = (await parameterizer.challenges.call(challengeID))[2]; 215 | assert.strictEqual(resolved, true, 'Challenge has not been resolved'); 216 | 217 | try { 218 | await utils.as(voterBob, parameterizer.claimReward, challengeID); 219 | } catch (err) { 220 | assert(utils.isEVMException(err), err.toString()); 221 | return; 222 | } 223 | assert(false, 'allowed voter on losing side to claim a reward'); 224 | }); 225 | }); 226 | }); 227 | 228 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2017 ConsenSys AG 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /test/registry/finalizeExit.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | 5 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 6 | const paramConfig = config.paramDefaults; 7 | const utils = require('../utils.js'); 8 | const BigNumber = require('bignumber.js'); 9 | 10 | contract('Registry', (accounts) => { 11 | describe('Function: finalizeExit', () => { 12 | const [applicant, challenger] = accounts; 13 | 14 | let token; 15 | let voting; 16 | let registry; 17 | let parameterizer; 18 | 19 | beforeEach(async () => { 20 | const { 21 | votingProxy, registryProxy, paramProxy, tokenInstance, 22 | } = await utils.getProxies(); 23 | voting = votingProxy; 24 | registry = registryProxy; 25 | parameterizer = paramProxy; 26 | token = tokenInstance; 27 | 28 | await utils.approveProxies(accounts, token, voting, parameterizer, registry); 29 | }); 30 | 31 | it('should allow a listing to exit when no challenge exists', async () => { 32 | // Adding an application to the whitelist 33 | const listing = utils.getListingHash('google.com'); 34 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 35 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 36 | const isWhitelisted = await registry.isWhitelisted.call(listing); 37 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 38 | // Exiting the whitelist 39 | await registry.initExit(listing, { from: applicant }); 40 | await utils.increaseTime(paramConfig.exitTimeDelay + 1); 41 | await registry.finalizeExit(listing, { from: applicant }); 42 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 43 | assert.strictEqual(isWhitelistedAfterExit, false, 'the listing was not removed on exit'); 44 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 45 | assert.strictEqual( 46 | initialApplicantTokenHoldings.toString(10), 47 | finalApplicantTokenHoldings.toString(10), 48 | 'the applicant\'s tokens were not returned to them after exiting the registry', 49 | ); 50 | // Make sure resetListing(), called in finalizeExit() correctly removed the listing 51 | const listingStruct = await registry.listings.call(listing); 52 | assert.strictEqual(listingStruct[5].toString(), '0', 'exitTime did not reset'); 53 | assert.strictEqual(listingStruct[6].toString(), '0', 'exitTimeExpiry did not reset'); 54 | }); 55 | 56 | it('should not allow a listing to finalize exit when exit was not initialized', async () => { 57 | // Adding an application to the whitelist 58 | const listing = utils.getListingHash('youtube.com'); 59 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 60 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 61 | const isWhitelisted = await registry.isWhitelisted.call(listing); 62 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 63 | 64 | // Trying to finalize an exit without ever calling initExit() 65 | try { 66 | await registry.finalizeExit(listing, { from: applicant }); 67 | assert(false, 'exit succeeded when it should have failed due to exit not being initialized'); 68 | } catch (err) { 69 | const errMsg = err.toString(); 70 | assert(utils.isEVMException(err), errMsg); 71 | } 72 | 73 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 74 | assert.strictEqual(isWhitelistedAfterExit, true, 'the listing was removed on finalizeExit'); 75 | 76 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 77 | assert.strictEqual( 78 | finalApplicantTokenHoldings.toString(), 79 | initialApplicantTokenHoldings.sub(paramConfig.minDeposit).toString(), 80 | 'the applicant\'s tokens were returned in spite of failing to exit', 81 | ); 82 | // Make sure the listing did not successfully initialize exit 83 | const listingStruct = await registry.listings.call(listing); 84 | assert.strictEqual(listingStruct[5].toString(), '0', 'exitTime was initialized even though initExit() was never called'); 85 | assert.strictEqual(listingStruct[6].toString(), '0', 'exitTimeExpiry initialized even though initExit() was never called'); 86 | }); 87 | 88 | it('should not allow a listing to finalize exit during the waiting period', async () => { 89 | // Adding an application to the whitelist 90 | const listing = utils.getListingHash('hangouts.com'); 91 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 92 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 93 | const isWhitelisted = await registry.isWhitelisted.call(listing); 94 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 95 | 96 | await registry.initExit(listing, { from: applicant }); 97 | // blockTimestamp is used to calculate when the applicant's exit time is up 98 | const blockTimestamp = new BigNumber(await utils.getBlockTimestamp()); 99 | // Trying to finalize exit during waiting period 100 | try { 101 | await registry.finalizeExit(listing, { from: applicant }); 102 | assert(false, 'exit succeeded when it should have failed becuase the user called finalizeExit before the delay period was over'); 103 | } catch (err) { 104 | const errMsg = err.toString(); 105 | assert(utils.isEVMException(err), errMsg); 106 | } 107 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 108 | assert(isWhitelistedAfterExit, true, 'the listing was removed on finalizeExit'); 109 | 110 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 111 | assert.strictEqual( 112 | finalApplicantTokenHoldings.toString(), 113 | initialApplicantTokenHoldings.sub(paramConfig.minDeposit).toString(), 114 | 'the applicant\'s tokens were returned in spite of failing to exit', 115 | ); 116 | // Make sure exitTimeDelay was correctly set 117 | const listingStruct = await registry.listings.call(listing); 118 | const exitTime = blockTimestamp.add(paramConfig.exitTimeDelay); 119 | assert.strictEqual(listingStruct[5].toString(), exitTime.toString(), 'exitTime was not initialized'); 120 | // Make sure exitTimeExpiry was correctly set 121 | const exitTimeExpiry = exitTime.add(paramConfig.exitPeriodLen); 122 | assert.strictEqual(listingStruct[6].toString(), exitTimeExpiry.toString(), 'exitTimeExpiry was not initialized'); 123 | }); 124 | 125 | it('should not allow a listing to finalize an exit when a challenge does exist', async () => { 126 | // Adding an application to the whitelist 127 | const listing = utils.getListingHash('520.com'); 128 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 129 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 130 | const isWhitelisted = await registry.isWhitelisted.call(listing); 131 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 132 | 133 | await registry.initExit(listing, { from: applicant }); 134 | // blockTimestamp is used to calculate when the applicant's exit time is up 135 | const blockTimestamp = new BigNumber(await utils.getBlockTimestamp()); 136 | await utils.increaseTime(paramConfig.exitTimeDelay + 1); 137 | // Challenge the listing 138 | await registry.challenge(listing, '', { from: challenger }); 139 | // Assert that challenge does exit 140 | const initialListingStruct = await registry.listings.call(listing); 141 | assert.notStrictEqual(initialListingStruct[4].toString(), '0', 'Challenge was never created'); 142 | // Trying to finalize an exit while there is an ongoing challenge 143 | try { 144 | await registry.finalizeExit(listing, { from: applicant }); 145 | assert(false, 'exit succeeded when it should have failed'); 146 | } catch (err) { 147 | const errMsg = err.toString(); 148 | assert(utils.isEVMException(err), errMsg); 149 | } 150 | 151 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 152 | assert.strictEqual( 153 | isWhitelistedAfterExit, 154 | true, 155 | 'the listing was able to exit while a challenge was active', 156 | ); 157 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 158 | assert.strictEqual( 159 | finalApplicantTokenHoldings.toString(), 160 | initialApplicantTokenHoldings.sub(paramConfig.minDeposit).toString(), 161 | 'the applicant\'s tokens were returned in spite of failing to exit', 162 | ); 163 | 164 | // Make sure exitTimeDelay was correctly set 165 | const listingStruct = await registry.listings.call(listing); 166 | const exitTime = blockTimestamp.add(paramConfig.exitTimeDelay); 167 | assert.strictEqual(listingStruct[5].toString(), exitTime.toString(), 'exitTime was not initialized'); 168 | // Make sure exitTimeExpiry was correctly set 169 | const exitTimeExpiry = exitTime.add(paramConfig.exitPeriodLen); 170 | assert.strictEqual(listingStruct[6].toString(), exitTimeExpiry.toString(), 'exitTimeExpiry was not initialized'); 171 | }); 172 | 173 | it('should not allow a listing to finalize an exit when exitPeriodLen has elapsed', async () => { 174 | // Adding an application to the whitelist 175 | const listing = utils.getListingHash('620-200.com'); 176 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 177 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 178 | const isWhitelisted = await registry.isWhitelisted.call(listing); 179 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 180 | 181 | // Initialize exit and advance time past exitPeriodLen 182 | await registry.initExit(listing, { from: applicant }); 183 | // blockTimestamp is used to calculate when the applicant's exit time is up 184 | const blockTimestamp = new BigNumber(await utils.getBlockTimestamp()); 185 | await utils.increaseTime(paramConfig.exitTimeDelay + 1); 186 | await utils.increaseTime(paramConfig.exitPeriodLen + 1); 187 | const listingStruct = await registry.listings.call(listing); 188 | 189 | try { 190 | await registry.finalizeExit(listing, { from: applicant }); 191 | assert(false, 'exit succeeded when it should have failed since exitPeriodLen elapsed'); 192 | } catch (err) { 193 | const errMsg = err.toString(); 194 | assert(utils.isEVMException(err), errMsg); 195 | } 196 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 197 | assert.strictEqual( 198 | isWhitelistedAfterExit, 199 | true, 200 | 'the listing was able to exit since exitPeriodLen elapsed', 201 | ); 202 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 203 | assert.strictEqual( 204 | finalApplicantTokenHoldings.toString(), 205 | initialApplicantTokenHoldings.sub(paramConfig.minDeposit).toString(), 206 | 'the applicant\'s tokens were returned in spite of failing to exit', 207 | ); 208 | 209 | const exitTime = blockTimestamp.add(paramConfig.exitTimeDelay); 210 | assert.strictEqual(listingStruct[5].toString(), exitTime.toString(), 'exitTime was not initialized'); 211 | 212 | const exitTimeExpiry = exitTime.add(paramConfig.exitPeriodLen); 213 | assert.strictEqual(listingStruct[6].toString(), exitTimeExpiry.toString(), 'exitTimeExpiry was not initialized'); 214 | }); 215 | 216 | it('should allow a listing to finalize after re-initializing a previous exit', async () => { 217 | // Add an application to the whitelsit 218 | const listing = utils.getListingHash('720-300.com'); 219 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 220 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 221 | const isWhitelisted = await registry.isWhitelisted.call(listing); 222 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 223 | 224 | // Initialize exit and fast forward past exitPeriodLen 225 | await registry.initExit(listing, { from: applicant }); 226 | await utils.increaseTime(paramConfig.exitTimeDelay + 1); 227 | await utils.increaseTime(paramConfig.exitPeriodLen + 1); 228 | // finalizeExit should fail since exitPeriodLen has elapsed 229 | try { 230 | await registry.finalizeExit(listing, { from: applicant }); 231 | assert(false, 'exit succeeded when it should have failed since exitPeriodLen elapsed'); 232 | } catch (err) { 233 | const errMsg = err.toString(); 234 | assert(utils.isEVMException(err), errMsg); 235 | } 236 | 237 | // Re-initialize the exit and finalize exit 238 | await registry.initExit(listing, { from: applicant }); 239 | await utils.increaseTime(paramConfig.exitTimeDelay + 1); 240 | await registry.finalizeExit(listing, { from: applicant }); 241 | 242 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 243 | assert.strictEqual( 244 | isWhitelistedAfterExit, 245 | false, 246 | 'the listing was not able to exit even though exitPeriodLen did not elapse', 247 | ); 248 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 249 | assert.strictEqual( 250 | finalApplicantTokenHoldings.toString(), 251 | initialApplicantTokenHoldings.toString(), 252 | 'the applicant\'s tokens were not returned in spite of exiting', 253 | ); 254 | const listingStruct = await registry.listings.call(listing); 255 | assert.strictEqual(listingStruct[5].toString(), '0', 'user was not able to successfully exit the listing'); 256 | assert.strictEqual(listingStruct[6].toString(), '0', 'user was not able to successfully exit the listing'); 257 | }); 258 | 259 | /** This test verifies that if a user calls initExit() and the expiration to exit is Friday, 260 | * and the parameter exitPeriodLen changes so the expiration is Saturday, 261 | * the user has until Friday to leave NOT Saturday 262 | */ 263 | it('should use snapshot values from initExit() to process finalizeExit()', async () => { 264 | // Add an application to the whitelsit 265 | const listing = utils.getListingHash('820-400.com'); 266 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 267 | const isWhitelisted = await registry.isWhitelisted.call(listing); 268 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 269 | 270 | // Get initial value of exitPeriodLen and calculate a longer exitPeriodLen 271 | const initialExitPeriodLen = await parameterizer.get('exitPeriodLen'); 272 | const newExitPeriodLen = initialExitPeriodLen.times(10); 273 | 274 | // Propose parameter change of exitPeriodLen so user has more time to leave 275 | const proposalReceipt = await utils.as(applicant, parameterizer.proposeReparameterization, 'exitPeriodLen', newExitPeriodLen); 276 | const { propID } = proposalReceipt.logs[0].args; 277 | 278 | // Increase time to allow proposal to be processed 279 | await utils.increaseTime(paramConfig.pApplyStageLength + 1); 280 | 281 | // Process reparameterization proposal after the user has initialized exit 282 | await registry.initExit(listing, { from: applicant }); 283 | await parameterizer.processProposal(propID); 284 | 285 | // Increase time past exitTimeExpiry so finalizeExit fails 286 | await utils.increaseTime(paramConfig.exitTimeDelay + paramConfig.exitTimeExpiry + 1); 287 | 288 | // Expect to fail because even though exitPeriodLen was increased, 289 | // the snapshotted values should have been retained 290 | try { 291 | await registry.finalizeExit(listing, { from: applicant }); 292 | } catch (err) { 293 | const errMsg = err.toString(); 294 | assert(utils.isEVMException(err), errMsg); 295 | return; 296 | } 297 | assert(false, 'exit succeeded when it should have failed because the original exitPeriodLen was snapshotted '); 298 | }); 299 | }); 300 | }); 301 | 302 | -------------------------------------------------------------------------------- /contracts/Parameterizer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity^0.4.11; 2 | 3 | import "plcr-revival/PLCRVoting.sol"; 4 | import "tokens/eip20/EIP20Interface.sol"; 5 | import "zeppelin/math/SafeMath.sol"; 6 | 7 | contract Parameterizer { 8 | 9 | // ------ 10 | // EVENTS 11 | // ------ 12 | 13 | event _ReparameterizationProposal(string name, uint value, bytes32 propID, uint deposit, uint appEndDate, address indexed proposer); 14 | event _NewChallenge(bytes32 indexed propID, uint challengeID, uint commitEndDate, uint revealEndDate, address indexed challenger); 15 | event _ProposalAccepted(bytes32 indexed propID, string name, uint value); 16 | event _ProposalExpired(bytes32 indexed propID); 17 | event _ChallengeSucceeded(bytes32 indexed propID, uint indexed challengeID, uint rewardPool, uint totalTokens); 18 | event _ChallengeFailed(bytes32 indexed propID, uint indexed challengeID, uint rewardPool, uint totalTokens); 19 | event _RewardClaimed(uint indexed challengeID, uint reward, address indexed voter); 20 | 21 | 22 | // ------ 23 | // DATA STRUCTURES 24 | // ------ 25 | 26 | using SafeMath for uint; 27 | 28 | struct ParamProposal { 29 | uint appExpiry; 30 | uint challengeID; 31 | uint deposit; 32 | string name; 33 | address owner; 34 | uint processBy; 35 | uint value; 36 | } 37 | 38 | struct Challenge { 39 | uint rewardPool; // (remaining) pool of tokens distributed amongst winning voters 40 | address challenger; // owner of Challenge 41 | bool resolved; // indication of if challenge is resolved 42 | uint stake; // number of tokens at risk for either party during challenge 43 | uint winningTokens; // (remaining) amount of tokens used for voting by the winning side 44 | mapping(address => bool) tokenClaims; 45 | } 46 | 47 | // ------ 48 | // STATE 49 | // ------ 50 | 51 | mapping(bytes32 => uint) public params; 52 | 53 | // maps challengeIDs to associated challenge data 54 | mapping(uint => Challenge) public challenges; 55 | 56 | // maps pollIDs to intended data change if poll passes 57 | mapping(bytes32 => ParamProposal) public proposals; 58 | 59 | // Global Variables 60 | EIP20Interface public token; 61 | PLCRVoting public voting; 62 | uint public PROCESSBY = 604800; // 7 days 63 | 64 | /** 65 | @dev Initializer Can only be called once 66 | @param _token The address where the ERC20 token contract is deployed 67 | @param _plcr address of a PLCR voting contract for the provided token 68 | @notice _parameters array of canonical parameters 69 | */ 70 | function init( 71 | address _token, 72 | address _plcr, 73 | uint[] _parameters 74 | ) public { 75 | require(_token != 0 && address(token) == 0); 76 | require(_plcr != 0 && address(voting) == 0); 77 | 78 | token = EIP20Interface(_token); 79 | voting = PLCRVoting(_plcr); 80 | 81 | // minimum deposit for listing to be whitelisted 82 | set("minDeposit", _parameters[0]); 83 | 84 | // minimum deposit to propose a reparameterization 85 | set("pMinDeposit", _parameters[1]); 86 | 87 | // period over which applicants wait to be whitelisted 88 | set("applyStageLen", _parameters[2]); 89 | 90 | // period over which reparmeterization proposals wait to be processed 91 | set("pApplyStageLen", _parameters[3]); 92 | 93 | // length of commit period for voting 94 | set("commitStageLen", _parameters[4]); 95 | 96 | // length of commit period for voting in parameterizer 97 | set("pCommitStageLen", _parameters[5]); 98 | 99 | // length of reveal period for voting 100 | set("revealStageLen", _parameters[6]); 101 | 102 | // length of reveal period for voting in parameterizer 103 | set("pRevealStageLen", _parameters[7]); 104 | 105 | // percentage of losing party's deposit distributed to winning party 106 | set("dispensationPct", _parameters[8]); 107 | 108 | // percentage of losing party's deposit distributed to winning party in parameterizer 109 | set("pDispensationPct", _parameters[9]); 110 | 111 | // type of majority out of 100 necessary for candidate success 112 | set("voteQuorum", _parameters[10]); 113 | 114 | // type of majority out of 100 necessary for proposal success in parameterizer 115 | set("pVoteQuorum", _parameters[11]); 116 | 117 | // minimum length of time user has to wait to exit the registry 118 | set("exitTimeDelay", _parameters[12]); 119 | 120 | // maximum length of time user can wait to exit the registry 121 | set("exitPeriodLen", _parameters[13]); 122 | } 123 | 124 | // ----------------------- 125 | // TOKEN HOLDER INTERFACE 126 | // ----------------------- 127 | 128 | /** 129 | @notice propose a reparamaterization of the key _name's value to _value. 130 | @param _name the name of the proposed param to be set 131 | @param _value the proposed value to set the param to be set 132 | */ 133 | function proposeReparameterization(string _name, uint _value) public returns (bytes32) { 134 | uint deposit = get("pMinDeposit"); 135 | bytes32 propID = keccak256(abi.encodePacked(_name, _value)); 136 | 137 | if (keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("dispensationPct")) || 138 | keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("pDispensationPct"))) { 139 | require(_value <= 100); 140 | } 141 | 142 | require(!propExists(propID)); // Forbid duplicate proposals 143 | require(get(_name) != _value); // Forbid NOOP reparameterizations 144 | 145 | // attach name and value to pollID 146 | proposals[propID] = ParamProposal({ 147 | appExpiry: now.add(get("pApplyStageLen")), 148 | challengeID: 0, 149 | deposit: deposit, 150 | name: _name, 151 | owner: msg.sender, 152 | processBy: now.add(get("pApplyStageLen")) 153 | .add(get("pCommitStageLen")) 154 | .add(get("pRevealStageLen")) 155 | .add(PROCESSBY), 156 | value: _value 157 | }); 158 | 159 | require(token.transferFrom(msg.sender, this, deposit)); // escrow tokens (deposit amt) 160 | 161 | emit _ReparameterizationProposal(_name, _value, propID, deposit, proposals[propID].appExpiry, msg.sender); 162 | return propID; 163 | } 164 | 165 | /** 166 | @notice challenge the provided proposal ID, and put tokens at stake to do so. 167 | @param _propID the proposal ID to challenge 168 | */ 169 | function challengeReparameterization(bytes32 _propID) public returns (uint challengeID) { 170 | ParamProposal memory prop = proposals[_propID]; 171 | uint deposit = prop.deposit; 172 | 173 | require(propExists(_propID) && prop.challengeID == 0); 174 | 175 | //start poll 176 | uint pollID = voting.startPoll( 177 | get("pVoteQuorum"), 178 | get("pCommitStageLen"), 179 | get("pRevealStageLen") 180 | ); 181 | 182 | challenges[pollID] = Challenge({ 183 | challenger: msg.sender, 184 | rewardPool: SafeMath.sub(100, get("pDispensationPct")).mul(deposit).div(100), 185 | stake: deposit, 186 | resolved: false, 187 | winningTokens: 0 188 | }); 189 | 190 | proposals[_propID].challengeID = pollID; // update listing to store most recent challenge 191 | 192 | //take tokens from challenger 193 | require(token.transferFrom(msg.sender, this, deposit)); 194 | 195 | (uint commitEndDate, uint revealEndDate,,,) = voting.pollMap(pollID); 196 | 197 | emit _NewChallenge(_propID, pollID, commitEndDate, revealEndDate, msg.sender); 198 | return pollID; 199 | } 200 | 201 | /** 202 | @notice for the provided proposal ID, set it, resolve its challenge, or delete it depending on whether it can be set, has a challenge which can be resolved, or if its "process by" date has passed 203 | @param _propID the proposal ID to make a determination and state transition for 204 | */ 205 | function processProposal(bytes32 _propID) public { 206 | ParamProposal storage prop = proposals[_propID]; 207 | address propOwner = prop.owner; 208 | uint propDeposit = prop.deposit; 209 | 210 | 211 | // Before any token transfers, deleting the proposal will ensure that if reentrancy occurs the 212 | // prop.owner and prop.deposit will be 0, thereby preventing theft 213 | if (canBeSet(_propID)) { 214 | // There is no challenge against the proposal. The processBy date for the proposal has not 215 | // passed, but the proposal's appExpirty date has passed. 216 | set(prop.name, prop.value); 217 | emit _ProposalAccepted(_propID, prop.name, prop.value); 218 | delete proposals[_propID]; 219 | require(token.transfer(propOwner, propDeposit)); 220 | } else if (challengeCanBeResolved(_propID)) { 221 | // There is a challenge against the proposal. 222 | resolveChallenge(_propID); 223 | } else if (now > prop.processBy) { 224 | // There is no challenge against the proposal, but the processBy date has passed. 225 | emit _ProposalExpired(_propID); 226 | delete proposals[_propID]; 227 | require(token.transfer(propOwner, propDeposit)); 228 | } else { 229 | // There is no challenge against the proposal, and neither the appExpiry date nor the 230 | // processBy date has passed. 231 | revert(); 232 | } 233 | 234 | assert(get("dispensationPct") <= 100); 235 | assert(get("pDispensationPct") <= 100); 236 | 237 | // verify that future proposal appExpiry and processBy times will not overflow 238 | now.add(get("pApplyStageLen")) 239 | .add(get("pCommitStageLen")) 240 | .add(get("pRevealStageLen")) 241 | .add(PROCESSBY); 242 | 243 | delete proposals[_propID]; 244 | } 245 | 246 | /** 247 | @notice Claim the tokens owed for the msg.sender in the provided challenge 248 | @param _challengeID the challenge ID to claim tokens for 249 | */ 250 | function claimReward(uint _challengeID) public { 251 | Challenge storage challenge = challenges[_challengeID]; 252 | // ensure voter has not already claimed tokens and challenge results have been processed 253 | require(challenge.tokenClaims[msg.sender] == false); 254 | require(challenge.resolved == true); 255 | 256 | uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID); 257 | uint reward = voterReward(msg.sender, _challengeID); 258 | 259 | // subtract voter's information to preserve the participation ratios of other voters 260 | // compared to the remaining pool of rewards 261 | challenge.winningTokens -= voterTokens; 262 | challenge.rewardPool -= reward; 263 | 264 | // ensures a voter cannot claim tokens again 265 | challenge.tokenClaims[msg.sender] = true; 266 | 267 | emit _RewardClaimed(_challengeID, reward, msg.sender); 268 | require(token.transfer(msg.sender, reward)); 269 | } 270 | 271 | /** 272 | @dev Called by a voter to claim their rewards for each completed vote. 273 | Someone must call updateStatus() before this can be called. 274 | @param _challengeIDs The PLCR pollIDs of the challenges rewards are being claimed for 275 | */ 276 | function claimRewards(uint[] _challengeIDs) public { 277 | // loop through arrays, claiming each individual vote reward 278 | for (uint i = 0; i < _challengeIDs.length; i++) { 279 | claimReward(_challengeIDs[i]); 280 | } 281 | } 282 | 283 | // -------- 284 | // GETTERS 285 | // -------- 286 | 287 | /** 288 | @dev Calculates the provided voter's token reward for the given poll. 289 | @param _voter The address of the voter whose reward balance is to be returned 290 | @param _challengeID The ID of the challenge the voter's reward is being calculated for 291 | @return The uint indicating the voter's reward 292 | */ 293 | function voterReward(address _voter, uint _challengeID) 294 | public view returns (uint) { 295 | uint winningTokens = challenges[_challengeID].winningTokens; 296 | uint rewardPool = challenges[_challengeID].rewardPool; 297 | uint voterTokens = voting.getNumPassingTokens(_voter, _challengeID); 298 | return (voterTokens * rewardPool) / winningTokens; 299 | } 300 | 301 | /** 302 | @notice Determines whether a proposal passed its application stage without a challenge 303 | @param _propID The proposal ID for which to determine whether its application stage passed without a challenge 304 | */ 305 | function canBeSet(bytes32 _propID) view public returns (bool) { 306 | ParamProposal memory prop = proposals[_propID]; 307 | 308 | return (now > prop.appExpiry && now < prop.processBy && prop.challengeID == 0); 309 | } 310 | 311 | /** 312 | @notice Determines whether a proposal exists for the provided proposal ID 313 | @param _propID The proposal ID whose existance is to be determined 314 | */ 315 | function propExists(bytes32 _propID) view public returns (bool) { 316 | return proposals[_propID].processBy > 0; 317 | } 318 | 319 | /** 320 | @notice Determines whether the provided proposal ID has a challenge which can be resolved 321 | @param _propID The proposal ID whose challenge to inspect 322 | */ 323 | function challengeCanBeResolved(bytes32 _propID) view public returns (bool) { 324 | ParamProposal memory prop = proposals[_propID]; 325 | Challenge memory challenge = challenges[prop.challengeID]; 326 | 327 | return (prop.challengeID > 0 && challenge.resolved == false && voting.pollEnded(prop.challengeID)); 328 | } 329 | 330 | /** 331 | @notice Determines the number of tokens to awarded to the winning party in a challenge 332 | @param _challengeID The challengeID to determine a reward for 333 | */ 334 | function challengeWinnerReward(uint _challengeID) public view returns (uint) { 335 | if(voting.getTotalNumberOfTokensForWinningOption(_challengeID) == 0) { 336 | // Edge case, nobody voted, give all tokens to the challenger. 337 | return 2 * challenges[_challengeID].stake; 338 | } 339 | 340 | return (2 * challenges[_challengeID].stake) - challenges[_challengeID].rewardPool; 341 | } 342 | 343 | /** 344 | @notice gets the parameter keyed by the provided name value from the params mapping 345 | @param _name the key whose value is to be determined 346 | */ 347 | function get(string _name) public view returns (uint value) { 348 | return params[keccak256(abi.encodePacked(_name))]; 349 | } 350 | 351 | /** 352 | @dev Getter for Challenge tokenClaims mappings 353 | @param _challengeID The challengeID to query 354 | @param _voter The voter whose claim status to query for the provided challengeID 355 | */ 356 | function tokenClaims(uint _challengeID, address _voter) public view returns (bool) { 357 | return challenges[_challengeID].tokenClaims[_voter]; 358 | } 359 | 360 | // ---------------- 361 | // PRIVATE FUNCTIONS 362 | // ---------------- 363 | 364 | /** 365 | @dev resolves a challenge for the provided _propID. It must be checked in advance whether the _propID has a challenge on it 366 | @param _propID the proposal ID whose challenge is to be resolved. 367 | */ 368 | function resolveChallenge(bytes32 _propID) private { 369 | ParamProposal memory prop = proposals[_propID]; 370 | Challenge storage challenge = challenges[prop.challengeID]; 371 | 372 | // winner gets back their full staked deposit, and dispensationPct*loser's stake 373 | uint reward = challengeWinnerReward(prop.challengeID); 374 | 375 | challenge.winningTokens = voting.getTotalNumberOfTokensForWinningOption(prop.challengeID); 376 | challenge.resolved = true; 377 | 378 | if (voting.isPassed(prop.challengeID)) { // The challenge failed 379 | if(prop.processBy > now) { 380 | set(prop.name, prop.value); 381 | } 382 | emit _ChallengeFailed(_propID, prop.challengeID, challenge.rewardPool, challenge.winningTokens); 383 | require(token.transfer(prop.owner, reward)); 384 | } 385 | else { // The challenge succeeded or nobody voted 386 | emit _ChallengeSucceeded(_propID, prop.challengeID, challenge.rewardPool, challenge.winningTokens); 387 | require(token.transfer(challenges[prop.challengeID].challenger, reward)); 388 | } 389 | } 390 | 391 | /** 392 | @dev sets the param keted by the provided name to the provided value 393 | @param _name the name of the param to be set 394 | @param _value the value to set the param to be set 395 | */ 396 | function set(string _name, uint _value) private { 397 | params[keccak256(abi.encodePacked(_name))] = _value; 398 | } 399 | } 400 | 401 | --------------------------------------------------------------------------------