├── .eslintignore ├── test ├── unit │ ├── registry │ │ ├── tokensClaims.js │ │ ├── canBeWhitelisted.js │ │ ├── challeneCanBeResolved.js │ │ ├── challengeCanBeResolved.js │ │ ├── Registry.js │ │ ├── isWhitelisted.js │ │ ├── tokenClaims.js │ │ ├── determineReward.js │ │ ├── appWasMade.js │ │ ├── userStories.js │ │ ├── deposit.js │ │ ├── updateStatus.js │ │ ├── exit.js │ │ ├── updateStatuses.js │ │ ├── withdraw.js │ │ ├── apply.js │ │ ├── challenge.js │ │ └── claimReward.js │ ├── parameterizer │ │ ├── get.js │ │ ├── propExists.js │ │ ├── canBeSet.js │ │ ├── challengeCanBeResolved.js │ │ ├── voterReward.js │ │ ├── tokenClaims.js │ │ ├── proposeReparameterization.js │ │ ├── claimRewards.js │ │ ├── challengeReparameterization.js │ │ ├── processProposal.js │ │ └── claimReward.js │ ├── ParameterizerFactory │ │ ├── newParameterizerWithToken.js │ │ └── newParameterizerBYOToken.js │ └── RegistryFactory │ │ ├── newRegistryWithToken.js │ │ └── newRegistryBYOToken.js ├── fcrJsConfig.json ├── exec.js ├── FutarchyChallenge.js └── utils.js ├── .gitattributes ├── .eslintrc.json ├── .gitignore ├── scripts ├── runGanache.sh ├── scenarios.sh ├── runTestrpc.sh └── test.sh ├── migrations ├── 1_initial_migration.js ├── 4_deploy_libs.js ├── 5_optional_plcr_factory.js ├── 8_deploy_registry_factory.js ├── 7_parameterizer_factory.js ├── 3_token.js ├── 2_1_deploy_dx_exchange.js ├── 2_2_deploy_dx_mock.js └── 6_deploy_futarchy_challenge_factory.js ├── .babelrc ├── contracts ├── Challenge │ ├── ChallengeInterface.sol │ ├── ChallengeFactoryInterface.sol │ ├── Oracles │ │ ├── TimedOracle.sol │ │ ├── CentralizedTimedOracleFactory.sol │ │ ├── ScalarPriceOracleFactory.sol │ │ ├── CentralizedTimedOracle.sol │ │ ├── ScalarPriceOracle.sol │ │ └── DutchExchangeMock.sol │ ├── FutarchyChallengeFactory.sol │ └── FutarchyChallenge.sol ├── IDutchExchange.sol ├── Migrations.sol ├── Imports.sol ├── RegistryFactory.sol └── ParameterizerFactory.sol ├── .solcover.js ├── ethpm.json ├── truffle.js ├── conf └── config.json ├── README.md ├── package.json └── LICENSE.txt /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/**/*.js 2 | -------------------------------------------------------------------------------- /test/unit/registry/tokensClaims.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | build/* 3 | truffle.js 4 | secrets.json 5 | *.swp 6 | installed_contracts/ 7 | coverage/ 8 | coverage.json 9 | -------------------------------------------------------------------------------- /scripts/runGanache.sh: -------------------------------------------------------------------------------- 1 | ganache-cli \ 2 | -l 7000000 \ 3 | -m "skull reason path dust cost unaware unknown outer shaft spell outer typical" 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "env": { 4 | "commonjs": { 5 | "plugins": [ 6 | ["transform-es2015-modules-commonjs"] 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /contracts/Challenge/ChallengeInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract ChallengeInterface { 4 | function ended() public view returns (bool); 5 | function passed() public view returns (bool); 6 | function winnerRewardAmount() public view returns (uint256); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/Challenge/ChallengeFactoryInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ChallengeInterface.sol"; 4 | 5 | contract ChallengeFactoryInterface { 6 | function createChallenge(address registry, address challenger, address listingOwner) external returns (ChallengeInterface); 7 | } 8 | -------------------------------------------------------------------------------- /test/fcrJsConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "local": { 3 | "tokenAddress": "", 4 | "registryAddress": "", 5 | "LMSRMarketMakerAddress": "", 6 | "web3Url": "http://localhost:8545", 7 | "socketWeb3Url": "ws://localhost:8545", 8 | "defaultOptions": { 9 | "gas": 6930000 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /contracts/IDutchExchange.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract IDutchExchange { 4 | function getPriceInPastAuction(address token1, address token2, uint auctionIndex) public view returns (uint num, uint den); 5 | function getAuctionIndex(address token1, address token2) public view returns (uint auctionIndex); 6 | } 7 | -------------------------------------------------------------------------------- /migrations/4_deploy_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/unit/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/unit/registry/challeneCanBeResolved.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 | -------------------------------------------------------------------------------- /test/unit/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 | -------------------------------------------------------------------------------- /contracts/Challenge/Oracles/TimedOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract TimedOracle { 4 | 5 | // ============ 6 | // STATE: 7 | // ============ 8 | // GLOBAL VARIABLES 9 | uint public resolutionDate; 10 | 11 | modifier resolutionDatePassed() { 12 | require(now > resolutionDate); 13 | _; 14 | } 15 | 16 | function TimedOracle(uint _resolutionDate) public { 17 | resolutionDate = _resolutionDate; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /migrations/5_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": "1.0.0", 4 | "description": "A generic, string-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": "1.3.0" 25 | }, 26 | "license": "Apache 2.0" 27 | } 28 | -------------------------------------------------------------------------------- /test/exec.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | require('babel-polyfill') 3 | 4 | const fs = require('fs') 5 | const config = require('../conf/config.json') 6 | const fcrJsConfig = require('./fcrJsConfig.json') 7 | const utils = require('./utils.js')(artifacts) 8 | 9 | module.exports = (callback) => { 10 | const scenarioName = process.argv[4] 11 | const scriptPath = `./scenarios/${scenarioName}.js` 12 | const script = require(scriptPath) 13 | 14 | script(artifacts, web3, config, fcrJsConfig, utils).then(() => { 15 | callback() 16 | }, (err) => { 17 | callback(err) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /migrations/8_deploy_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 | -------------------------------------------------------------------------------- /scripts/scenarios.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # really would like to modify testrpc to add: 4 | # - account params from a JSON file 5 | # - `testrpc start` and `testrpc stop`, so these custom scripts don't 6 | # have to be added to every project 7 | 8 | output=$(nc -z localhost 8545; echo $?) 9 | [ $output -eq "0" ] && ganache_running=true 10 | if [ ! $ganache_running ]; then 11 | echo "Starting our own ganache instance" 12 | ganache-cli \ 13 | -l 7000000 \ 14 | -m "skull reason path dust cost unaware unknown outer shaft spell outer typical" \ 15 | > /dev/null & 16 | ganache_pid=$! 17 | fi 18 | 19 | npm run migrate-reset 20 | 21 | npm run scenario addListing 22 | 23 | kill -9 $ganache_pid 24 | -------------------------------------------------------------------------------- /migrations/7_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.24; 2 | 3 | // This is a useless import, but it forces EIP20.sol to be compiled. We need its build file for 4 | // the test pipeline. 5 | import "tokens/eip20/EIP20.sol"; 6 | 7 | contract Migrations { 8 | address public owner; 9 | uint public last_completed_migration; 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function Migrations() public { 16 | owner = msg.sender; 17 | } 18 | 19 | function setCompleted(uint completed) public restricted { 20 | last_completed_migration = completed; 21 | } 22 | 23 | function upgrade(address new_address) public restricted { 24 | Migrations upgraded = Migrations(new_address); 25 | upgraded.setCompleted(last_completed_migration); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/Imports.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import '@gnosis.pm/gnosis-core-contracts/contracts/Tokens/EtherToken.sol'; 4 | import '@gnosis.pm/gno-token/contracts/TokenGNO.sol'; 5 | import '@gnosis.pm/owl-token/contracts/TokenOWLProxy.sol'; 6 | import '@gnosis.pm/owl-token/contracts/OWLAirdrop.sol'; 7 | import '@gnosis.pm/gnosis-core-contracts/contracts/Markets/StandardMarketFactory.sol'; 8 | 9 | import '@gnosis.pm/dx-contracts/contracts/DutchExchange.sol'; 10 | import '@gnosis.pm/dx-contracts/contracts/DutchExchangeProxy.sol'; 11 | import '@gnosis.pm/dx-contracts/contracts/TokenFRT.sol'; 12 | import '@gnosis.pm/dx-contracts/contracts/Oracle/PriceFeed.sol'; 13 | import '@gnosis.pm/dx-contracts/contracts/Oracle/PriceOracleInterface.sol'; 14 | import '@gnosis.pm/dx-contracts/contracts/ForTestingOnly/TokenOMG.sol'; 15 | import '@gnosis.pm/dx-contracts/contracts/ForTestingOnly/TokenRDN.sol'; 16 | 17 | contract Imports { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /test/FutarchyChallenge.js: -------------------------------------------------------------------------------- 1 | // contract('FutarchyChallenge', (accounts) => { 2 | // describe('when deployed correctly, ', () => { 3 | // it('sets the correct token address') 4 | // it('sets the correct stake amount') 5 | // it('sets the correct trading period') 6 | // it('sets the correct timeToPriceResolution') 7 | // it('sets the correct upperBound') 8 | // it('sets the correct lowerBound') 9 | // it('sets the correct futarchyOracleFactory') 10 | // it('sets the correct priceOracleFactory') 11 | // it('sets the correct lsmrtMarketMaker') 12 | // it('sets isStarted to false') 13 | // it('sets marketsAreClosed to false') 14 | // it('sets isFunded to false') 15 | // } 16 | // 17 | // describe('when deployed with incorrect parameters, ', () => { 18 | // it('throws if tradingPeriod < 1') 19 | // it('throws if timeToPriceResolution < tradingPeriod') // might be unnecessary 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /test/unit/parameterizer/get.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const utils = require('../../utils')(artifacts); 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 | before(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 | -------------------------------------------------------------------------------- /scripts/runTestrpc.sh: -------------------------------------------------------------------------------- 1 | testrpc \ 2 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200,1000000000000000000000000" \ 3 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201,1000000000000000000000000" \ 4 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202,1000000000000000000000000" \ 5 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203,1000000000000000000000000" \ 6 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204,1000000000000000000000000" \ 7 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205,1000000000000000000000000" \ 8 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206,1000000000000000000000000" \ 9 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207,1000000000000000000000000" \ 10 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208,1000000000000000000000000" \ 11 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" \ 12 | --port=8545 13 | -------------------------------------------------------------------------------- /contracts/Challenge/Oracles/CentralizedTimedOracleFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | import "../Oracles/CentralizedTimedOracle.sol"; 3 | 4 | 5 | /// @title Centralized oracle factory contract - Allows to create centralized oracle contracts 6 | /// @author Stefan George - 7 | contract CentralizedTimedOracleFactory { 8 | 9 | /* 10 | * Events 11 | */ 12 | event CentralizedTimedOracleCreation(address indexed creator, CentralizedTimedOracle centralizedTimedOracle, bytes ipfsHash, uint resolutionDate); 13 | 14 | /* 15 | * Public functions 16 | */ 17 | /// @dev Creates a new centralized oracle contract 18 | /// @param ipfsHash Hash idxentifying off chain event description 19 | /// @return Oracle contract 20 | function createCentralizedTimedOracle(bytes ipfsHash, uint resolutionDate) 21 | external 22 | returns (CentralizedTimedOracle centralizedTimedOracle) 23 | { 24 | centralizedTimedOracle = new CentralizedTimedOracle(msg.sender, ipfsHash, resolutionDate); 25 | CentralizedTimedOracleCreation(msg.sender, centralizedTimedOracle, ipfsHash, resolutionDate); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/Challenge/Oracles/ScalarPriceOracleFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.15; 2 | import "./ScalarPriceOracle.sol"; 3 | 4 | contract ScalarPriceOracleFactory { 5 | 6 | /* 7 | * Events 8 | */ 9 | event ScalarPriceOracleCreation(ScalarPriceOracle scalarPriceOracle, uint resolutionDate); 10 | 11 | address token; 12 | address comparatorToken; 13 | address dutchExchange; 14 | 15 | function ScalarPriceOracleFactory( 16 | address _token, 17 | address _comparatorToken, 18 | address _dutchExchange 19 | ) public { 20 | token = _token; 21 | comparatorToken = _comparatorToken; 22 | dutchExchange = _dutchExchange; 23 | } 24 | 25 | /* 26 | * Public functions 27 | */ 28 | /// @dev Creates a new centralized oracle contract 29 | /// @param _resolutionDate date of price resolution 30 | /// @return Oracle contract 31 | function createScalarPriceOracle(uint _resolutionDate) 32 | external 33 | returns (ScalarPriceOracle scalarPriceOracle) 34 | { 35 | scalarPriceOracle = new ScalarPriceOracle(_resolutionDate, dutchExchange, token, comparatorToken); 36 | ScalarPriceOracleCreation(scalarPriceOracle, _resolutionDate); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/unit/parameterizer/propExists.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const utils = require('../../utils')(artifacts); 4 | 5 | contract('Parameterizer', (accounts) => { 6 | describe('Function: propExists', () => { 7 | const [proposer] = accounts; 8 | 9 | let token; 10 | let parameterizer; 11 | 12 | before(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 | -------------------------------------------------------------------------------- /migrations/3_token.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const Token = artifacts.require('tokens/eip20/EIP20.sol'); 4 | 5 | const fs = require('fs'); 6 | 7 | module.exports = (deployer, network) => { 8 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 9 | 10 | async function giveTokensTo(tokenHolders) { 11 | if (tokenHolders.length === 0) { return; } 12 | const token = await Token.deployed(); 13 | const tokenHolder = tokenHolders[0]; 14 | 15 | // eslint-disable-next-line 16 | console.log(`Allocating ${tokenHolder.amount} ${config.token.symbol} tokens to ` + 17 | `${tokenHolder.address}.`); 18 | 19 | await token.transfer(tokenHolder.address, tokenHolder.amount); 20 | 21 | const receipt = await giveTokensTo(tokenHolders.slice(1)); 22 | return receipt 23 | } 24 | 25 | if (config.token.deployToken) { 26 | return deployer.deploy( 27 | Token, config.token.supply, config.token.name, config.token.decimals, 28 | config.token.symbol, 29 | ) 30 | .then(async () => { 31 | const receipt = await giveTokensTo(config.token.tokenHolders) 32 | return receipt 33 | }) 34 | } else { 35 | // eslint-disable-next-line 36 | console.log('skipping optional token deploy and using the token at address ' + 37 | `${config.token.address} on network ${network}.`); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 7 | 8 | contract('Registry', (accounts) => { 9 | describe('Function: Registry (constructor)', () => { 10 | let token; 11 | let parameterizer; 12 | let registry; 13 | 14 | before(async () => { 15 | const { 16 | paramProxy, registryProxy, tokenInstance, 17 | } = await utils.getProxies(); 18 | parameterizer = paramProxy; 19 | registry = registryProxy; 20 | token = tokenInstance; 21 | 22 | await utils.approveProxies(accounts, token, false, parameterizer, registry); 23 | }); 24 | 25 | it('should instantiate storage variables with the values in the config file', async () => { 26 | assert.strictEqual((await registry.token.call()), token.address, 'The token storage ' + 27 | 'variable is improperly initialized'); 28 | assert.strictEqual( 29 | (await registry.parameterizer.call()), parameterizer.address, 30 | 'The parameterizer storage variable is improperly initialized', 31 | ); 32 | assert.strictEqual( 33 | (await registry.name.call()), config.name, 34 | 'The name storage variable is improperly initialized', 35 | ); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 9 | 10 | contract('Registry', (accounts) => { 11 | describe('Function: isWhitelisted', () => { 12 | const [applicant] = accounts; 13 | 14 | let token; 15 | let registry; 16 | 17 | before(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 | require('babel-register') 2 | require('babel-polyfill') 3 | 4 | const HDWalletProvider = require('truffle-hdwallet-provider'); 5 | const fs = require('fs'); 6 | 7 | let secrets; 8 | let mnemonic = ''; 9 | 10 | if (fs.existsSync('secrets.json')) { 11 | secrets = JSON.parse(fs.readFileSync('secrets.json', 'utf8')); 12 | ({ mnemonic } = secrets); 13 | } 14 | 15 | module.exports = { 16 | networks: { 17 | development: { 18 | host: 'localhost', 19 | network_id: '*', 20 | port: 8545, 21 | gas: 6500000, 22 | gasPrice: 5000000000 23 | }, 24 | testing: { 25 | host: 'localhost', 26 | network_id: '*', 27 | port: 8545, 28 | gas: 6500000, 29 | gasPrice: 5000000000 30 | }, 31 | // mainnet: { 32 | // provider: new HDWalletProvider(mnemonic, 'https://mainnet.infura.io'), 33 | // network_id: '1', 34 | // gas: 4500000, 35 | // gasPrice: 10000000000, 36 | // }, 37 | rinkeby: { 38 | provider: new HDWalletProvider(mnemonic, 'https://rinkeby.infura.io'), 39 | network_id: '*', 40 | gas: 6500000, 41 | gasPrice: 5000000000, 42 | }, 43 | // config for solidity-coverage 44 | // coverage: { 45 | // host: 'localhost', 46 | // network_id: '*', 47 | // port: 7545, // <-- If you change this, also set the port option in .solcover.js. 48 | // gas: 0xfffffffffff, // <-- Use this high gas value 49 | // gasPrice: 0x01, // <-- Use this low gas price 50 | // }, 51 | }, 52 | solc: { 53 | optimizer: { 54 | enabled: true 55 | } 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # really would like to modify testrpc to add: 4 | # - account params from a JSON file 5 | # - `testrpc start` and `testrpc stop`, so these custom scripts don't 6 | # have to be added to every project 7 | 8 | output=$(nc -z localhost 8545; echo $?) 9 | [ $output -eq "0" ] && trpc_running=true 10 | if [ ! $trpc_running ]; then 11 | echo "Starting our own testrpc node instance" 12 | # we give each account 1M ether, needed for high-value tests 13 | testrpc \ 14 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200,1000000000000000000000000" \ 15 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201,1000000000000000000000000" \ 16 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202,1000000000000000000000000" \ 17 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203,1000000000000000000000000" \ 18 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204,1000000000000000000000000" \ 19 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205,1000000000000000000000000" \ 20 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206,1000000000000000000000000" \ 21 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207,1000000000000000000000000" \ 22 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208,1000000000000000000000000" \ 23 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" \ 24 | --port=8545 \ 25 | > /dev/null & 26 | trpc_pid=$! 27 | fi 28 | npm run test:truffle 29 | kill -9 $trpc_pid 30 | -------------------------------------------------------------------------------- /contracts/Challenge/Oracles/CentralizedTimedOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import './TimedOracle.sol'; 4 | import '@gnosis.pm/gnosis-core-contracts/contracts/Oracles/Oracle.sol'; 5 | import '@gnosis.pm/gnosis-core-contracts/contracts/Oracles/CentralizedOracleFactory.sol'; 6 | 7 | 8 | //TODO: Make TimedOracle AND PriceOracle separately to PR for Gnosis 9 | // right now this is a combination of centralized and Timed oracle 10 | contract CentralizedTimedOracle is Oracle, TimedOracle { 11 | 12 | event OutcomeAssignment(int outcome); 13 | 14 | address public owner; 15 | bytes public ipfsHash; 16 | bool public isSet; 17 | int public outcome; 18 | 19 | modifier isOwner () { 20 | // Only owner is allowed to proceed 21 | require(msg.sender == owner); 22 | _; 23 | } 24 | 25 | function CentralizedTimedOracle( 26 | address _owner, 27 | bytes _ipfsHash, 28 | uint _resolutionDate 29 | ) public 30 | TimedOracle(_resolutionDate) 31 | { 32 | owner = _owner; 33 | ipfsHash = _ipfsHash; 34 | } 35 | 36 | /// @dev Sets event outcome 37 | /// @param _outcome Event outcome 38 | function setOutcome(int _outcome) 39 | public 40 | resolutionDatePassed 41 | isOwner 42 | { 43 | // Result is not set yet 44 | require(!isSet); 45 | isSet = true; 46 | outcome = _outcome; 47 | OutcomeAssignment(_outcome); 48 | } 49 | 50 | /// @dev Returns if winning outcome is set 51 | /// @return Is outcome set? 52 | function isOutcomeSet() 53 | public 54 | view 55 | returns (bool) 56 | { 57 | return isSet; 58 | } 59 | 60 | /// @dev Returns outcome 61 | /// @return Outcome 62 | function getOutcome() 63 | public 64 | view 65 | returns (int) 66 | { 67 | return outcome; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/unit/parameterizer/canBeSet.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../../utils')(artifacts); 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 | before(async () => { 17 | const { paramProxy, tokenInstance } = await utils.getProxies(); 18 | parameterizer = paramProxy; 19 | token = tokenInstance; 20 | await utils.approveProxies(accounts, token, false, parameterizer, false); 21 | }); 22 | 23 | it('should true if a proposal passed its application stage with no challenge', async () => { 24 | const propID = await utils.proposeReparamAndGetPropID('voteQuorum', '51', proposer, parameterizer); 25 | 26 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 27 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 28 | 29 | const result = await parameterizer.canBeSet(propID); 30 | assert.strictEqual(result, true, 'should have returned true because enough time has passed'); 31 | }); 32 | 33 | it('should false if a proposal did not pass its application stage with no challenge', async () => { 34 | const propID = await utils.proposeReparamAndGetPropID('dispensationPct', '58', proposer, parameterizer); 35 | 36 | const betterBeFalse = await parameterizer.canBeSet(propID); 37 | assert.strictEqual(betterBeFalse, false, 'should have returned false because not enough time has passed'); 38 | 39 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 40 | 41 | const result = await parameterizer.canBeSet(propID); 42 | assert.strictEqual(result, true, 'should have been able to set because commit period is done'); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/unit/parameterizer/challengeCanBeResolved.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../../utils')(artifacts); 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 | before(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/unit/registry/tokenClaims.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract artifacts */ 3 | const Registry = artifacts.require('Registry.sol'); 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 | const utils = require('../../utils.js')(artifacts); 12 | 13 | const bigTen = number => new BN(number.toString(10), 10); 14 | 15 | contract('Registry', (accounts) => { 16 | describe('Function: tokenClaims', () => { 17 | const minDeposit = bigTen(paramConfig.minDeposit); 18 | const [applicant, challenger, voter] = accounts; 19 | 20 | it('should report properly whether a voter has claimed tokens', async () => { 21 | const registry = await Registry.deployed(); 22 | const voting = await utils.getVoting(); 23 | const listing = utils.getListingHash('claims.com'); 24 | 25 | await utils.addToWhitelist(listing, minDeposit, applicant); 26 | 27 | const pollID = await utils.challengeAndGetPollID(listing, challenger); 28 | 29 | await utils.commitVote(pollID, '0', '10', '420', voter); 30 | await utils.increaseTime(paramConfig.commitStageLength + 1); 31 | 32 | await utils.as(voter, voting.revealVote, pollID, '0', '420'); 33 | await utils.increaseTime(paramConfig.revealStageLength + 1); 34 | 35 | await utils.as(challenger, registry.updateStatus, listing); 36 | 37 | const initialHasClaimed = await registry.tokenClaims.call(pollID, voter); 38 | assert.strictEqual(initialHasClaimed, false, 'The voter is purported to have claimed ' + 39 | 'their reward, when in fact they have not'); 40 | 41 | await utils.as(voter, registry.claimReward, pollID, '420'); 42 | 43 | const finalHasClaimed = await registry.tokenClaims.call(pollID, voter); 44 | assert.strictEqual(finalHasClaimed, true, 'The voter is purported to not have claimed ' + 45 | 'their reward, when in fact they have'); 46 | }); 47 | }); 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /conf/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "paramDefaults": { 3 | "minDeposit": 10, 4 | "pMinDeposit": 100, 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 | }, 16 | "name": "Futarchy Curated Registry", 17 | "token": { 18 | "address": "0x337cDDa6D41A327c5ad456166CCB781a9722AFf3", 19 | "deployToken": true, 20 | "decimals": "18", 21 | "name": "Futarchy Curated Registry Coin", 22 | "symbol": "FCR", 23 | "supply": "1000000000000000000000000000", 24 | "tokenHolders": [ 25 | { "address": "0xE3717CCDfbE15649F44FF27a81BE38d4648E1Ef0", 26 | "amount": "100000000000000000000000000" }, 27 | { "address": "0x3Fda7cbA5245334158d2420840C61d6aAcf010BF", 28 | "amount": "100000000000000000000000000" }, 29 | { "address": "0x0500a431Bd8Eb01C31422E6f068822a0b6718669", 30 | "amount": "100000000000000000000000000" }, 31 | { "address": "0x6a0a5E3C4b50A57190D26463c542F2015932A3c1", 32 | "amount": "100000000000000000000000000" }, 33 | { "address": "0x088a1D5b8C4e3207b4C24Bd563Be97EfDB5EdfCa", 34 | "amount": "100000000000000000000000000" }, 35 | { "address": "0x3E941B8a1A0419e11fE2F7206df554d3d710Fd7D", 36 | "amount": "100000000000000000000000000" }, 37 | { "address": "0x4022640c28D39B263E2fe805f78F6cdF13ccc350", 38 | "amount": "100000000000000000000000000" }, 39 | { "address": "0x5Dc0f8A46D2C9bF3fAda997c95dA881bB834b0dE", 40 | "amount": "100000000000000000000000000" }, 41 | { "address": "0x35ED675fed20c3d039a4C3Bc6d4704e53ba6E562", 42 | "amount": "100000000000000000000000000" }, 43 | { "address": "0x5663B1Bde9065895320b1aF983cE3d1d0E33171c", 44 | "amount": "100000000000000000000000000" } 45 | ] 46 | }, 47 | "challengeFactory": { 48 | "tradingPeriod": 3600, 49 | "timeToPriceResolution": 86400, 50 | "stakeAmount": 10 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/Challenge/Oracles/ScalarPriceOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import '@gnosis.pm/gnosis-core-contracts/contracts/Oracles/Oracle.sol'; 4 | import "../../IDutchExchange.sol"; 5 | 6 | contract ScalarPriceOracle is Oracle { 7 | 8 | event OutcomeAssignment(int outcome); 9 | 10 | bool public isSet; 11 | int public outcome; 12 | uint public resolutionDate; 13 | address public token; 14 | address public comparatorToken; 15 | IDutchExchange public dutchExchange; 16 | 17 | function ScalarPriceOracle( 18 | uint _resolutionDate, 19 | address _dutchExchange, 20 | address _token, 21 | address _comparatorToken 22 | ) public 23 | { 24 | resolutionDate = _resolutionDate; 25 | dutchExchange = IDutchExchange(_dutchExchange); 26 | token = _token; 27 | comparatorToken = _comparatorToken; 28 | 29 | } 30 | 31 | /// @dev Sets event outcome 32 | function setOutcome() 33 | public 34 | { 35 | require(resolutionDate <= now); 36 | require(!isSet); 37 | 38 | outcome = calculateAveragePrice(); 39 | isSet = true; 40 | OutcomeAssignment(outcome); 41 | } 42 | 43 | /// @dev Returns if winning outcome is set 44 | /// @return Is outcome set? 45 | function isOutcomeSet() 46 | public 47 | view 48 | returns (bool) 49 | { 50 | return isSet; 51 | } 52 | 53 | /// @dev Returns outcome 54 | /// @return Outcome 55 | function getOutcome() 56 | public 57 | view 58 | returns (int) 59 | { 60 | return outcome; 61 | } 62 | 63 | function calculateAveragePrice() private returns(int avgPrice) { 64 | uint currentAuctionIndex = dutchExchange.getAuctionIndex(token, comparatorToken); 65 | uint firstReferencedIndex = currentAuctionIndex - NUM_PRICE_POINTS; 66 | uint NUM_PRICE_POINTS = 5; 67 | 68 | uint i = 0; 69 | uint num; 70 | uint den; 71 | uint sumPrice; 72 | while(i < NUM_PRICE_POINTS) { 73 | (num, den) = dutchExchange.getPriceInPastAuction(token, comparatorToken, firstReferencedIndex + i); 74 | sumPrice += (num * 10**18)/(den); 75 | i++; 76 | } 77 | avgPrice = int(sumPrice)/int(NUM_PRICE_POINTS); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/unit/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 | before(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 | ]; 42 | const parameterizerReceipt = await parameterizerFactory.newParameterizerWithToken( 43 | tokenParams.supply, 44 | tokenParams.name, 45 | tokenParams.decimals, 46 | tokenParams.symbol, 47 | parameters, 48 | { from: accounts[0] }, 49 | ); 50 | const { creator, token } = parameterizerReceipt.logs[0].args; 51 | 52 | const tokenInstance = await Token.at(token); 53 | const actualName = await tokenInstance.name.call(); 54 | assert.strictEqual(actualName, tokenParams.name, 'token.name is incorrect'); 55 | 56 | // verify: parameterizer's creator 57 | assert.strictEqual(creator, accounts[0], 'the creator emitted in the newParameterizer event ' + 58 | 'not correspond to the one which sent the creation transaction'); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /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 string-keyed [token-curated registry (TCR)](https://medium.com/@ilovebagels/token-curated-registries-1-0-61a232f8dac7). 6 | 7 | ## Initialize 8 | The only environmental dependency you need is Node. Presently we can guarantee this all works with Node 8. 9 | ``` 10 | npm install 11 | npm run compile 12 | ``` 13 | 14 | ## Tests 15 | 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`. 16 | 17 | ## Composition of the repo 18 | 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. 19 | 20 | ## Deploying your own TCR 21 | To deploy your own TCR, first open up `conf/config.json`. The `paramDefaults` object in the config JSON will specify the starting parameters your TCR is deployed with. In the `token` object, set `deployToken` to `true` if you want to deploy this TCR's token as part of the TCR deployment. You can specifiy initial recipients of the token in the `tokenHolders` array. If you have already deployed a token, set `deployToken` to `false` and provide the token's address in the `address` property. The token should be EIP20. 22 | 23 | The `package.json` includes scripts for deploying to rinkeby and mainnet. Modify `truffle.js` and `package.json` if you need other networks. You'll need a `secrets.json` file with a funded mnemonic on the `m/44'/60'/0'/0/0` HD path in the root of the repo to deploy. Your `secrets.json should look like this: 24 | ``` 25 | { 26 | "mnemonic": "my good mnemonic" 27 | } 28 | ``` 29 | You can use [https://iancoleman.io/bip39/](https://iancoleman.io/bip39/) to generate a mnemonic and derive its `m/44'/60'/0'/0/0` address. 30 | 31 | ## Packages 32 | 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. All packages are installed automatically when running `npm run compile`. 33 | 34 | -------------------------------------------------------------------------------- /test/unit/registry/determineReward.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract artifacts */ 3 | const Registry = artifacts.require('Registry.sol'); 4 | 5 | const fs = require('fs'); 6 | 7 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 8 | const paramConfig = config.paramDefaults; 9 | 10 | const utils = require('../../utils.js')(artifacts); 11 | 12 | contract('Registry', (accounts) => { 13 | describe('Function: determineReward', () => { 14 | const [applicant, challenger] = accounts; 15 | it('should revert if the challenge has already been resolved', async () => { 16 | const registry = await Registry.deployed(); 17 | const listing = utils.getListingHash('failure.net'); 18 | 19 | // Apply 20 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 21 | // Challenge 22 | const challengeID = await utils.challengeAndGetPollID(listing, challenger); 23 | // Resolve challenge 24 | await utils.increaseTime(paramConfig.commitStageLength + paramConfig.revealStageLength + 1); 25 | await registry.updateStatus(listing); 26 | 27 | // Verify that the challenge has been resolved 28 | const challenge = await registry.challenges.call(challengeID); 29 | const resolved = challenge[2]; 30 | assert.strictEqual(resolved, true, 'Challenge has not been resolved'); 31 | 32 | // Try to determine reward 33 | try { 34 | await utils.as(challenger, registry.determineReward, challengeID); 35 | } catch (err) { 36 | assert(utils.isEVMException(err), err.toString()); 37 | return; 38 | } 39 | assert(false, 'determined reward after challenge already resolved'); 40 | }); 41 | 42 | it('should revert if the poll has not ended yet', async () => { 43 | const registry = await Registry.deployed(); 44 | const listing = utils.getListingHash('failure.net'); 45 | 46 | // Apply 47 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 48 | // Challenge 49 | const challengeID = await utils.challengeAndGetPollID(listing, challenger); 50 | 51 | try { 52 | await utils.as(challenger, registry.determineReward, challengeID); 53 | } catch (err) { 54 | assert(utils.isEVMException(err), err.toString()); 55 | return; 56 | } 57 | assert(false, 'determined reward before poll has ended'); 58 | }); 59 | }); 60 | }); 61 | 62 | -------------------------------------------------------------------------------- /migrations/2_1_deploy_dx_exchange.js: -------------------------------------------------------------------------------- 1 | /* eslint no-multi-spaces: 0, no-console: 0 */ 2 | 3 | const dxMigrateDependencies = require('@gnosis.pm/dx-contracts/src/migrations/2_migrate_dependencies') 4 | const dxDeployPriceFeed = require('@gnosis.pm/dx-contracts/src/migrations/3_deploy_price_feed') 5 | const deployFrt = require('@gnosis.pm/dx-contracts/src/migrations/4_deploy_FRT') 6 | const deployDx = require('@gnosis.pm/dx-contracts/src/migrations/5_deploy_DX') 7 | const setupDx = require('@gnosis.pm/dx-contracts/src/migrations/6_setup_DX') 8 | const setDxAsFrtMinter = require('@gnosis.pm/dx-contracts/src/migrations/7_set_DX_as_FRT_minter') 9 | 10 | module.exports = (deployer, network, accounts) => { 11 | if (network !== 'development') { 12 | return deployer.then(() => { 13 | logDxContractMigration('2_migrate_dependencies') 14 | return dxMigrateDependencies({ 15 | artifacts, 16 | deployer, 17 | network, 18 | accounts, 19 | web3 20 | }) 21 | }) 22 | .then(() => { 23 | logDxContractMigration('3_deploy_price_feed') 24 | return dxDeployPriceFeed({ 25 | artifacts, 26 | deployer, 27 | network, 28 | accounts, 29 | web3, 30 | ethUsdPrice: process.env.ETH_USD_PRICE, 31 | feedExpirePeriodDays: process.env.FEED_EXPIRE_PERIOD_DAYS 32 | }) 33 | }) 34 | .then(() => { 35 | logDxContractMigration('4_deploy_FRT') 36 | return deployFrt({ 37 | artifacts, 38 | deployer, 39 | network, 40 | accounts 41 | }) 42 | }) 43 | .then(() => { 44 | logDxContractMigration('5_deploy_DX') 45 | return deployDx({ 46 | artifacts, 47 | deployer, 48 | network, 49 | accounts 50 | }) 51 | }) 52 | .then(() => { 53 | logDxContractMigration('6_setup_DX') 54 | return setupDx({ 55 | artifacts, 56 | deployer, 57 | network, 58 | accounts, 59 | thresholdNewTokenPairUsd: process.env.THRESHOLD_NEW_TOKEN_PAIR_USD, 60 | thresholdAuctionStartUsd: process.env.THRESHOLD_AUCTION_START_USD 61 | }) 62 | }) 63 | .then(() => { 64 | logDxContractMigration('7_set_DX_as_FRT_minter') 65 | return setDxAsFrtMinter({ 66 | artifacts, 67 | deployer, 68 | network, 69 | accounts 70 | }) 71 | }) 72 | } 73 | } 74 | 75 | function logDxContractMigration (msg) { 76 | console.log(`Running dx-contract migration: ${msg}`) 77 | } 78 | -------------------------------------------------------------------------------- /test/unit/parameterizer/voterReward.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../../utils')(artifacts); 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 | before(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, '420'); // 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, '420'); 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/unit/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 | before(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 | ]; 50 | const parameterizerReceipt = await parameterizerFactory 51 | .newParameterizerBYOToken(token.address, parameters, { from: accounts[0] }); 52 | const parameterizer = Parameterizer.at(parameterizerReceipt.logs[0].args.parameterizer); 53 | const { creator } = parameterizerReceipt.logs[0].args; 54 | 55 | // verify: parameterizer's token 56 | const parameterizerToken = await parameterizer.token.call(); 57 | assert.strictEqual( 58 | parameterizerToken, 59 | token.address, 60 | 'the token connected to parameterizer is incorrect', 61 | ); 62 | // verify: parameterizer's creator 63 | assert.strictEqual(creator, accounts[0], 'the creator emitted in the newParameterizer event ' + 64 | 'not correspond to the one which sent the creation transaction'); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 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 | before(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 utils.as(applicant, registry.exit, listing); 52 | const resultFour = await registry.appWasMade(listing); 53 | assert.strictEqual(resultFour, false, 'should have returned false because exit'); 54 | }); 55 | 56 | it('should return false if applicationExpiry was uninitialized', async () => { 57 | const listing = utils.getListingHash('falseapp.net'); 58 | 59 | const result = await registry.appWasMade(listing); 60 | assert.strictEqual(result, false, 'should have returned false because listing was never applied'); 61 | }); 62 | }); 63 | }); 64 | 65 | -------------------------------------------------------------------------------- /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 | "coverage": "solidity-coverage", 9 | "deploy-local": "truffle migrate --reset", 10 | "deploy-rinkeby": "truffle migrate --reset --network rinkeby", 11 | "deploy-mainnet": "truffle migrate --network mainnet", 12 | "test:truffle": "run-with-testrpc -l 20000000 'truffle test --network testing'", 13 | "test": "truffle test", 14 | "scenario": "truffle exec ./test/exec.js", 15 | "scenarios": "scripts/scenarios.sh", 16 | "migrate": "truffle migrate", 17 | "migrate-reset": "truffle migrate --reset", 18 | "fix": "eslint --fix", 19 | "lint": "eslint", 20 | "ganache": "scripts/runGanache.sh", 21 | "truffle-version": "truffle version" 22 | }, 23 | "author": "Irene Lin , Mira Zeitlin , Yorke Rhodes ", 24 | "license": "ISC", 25 | "dependencies": { 26 | "@gnosis.pm/dx-contracts": "git://git@github.com:levelkdev/dx-contracts#71091ee7bb9550ee55e3f9cc89a571d86f4b5f28", 27 | "@gnosis.pm/gnosis-core-contracts": "git://git@github.com:levelkdev/pm-contracts#78ba5acb928381d3335b6cff851d16f7d61e8afd", 28 | "@gnosis.pm/gno-token": "git://git@github.com:levelkdev/gno-token#e3482f0a9461ba8e13d87b07faaad573efb96214", 29 | "@gnosis.pm/owl-token": "git://git@github.com:levelkdev/owl-token#6d09154539b9dd7d2e9a32f3606f802086484b28", 30 | "@gnosis.pm/util-contracts": "git://git@github.com:levelkdev/util-contracts#c8c4c50a8474fd99b9b576bcc0bf42a0505f3752", 31 | "openzeppelin-solidity": "github:gnosis/openzeppelin-solidity#fix-signed-safemath", 32 | "babel-cli": "^6.24.1", 33 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", 34 | "babel-plugin-transform-runtime": "^6.23.0", 35 | "babel-polyfill": "^6.26.0", 36 | "babel-preset-es2015": "^6.24.1", 37 | "babel-preset-stage-0": "^6.24.1", 38 | "babel-register": "^6.26.0", 39 | "bignumber.js": "5.0.0", 40 | "bip39": "2.5.0", 41 | "bn.js": "4.11.8", 42 | "ethereumjs-abi": "0.6.5", 43 | "ethereumjs-wallet": "0.6.0", 44 | "ethjs": "0.3.1", 45 | "ethjs-provider-http": "0.1.6", 46 | "ethjs-query": "0.3.2", 47 | "ethjs-rpc": "0.1.8", 48 | "ethjs-util": "0.1.4", 49 | "fcr-js": "git://git@github.com:levelkdev/fcr-js#f42077f4356c0aeef5cb64c70041f32c127ca4ba", 50 | "lk-test-helpers": "0.1.3", 51 | "truffle": "4.1.11", 52 | "truffle-hdwallet-provider": "0.0.3", 53 | "web3": "^1.0.0-beta.26" 54 | }, 55 | "devDependencies": { 56 | "eslint": "4.15.0", 57 | "eslint-config-airbnb-base": "12.1.0", 58 | "eslint-plugin-import": "2.8.0", 59 | "lodash": "^4.17.10", 60 | "run-with-testrpc": "^0.3.0", 61 | "solidity-coverage": "^0.4.15" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/unit/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 FutarchyChallengeFactory = artifacts.require('./FutarchyChallengeFactory.sol'); 7 | const Registry = artifacts.require('./Registry.sol'); 8 | const fs = require('fs'); 9 | 10 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 11 | const paramConfig = config.paramDefaults; 12 | 13 | contract('RegistryFactory', (accounts) => { 14 | describe('Function: newRegistryWithToken', () => { 15 | let registryFactory, challengeFactory; 16 | 17 | before(async () => { 18 | registryFactory = await RegistryFactory.deployed(); 19 | challengeFactory = await FutarchyChallengeFactory.deployed(); 20 | }); 21 | 22 | it('should deploy and initialize a new Registry contract', async () => { 23 | const tokenParams = { 24 | supply: '1000', 25 | name: 'TEST', 26 | decimals: '2', 27 | symbol: 'TST', 28 | }; 29 | 30 | // new parameterizer using factory/proxy 31 | const parameters = [ 32 | paramConfig.minDeposit, 33 | paramConfig.pMinDeposit, 34 | paramConfig.applyStageLength, 35 | paramConfig.pApplyStageLength, 36 | paramConfig.commitStageLength, 37 | paramConfig.pCommitStageLength, 38 | paramConfig.revealStageLength, 39 | paramConfig.pRevealStageLength, 40 | paramConfig.dispensationPct, 41 | paramConfig.pDispensationPct, 42 | paramConfig.voteQuorum, 43 | paramConfig.pVoteQuorum, 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 | challengeFactory.address, 55 | { from: accounts[0] }, 56 | ); 57 | const { creator } = registryReceipt.logs[0].args; 58 | const registry = Registry.at(registryReceipt.logs[0].args.registry); 59 | 60 | // verify: registry's token 61 | const registryToken = EIP20.at(await registry.token.call()); 62 | const tokenName = await registryToken.name.call(); 63 | assert.strictEqual( 64 | tokenName, 65 | tokenParams.name, 66 | 'the token attached to the Registry contract does not correspond to the one emitted in the newRegistry event', 67 | ); 68 | // verify: registry's name 69 | const registryName = await registry.name.call(); 70 | assert.strictEqual( 71 | registryName, 72 | 'NEW TCR', 73 | 'the registry\'s name is incorrect', 74 | ); 75 | // verify: registry's creator 76 | assert.strictEqual(creator, accounts[0], 'the creator emitted in the newRegistry event ' + 77 | 'not correspond to the one which sent the creation transaction'); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/unit/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 ChallengeFactory = artifacts.require('./Challenge/FutarchyChallengeFactory'); 8 | const fs = require('fs'); 9 | 10 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 11 | const paramConfig = config.paramDefaults; 12 | 13 | contract('RegistryFactory', (accounts) => { 14 | describe('Function: newRegistryBYOToken', () => { 15 | let registryFactory, challengeFactory; 16 | 17 | before(async () => { 18 | registryFactory = await RegistryFactory.deployed(); 19 | challengeFactory = await ChallengeFactory.deployed(); 20 | }); 21 | 22 | it('should deploy and initialize a new Registry contract', async () => { 23 | const tokenParams = { 24 | supply: '1000', 25 | name: 'TEST', 26 | decimals: '2', 27 | symbol: 'TST', 28 | }; 29 | // new EIP20 token 30 | const token = await EIP20.new( 31 | tokenParams.supply, 32 | tokenParams.name, 33 | tokenParams.decimals, 34 | tokenParams.symbol, 35 | ); 36 | // new parameterizer using factory/proxy 37 | const parameters = [ 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 | ]; 51 | 52 | // new registry using factory/proxy 53 | const registryReceipt = await registryFactory.newRegistryBYOToken( 54 | token.address, 55 | parameters, 56 | 'NEW TCR', 57 | challengeFactory.address, 58 | { from: accounts[0] }, 59 | ); 60 | 61 | const { creator } = registryReceipt.logs[0].args; 62 | const registry = Registry.at(registryReceipt.logs[0].args.registry); 63 | 64 | // verify: registry's token 65 | const registryToken = await registry.token(); 66 | 67 | assert.strictEqual( 68 | registryToken, 69 | token.address, 70 | 'the token attached to the Registry contract does not correspond to the one emitted in the newRegistry event', 71 | ); 72 | // verify: registry's name 73 | const registryName = await registry.name.call(); 74 | assert.strictEqual( 75 | registryName, 76 | 'NEW TCR', 77 | 'the registry\'s name is incorrect', 78 | ); 79 | 80 | // verify: registry's creator 81 | assert.strictEqual(creator, accounts[0], 'the creator emitted in the newRegistry event ' + 82 | 'not correspond to the one which sent the creation transaction'); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /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 "./Parameterizer.sol"; 7 | 8 | contract RegistryFactory { 9 | 10 | event NewRegistry(address creator, EIP20 token, Parameterizer parameterizer, Registry registry); 11 | 12 | ParameterizerFactory public parameterizerFactory; 13 | ProxyFactory public proxyFactory; 14 | Registry public canonizedRegistry; 15 | 16 | /// @dev constructor deploys a new proxyFactory. 17 | constructor(ParameterizerFactory _parameterizerFactory) public { 18 | parameterizerFactory = _parameterizerFactory; 19 | proxyFactory = parameterizerFactory.proxyFactory(); 20 | canonizedRegistry = new Registry(); 21 | } 22 | 23 | /* 24 | @dev deploys and initializes a new Registry 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 Registry contract 27 | */ 28 | function newRegistryBYOToken( 29 | EIP20 _token, 30 | uint[] _parameters, 31 | string _name, 32 | address _challengeFactory 33 | ) public returns (Registry) { 34 | Parameterizer parameterizer = parameterizerFactory.newParameterizerBYOToken(_token, _parameters); 35 | 36 | Registry registry = Registry(proxyFactory.createProxy(canonizedRegistry, "")); 37 | registry.init(_token, parameterizer, _challengeFactory, _name); 38 | 39 | emit NewRegistry(msg.sender, _token, parameterizer, registry); 40 | return registry; 41 | } 42 | 43 | /* 44 | @dev deploys and initializes a new Registry contract, an EIP20, a PLCRVoting, and Parameterizer 45 | to be consumed by the Registry's initializer. 46 | @param _supply the total number of tokens to mint in the EIP20 contract 47 | @param _name the name of the new EIP20 token 48 | @param _decimals the decimal precision to be used in rendering balances in the EIP20 token 49 | @param _symbol the symbol of the new EIP20 token 50 | */ 51 | function newRegistryWithToken( 52 | uint _supply, 53 | string _tokenName, 54 | uint8 _decimals, 55 | string _symbol, 56 | uint[] _parameters, 57 | string _registryName, 58 | address _challengeFactory 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 | 66 | // Create & initialize a new Registry contract 67 | Registry registry = Registry(proxyFactory.createProxy(canonizedRegistry, "")); 68 | registry.init(token, parameterizer, _challengeFactory, _registryName); 69 | 70 | emit NewRegistry(msg.sender, token, parameterizer, registry); 71 | return registry; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contracts/ParameterizerFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 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 | -------------------------------------------------------------------------------- /migrations/2_2_deploy_dx_mock.js: -------------------------------------------------------------------------------- 1 | const Math = artifacts.require('@gnosis.pm/gnosis-core-contracts/Math') 2 | 3 | const DutchExchangeMock = artifacts.require('DutchExchangeMock') 4 | const EtherToken = artifacts.require('EtherToken') 5 | const PriceFeed = artifacts.require('PriceFeed') 6 | const PriceOracleInterface = artifacts.require('PriceOracleInterface') 7 | const StandardToken = artifacts.require('StandardToken') 8 | const TokenGNO = artifacts.require('TokenGNO') 9 | const TokenRDN = artifacts.require('TokenRDN') 10 | const TokenOMG = artifacts.require('TokenOMG') 11 | const TokenOWL = artifacts.require('TokenOWL') 12 | const TokenOWLProxy = artifacts.require('TokenOWLProxy') 13 | 14 | const TokenMGN = artifacts.require('TokenFRT') 15 | const Medianizer = artifacts.require('Medianizer') 16 | const Proxy = artifacts.require('Proxy') 17 | const OWLAirdrop = artifacts.require('OWLAirdrop') 18 | // ETH price as reported by MakerDAO with 18 decimal places 19 | let currentETHPrice = (1100 * (10 ** 18)) 20 | 21 | module.exports = (deployer, network, accounts) => { 22 | if (network == 'development') { 23 | return deployer.deploy(Math) 24 | // Linking 25 | .then(() => deployer.link(Math, [StandardToken, EtherToken, TokenGNO, TokenMGN, TokenOWL, TokenOWLProxy, OWLAirdrop])) 26 | .then(() => deployer.link(Math, [TokenRDN, TokenOMG])) 27 | 28 | // Deployment of Tokens 29 | .then(() => deployer.deploy(EtherToken)) 30 | // .then(() => deployer.deploy(TokenGNO, 100000 * (10 ** 18))) 31 | // .then(() => deployer.deploy(TokenRDN, 100000 * (10 ** 18))) 32 | // .then(() => deployer.deploy(TokenOMG, 100000 * (10 ** 18))) 33 | .then(() => deployer.deploy(TokenMGN, accounts[0])) 34 | .then(() => deployer.deploy(TokenOWL)) 35 | .then(() => deployer.deploy(TokenOWLProxy, TokenOWL.address)) 36 | 37 | // Deployment of PriceFeedInfrastructure 38 | .then(() => deployer.deploy(PriceFeed)) 39 | .then(() => deployer.deploy(Medianizer)) 40 | .then(() => deployer.deploy(PriceOracleInterface, accounts[0], Medianizer.address)) 41 | .then(() => Medianizer.deployed()) 42 | .then(M => M.set(PriceFeed.address, { from: accounts[0] })) 43 | .then(() => PriceFeed.deployed()) 44 | .then(P => P.post(currentETHPrice, 1516168838 * 2, Medianizer.address, { from: accounts[0] })) 45 | 46 | // Deployment of DutchExchange 47 | .then(() => deployer.deploy(DutchExchangeMock)) 48 | .then(() => deployer.deploy(Proxy, DutchExchangeMock.address)) 49 | 50 | // @dev DX Constructor creates exchange 51 | .then(() => Proxy.deployed()) 52 | .then(p => { 53 | return DutchExchangeMock.at(p.address).setupDutchExchange( 54 | TokenMGN.address, 55 | TokenOWLProxy.address, 56 | accounts[0], // @param _owner will be the admin of the contract 57 | EtherToken.address, // @param _ETH - address of ETH ERC-20 token 58 | PriceOracleInterface.address, // @param _priceOracleAddress - address of priceOracle 59 | 10000000000000000000000, // @param _thresholdNewTokenPair: 10,000 dollar 60 | 1000000000000000000000, // @param _thresholdNewAuction: 1,000 dollar 61 | ) 62 | }) 63 | } 64 | } -------------------------------------------------------------------------------- /migrations/6_deploy_futarchy_challenge_factory.js: -------------------------------------------------------------------------------- 1 | /* global artifacts */ 2 | 3 | const Token = artifacts.require('tokens/eip20/EIP20.sol'); 4 | const Math = artifacts.require('@gnosis.pm/gnosis-core-contracts/Math') 5 | const CategoricalEvent = artifacts.require('CategoricalEvent') 6 | const ScalarEvent = artifacts.require('ScalarEvent') 7 | const OutcomeToken = artifacts.require('OutcomeToken') 8 | const StandardMarket = artifacts.require('StandardMarket') 9 | const StandardMarketWithPriceLogger = artifacts.require('StandardMarketWithPriceLogger') 10 | const StandardMarketFactory = artifacts.require('StandardMarketFactory') 11 | const StandardMarketWithPriceLoggerFactory = artifacts.require('StandardMarketWithPriceLoggerFactory') 12 | const FutarchyChallengeFactory = artifacts.require('FutarchyChallengeFactory') 13 | const FutarchyOracleFactory = artifacts.require('FutarchyOracleFactory') 14 | const FutarchyOracle = artifacts.require('FutarchyOracle') 15 | const CentralizedTimedOracleFactory = artifacts.require('CentralizedTimedOracleFactory') 16 | const EventFactory = artifacts.require('EventFactory') 17 | const LMSRMarketMaker = artifacts.require('LMSRMarketMaker') 18 | const EtherToken = artifacts.require('EtherToken') 19 | 20 | const fs = require('fs') 21 | const config = JSON.parse(fs.readFileSync('../conf/config.json')) 22 | const paramConfig = config.paramDefaults 23 | 24 | const tradingPeriod = 60 * 60 25 | const timeToPriceResolution = 60 * 60 * 24 * 7 // a week 26 | const futarchyFundingAmount = paramConfig.minDeposit * 10 ** 18 27 | 28 | module.exports = (deployer, network) => { 29 | const DutchExchange = network == 'development' ? artifacts.require('DutchExchangeMock') : artifacts.require('DutchExchange') 30 | 31 | return deployer.then(async () => { 32 | await deployer.deploy(Math) 33 | deployer.link(Math, [EtherToken, StandardMarketFactory, StandardMarketWithPriceLoggerFactory, FutarchyChallengeFactory, EventFactory, LMSRMarketMaker, CategoricalEvent, ScalarEvent, OutcomeToken]) 34 | await deployer.deploy([CategoricalEvent, ScalarEvent, OutcomeToken,]) 35 | await deployer.deploy(EventFactory, CategoricalEvent.address, ScalarEvent.address, OutcomeToken.address) 36 | 37 | deployer.link(Math, [StandardMarket, StandardMarketWithPriceLogger]) 38 | await deployer.deploy([StandardMarket, StandardMarketWithPriceLogger]) 39 | await deployer.deploy(StandardMarketFactory, StandardMarket.address) 40 | await deployer.deploy(StandardMarketWithPriceLoggerFactory, StandardMarketWithPriceLogger.address) 41 | 42 | await deployer.deploy(CentralizedTimedOracleFactory) 43 | await deployer.deploy(LMSRMarketMaker) 44 | await deployer.deploy(EtherToken) 45 | await deployer.deploy(FutarchyOracle) 46 | await deployer.deploy(FutarchyOracleFactory, FutarchyOracle.address, EventFactory.address, StandardMarketWithPriceLoggerFactory.address) 47 | 48 | await deployer.deploy( 49 | FutarchyChallengeFactory, 50 | Token.address, 51 | EtherToken.address, 52 | futarchyFundingAmount, 53 | tradingPeriod, 54 | timeToPriceResolution, 55 | FutarchyOracleFactory.address, 56 | CentralizedTimedOracleFactory.address, 57 | LMSRMarketMaker.address, 58 | network == 'rinkeby' ? '0x4e69969D9270fF55fc7c5043B074d4e45F795587' : DutchExchange.address 59 | ) 60 | 61 | }).catch((err) => { throw err }) 62 | }; 63 | -------------------------------------------------------------------------------- /test/unit/parameterizer/tokenClaims.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../../utils')(artifacts); 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 | before(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, '420'); 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 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 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 | before(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/unit/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')(artifacts); 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 | before(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/unit/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')(artifacts); 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 | before(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 | 99 | await utils.as(applicant, registry.exit, listing); 100 | const resultTwo = await registry.isWhitelisted(listing); 101 | assert.strictEqual(resultTwo, false, 'Listing should not be in the whitelist'); 102 | 103 | try { 104 | await utils.as(applicant, registry.updateStatus, listing); 105 | } catch (err) { 106 | assert(utils.isEVMException(err), err.toString()); 107 | return; 108 | } 109 | assert(false, 'Listing should not have been whitelisted'); 110 | }); 111 | }); 112 | }); 113 | 114 | -------------------------------------------------------------------------------- /test/unit/registry/exit.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')(artifacts); 9 | 10 | contract('Registry', (accounts) => { 11 | describe('Function: exit', () => { 12 | const [applicant, challenger, voter] = accounts; 13 | 14 | let token; 15 | let registry; 16 | 17 | before(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 allow a listing to exit when no challenge exists', async () => { 26 | const listing = utils.getListingHash('consensys.net'); 27 | 28 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 29 | 30 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 31 | 32 | const isWhitelisted = await registry.isWhitelisted.call(listing); 33 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 34 | 35 | await registry.exit(listing, { from: applicant }); 36 | 37 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 38 | assert.strictEqual(isWhitelistedAfterExit, false, 'the listing was not removed on exit'); 39 | 40 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 41 | assert.strictEqual( 42 | initialApplicantTokenHoldings.toString(10), 43 | finalApplicantTokenHoldings.toString(10), 44 | 'the applicant\'s tokens were not returned to them after exiting the registry', 45 | ); 46 | }); 47 | 48 | it('should not allow a listing to exit when a challenge does exist', async () => { 49 | const listing = utils.getListingHash('consensys.net'); 50 | 51 | const initialApplicantTokenHoldings = await token.balanceOf.call(applicant); 52 | 53 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 54 | 55 | const isWhitelisted = await registry.isWhitelisted.call(listing); 56 | assert.strictEqual(isWhitelisted, true, 'the listing was not added to the registry'); 57 | 58 | await registry.challenge(listing, '', { from: challenger }); 59 | try { 60 | await registry.exit(listing, { from: applicant }); 61 | assert(false, 'exit succeeded when it should have failed'); 62 | } catch (err) { 63 | const errMsg = err.toString(); 64 | assert(utils.isEVMException(err), errMsg); 65 | } 66 | 67 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 68 | assert.strictEqual( 69 | isWhitelistedAfterExit, 70 | true, 71 | 'the listing was able to exit while a challenge was active', 72 | ); 73 | 74 | const finalApplicantTokenHoldings = await token.balanceOf.call(applicant); 75 | assert( 76 | initialApplicantTokenHoldings.gt(finalApplicantTokenHoldings), 77 | 'the applicant\'s tokens were returned in spite of failing to exit', 78 | ); 79 | 80 | // Clean up state, remove consensys.net (it fails its challenge due to draw) 81 | await utils.increaseTime(paramConfig.commitStageLength + paramConfig.revealStageLength + 1); 82 | await registry.updateStatus(listing); 83 | }); 84 | 85 | it('should not allow a listing to be exited by someone who doesn\'t own it', async () => { 86 | const listing = utils.getListingHash('consensys.net'); 87 | 88 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 89 | 90 | try { 91 | await registry.exit(listing, { from: voter }); 92 | assert(false, 'exit succeeded when it should have failed'); 93 | } catch (err) { 94 | const errMsg = err.toString(); 95 | assert(utils.isEVMException(err), errMsg); 96 | } 97 | const isWhitelistedAfterExit = await registry.isWhitelisted.call(listing); 98 | assert.strictEqual( 99 | isWhitelistedAfterExit, 100 | true, 101 | 'the listing was exited by someone other than its owner', 102 | ); 103 | }); 104 | 105 | it('should revert if listing is in application stage', async () => { 106 | const listing = utils.getListingHash('real.net'); 107 | 108 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 109 | 110 | try { 111 | await registry.exit(listing, { from: applicant }); 112 | } catch (err) { 113 | assert(utils.isEVMException(err), err.toString()); 114 | return; 115 | } 116 | assert(false, 'exit succeeded for non-whitelisted listing'); 117 | }); 118 | }); 119 | }); 120 | 121 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 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 | before(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 applicantStartingBalance = await token.balanceOf.call(secondProposer); 93 | 94 | try { 95 | await utils.as(secondProposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 96 | assert(false, 'should not have been able to make duplicate proposal'); 97 | } catch (err) { 98 | assert(utils.isEVMException(err), err.toString()); 99 | } 100 | 101 | const applicantEndingBalance = await token.balanceOf.call(secondProposer); 102 | 103 | assert.strictEqual(applicantEndingBalance.toString(10), applicantStartingBalance.toString(10), 'starting balance and ' 104 | + 'ending balance should have been equal'); 105 | }); 106 | 107 | it('should revert if token transfer from user fails', async () => { 108 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 109 | await token.approve(parameterizer.address, '0', { from: secondProposer }); 110 | 111 | try { 112 | await utils.as(secondProposer, parameterizer.proposeReparameterization, 'voteQuorum', '89'); 113 | } catch (err) { 114 | assert(utils.isEVMException(err), err.toString()); 115 | return; 116 | } 117 | assert(false, 'allowed proposal with fewer tokens than minDeposit'); 118 | }); 119 | }); 120 | }); 121 | 122 | -------------------------------------------------------------------------------- /test/unit/registry/updateStatuses.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const BN = require('bignumber.js'); 5 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 6 | const paramConfig = config.paramDefaults; 7 | const FutarchyOracle = artifacts.require('FutarchyOracle') 8 | 9 | const utils = require('../../utils.js')(artifacts); 10 | 11 | const bigTen = number => new BN(number.toString(10), 10); 12 | const toWei = number => new BN(number * 10 ** 18) 13 | 14 | contract('Registry', (accounts) => { 15 | describe('Function: updateStatuses', () => { 16 | const [applicant, challenger] = accounts; 17 | const minDeposit = bigTen(paramConfig.minDeposit); 18 | 19 | let token; 20 | let registry; 21 | let fcr; 22 | 23 | beforeEach(async () => { 24 | const { registryProxy, tokenInstance, fcrjs } = await utils.getProxies() 25 | registry = registryProxy; 26 | token = tokenInstance; 27 | fcr = fcrjs; 28 | 29 | let minDeposit = toWei(paramConfig.minDeposit) 30 | await utils.approveProxies(accounts, token, false, false, registry) 31 | }); 32 | 33 | it.only('should whitelist an array of 1 listing', async () => { 34 | const listingTitle = 'whitelistmepls.io' 35 | const listingHash = fcr.registry.getListingHash('whitelistmepls.io') 36 | 37 | await fcr.registry.apply(applicant, listingTitle, minDeposit, '') 38 | // const time = paramConfig.applyStageLength + 1 39 | // await utils.increaseTime(time); 40 | // 41 | // const listings = [listing]; 42 | // await utils.as(applicant, registry.updateStatuses, listings); 43 | // const wl = await registry.listings.call(listing); 44 | // assert.strictEqual(wl[1], true, 'should have been whitelisted'); 45 | }); 46 | 47 | it('should whitelist an array of 2 listings', async () => { 48 | const listing1 = utils.getListingHash('whitelistus1.io'); 49 | const listing2 = utils.getListingHash('whitelistus2.io'); 50 | await utils.as(applicant, registry.apply, listing1, minDeposit, ''); 51 | await utils.as(applicant, registry.apply, listing2, minDeposit, ''); 52 | 53 | await utils.increaseTime(paramConfig.applyStageLength + 1); 54 | 55 | const listings = [listing1, listing2]; 56 | await utils.as(applicant, registry.updateStatuses, listings); 57 | const wl1 = await registry.listings.call(listing1); 58 | assert.strictEqual(wl1[1], true, 'listing 1 should have been whitelisted'); 59 | const wl2 = await registry.listings.call(listing2); 60 | assert.strictEqual(wl2[1], true, 'listing 2 should have been whitelisted'); 61 | }); 62 | 63 | it('should not whitelist an array of 1 listing that is still pending an application', async () => { 64 | const listing = utils.getListingHash('tooearlybuddy.io'); 65 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 66 | 67 | const listings = [listing]; 68 | try { 69 | await utils.as(applicant, registry.updateStatuses, listings); 70 | } catch (err) { 71 | assert(utils.isEVMException(err), err.toString()); 72 | return; 73 | } 74 | assert(false, 'Listing should not have been whitelisted'); 75 | }); 76 | 77 | it('should not whitelist a listing that is currently being challenged', async () => { 78 | const listing = utils.getListingHash('dontwhitelist.io'); 79 | 80 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 81 | await utils.as(challenger, registry.createChallenge, listing, ''); 82 | 83 | const listings = [listing]; 84 | try { 85 | await registry.updateStatuses(listings); 86 | } catch (err) { 87 | assert(utils.isEVMException(err), err.toString()); 88 | return; 89 | } 90 | assert(false, 'Listing should not have been whitelisted'); 91 | }); 92 | 93 | it('should not whitelist an array of 1 listing that failed a challenge', async () => { 94 | const trader = accounts[3] 95 | const listingTitle = 'dontwhitelist.net' 96 | const listingHash = await fcr.registry.getListingHash(listingTitle) 97 | 98 | await fcr.registry.apply(applicant, listingTitle, minDeposit, '') 99 | 100 | await utils.createAndStartChallenge(fcr, listingTitle, challenger); 101 | await utils.makeChallengeFail(fcr, listingTitle, trader); 102 | 103 | const result = await registry.isWhitelisted(listingHash); 104 | assert.strictEqual(result, false, 'Listing should not have been whitelisted'); 105 | }); 106 | 107 | it('should not be possible to add an array of 1 listing to the whitelist just by calling updateStatuses', async () => { 108 | const listing = utils.getListingHash('updatemenow.net'); 109 | 110 | try { 111 | const listings = [listing]; 112 | await utils.as(applicant, registry.updateStatuses, listings); 113 | } catch (err) { 114 | assert(utils.isEVMException(err), err.toString()); 115 | return; 116 | } 117 | assert(false, 'Listing should not have been whitelisted'); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 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 | before(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 | -------------------------------------------------------------------------------- /contracts/Challenge/FutarchyChallengeFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | import '@gnosis.pm/gnosis-core-contracts/contracts/Oracles/FutarchyOracleFactory.sol'; 3 | import './Oracles/ScalarPriceOracleFactory.sol'; 4 | import "./ChallengeFactoryInterface.sol"; 5 | import "./FutarchyChallenge.sol"; 6 | import "../IDutchExchange.sol"; 7 | 8 | contract FutarchyChallengeFactory is ChallengeFactoryInterface { 9 | // ------ 10 | // EVENTS 11 | // ------ 12 | event SetUpperAndLowerBound(int upperBound, int lowerBound); 13 | 14 | // ------- 15 | // STATE: 16 | // ------- 17 | // GLOBAL VARIABLES 18 | address public token; // Address of the TCR's intrinsic ERC20 token 19 | address public comparatorToken; // Address of token to which TCR's intrinsic token will be compared 20 | uint public stakeAmount; // Amount that must be staked to initiate a Challenge 21 | uint public tradingPeriod; // Duration for open trading on futarchy prediction markets 22 | uint public timeToPriceResolution; // Duration from start of prediction markets until date of final price resolution 23 | 24 | FutarchyOracleFactory public futarchyOracleFactory; // Factory for creating Futarchy Oracles 25 | ScalarPriceOracleFactory public scalarPriceOracleFactory; // Factory for creating Oracles to resolve Futarchy's scalar prediction markets 26 | LMSRMarketMaker public lmsrMarketMaker; // LMSR Market Maker for futarchy's prediction markets 27 | IDutchExchange public dutchExchange; // Dutch Exchange contract to retrive token prices 28 | 29 | uint NUM_PRICE_POINTS = 5; // number of past price points to reference for price average when determining TCR token value 30 | 31 | // ------------ 32 | // CONSTRUCTOR: 33 | // ------------ 34 | /// @dev Contructor Sets the global state of the factory 35 | /// @param _tokenAddr Address of the TCR's intrinsic ERC20 token 36 | /// @param _comparatorToken Address of token to which TCR's intrinsic token value will be compared 37 | /// @param _stakeAmount Amount that must be staked to initiate a Challenge 38 | /// @param _tradingPeriod Duration for open trading on futarchy prediction markets before futarchy resolution 39 | /// @param _timeToPriceResolution Duration from start of prediction markets until date of final price resolution 40 | /// @param _futarchyOracleFactory Factory for creating Futarchy Oracles 41 | /// @param _scalarPriceOracleFactory Factory for creating Oracles to resolve Futarchy's scalar prediction markets 42 | /// @param _lmsrMarketMaker LMSR Market Maker for futarchy's prediction markets 43 | function FutarchyChallengeFactory( 44 | address _tokenAddr, 45 | address _comparatorToken, 46 | uint _stakeAmount, 47 | uint _tradingPeriod, 48 | uint _timeToPriceResolution, 49 | FutarchyOracleFactory _futarchyOracleFactory, 50 | ScalarPriceOracleFactory _scalarPriceOracleFactory, 51 | LMSRMarketMaker _lmsrMarketMaker, 52 | address _dutchExchange 53 | ) public { 54 | token = _tokenAddr; 55 | comparatorToken = _comparatorToken; 56 | stakeAmount = _stakeAmount; 57 | tradingPeriod = _tradingPeriod; 58 | timeToPriceResolution = _timeToPriceResolution; 59 | 60 | futarchyOracleFactory = _futarchyOracleFactory; 61 | scalarPriceOracleFactory = _scalarPriceOracleFactory; 62 | lmsrMarketMaker = _lmsrMarketMaker; 63 | dutchExchange = IDutchExchange(_dutchExchange); 64 | } 65 | 66 | // -------------------- 67 | // FACTORY INTERFACE: 68 | // -------------------- 69 | /// @dev createChallenge Creates challenge associated to a Registry listing 70 | /// @param _challenger Address of the challenger 71 | /// @param _listingOwner Address of the listing owner 72 | /// @return ChallengeInterface Newly created Challenge 73 | function createChallenge(address _registry, address _challenger, address _listingOwner) external returns (ChallengeInterface) { 74 | int upperBound; 75 | int lowerBound; 76 | (upperBound, lowerBound) = determinePriceBounds(); 77 | 78 | uint resolutionDate = now + timeToPriceResolution; 79 | ScalarPriceOracle scalarPriceOracle = scalarPriceOracleFactory.createScalarPriceOracle( 80 | resolutionDate 81 | ); 82 | 83 | return new FutarchyChallenge( 84 | token, 85 | _registry, 86 | _challenger, 87 | _listingOwner, 88 | stakeAmount, 89 | tradingPeriod, 90 | timeToPriceResolution, 91 | upperBound, 92 | lowerBound, 93 | futarchyOracleFactory, 94 | scalarPriceOracle, 95 | lmsrMarketMaker 96 | ); 97 | } 98 | 99 | function determinePriceBounds() internal returns (int upperBound, int lowerBound) { 100 | uint currentAuctionIndex = dutchExchange.getAuctionIndex(token, comparatorToken); 101 | 102 | uint firstReferencedIndex = currentAuctionIndex - NUM_PRICE_POINTS; 103 | 104 | uint i = 0; 105 | uint num; 106 | uint den; 107 | uint avgPrice; 108 | while(i < NUM_PRICE_POINTS) { 109 | (num, den) = dutchExchange.getPriceInPastAuction(token, comparatorToken, firstReferencedIndex + i); 110 | 111 | avgPrice += (num * 10**18)/uint(den); 112 | i++; 113 | } 114 | avgPrice = avgPrice/uint(NUM_PRICE_POINTS); 115 | 116 | upperBound = int(avgPrice) * 2; 117 | lowerBound = 0; 118 | 119 | SetUpperAndLowerBound(upperBound, lowerBound); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /contracts/Challenge/FutarchyChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | import '@gnosis.pm/gnosis-core-contracts/contracts/Oracles/FutarchyOracleFactory.sol'; 3 | import '@gnosis.pm/gnosis-core-contracts/contracts/MarketMakers/LMSRMarketMaker.sol'; 4 | import "./Oracles/ScalarPriceOracle.sol"; 5 | import "./ChallengeInterface.sol"; 6 | 7 | contract FutarchyChallenge is ChallengeInterface { 8 | 9 | event _Started(address challenger, uint stakeAmount, address futarchyOracleAddress); 10 | event _Funded(address challenger, uint stakeAmount, address futarchyOracleAddress); 11 | 12 | // ============ 13 | // STATE: 14 | // ============ 15 | // GLOBAL VARIABLES 16 | 17 | address public challenger; // the address of the challenger 18 | address public listingOwner; // the address of the listingOwner 19 | bool public isStarted; // true if challenger has executed start() 20 | bool public marketsAreClosed; // true if futarchy markets are closed 21 | uint public stakeAmount; // number of tokens to stake for either party during challenge 22 | uint public tradingPeriod; // duration for open trading before futarchy decision resolution 23 | uint public timeToPriceResolution; // Duration from start of prediction markets until date of final price resolution 24 | int public upperBound; 25 | int public lowerBound; 26 | bool public isFunded; 27 | 28 | FutarchyOracle public futarchyOracle; // Futarchy Oracle to resolve challenge 29 | FutarchyOracleFactory public futarchyOracleFactory; // Factory to create FutarchyOracle 30 | ScalarPriceOracle public scalarPriceOracle; // Oracle to resolve scalar prediction markets 31 | LMSRMarketMaker public lmsrMarketMaker; // MarketMaker for scalar prediction markets 32 | ERC20 public token; // Address of the TCR's intrinsic ERC20 token 33 | address public registry; // Address of TCR 34 | uint public winningMarketIndex; // Index of scalar prediction market with greatest average price for long token 35 | 36 | 37 | // ------------ 38 | // CONSTRUCTOR: 39 | // ------------ 40 | /// @dev Contructor Sets up majority of the FutarchyChallenge global state variables 41 | /// @param _tokenAddr Address of the TCR's intrinsic ERC20 token 42 | /// @param _challenger Address of the challenger 43 | /// @param _listingOwner Address of the listing owner 44 | /// @param _stakeAmount Number of tokens to stake for either party during challenge 45 | /// @param _tradingPeriod Duration for open trading on scalar prediction markets 46 | /// @param _timeToPriceResolution Duration from start of prediction markets until date of final price resolution 47 | /// @param _futarchyOracleFactory Factory to create futarchyOracle 48 | /// @param _scalarPriceOracle Factory to create scalarPriceOracle for scalar prediction markets 49 | /// @param _lmsrMarketMaker LMSR Market Maker for scalar prediction markets 50 | function FutarchyChallenge( 51 | address _tokenAddr, 52 | address _registryAddr, 53 | address _challenger, 54 | address _listingOwner, 55 | uint _stakeAmount, 56 | uint _tradingPeriod, 57 | uint _timeToPriceResolution, 58 | int _upperBound, 59 | int _lowerBound, 60 | FutarchyOracleFactory _futarchyOracleFactory, 61 | ScalarPriceOracle _scalarPriceOracle, 62 | LMSRMarketMaker _lmsrMarketMaker 63 | ) public { 64 | challenger = _challenger; 65 | listingOwner = _listingOwner; 66 | token = ERC20(_tokenAddr); 67 | registry = _registryAddr; 68 | stakeAmount = _stakeAmount; 69 | tradingPeriod = _tradingPeriod; 70 | timeToPriceResolution = _timeToPriceResolution; 71 | upperBound = _upperBound; 72 | lowerBound = _lowerBound; 73 | futarchyOracleFactory = _futarchyOracleFactory; 74 | scalarPriceOracle = _scalarPriceOracle; 75 | lmsrMarketMaker = _lmsrMarketMaker; 76 | } 77 | 78 | // ------------ 79 | // Challenge Interface: 80 | // ------------ 81 | /// @dev start Creates and funds FutarchyOracle. Futarchy Oracle will spin up 82 | /// corresponding prediction markets which will open for trade within 83 | /// 60 seconds of this function invocation 84 | 85 | function start() public { 86 | require(!isStarted); 87 | 88 | uint _startDate = now; 89 | 90 | futarchyOracle = futarchyOracleFactory.createFutarchyOracle( 91 | token, 92 | scalarPriceOracle, 93 | 2, 94 | lowerBound, 95 | upperBound, 96 | lmsrMarketMaker, 97 | 0, 98 | tradingPeriod, 99 | _startDate 100 | ); 101 | 102 | isStarted = true; 103 | 104 | _Started(msg.sender, stakeAmount, address(futarchyOracle)); 105 | } 106 | 107 | function fund() public { 108 | require(isStarted && !isFunded); 109 | require(token.transferFrom(msg.sender, this, stakeAmount)); 110 | require(token.approve(futarchyOracle, stakeAmount)); 111 | futarchyOracle.fund(stakeAmount); 112 | isFunded = true; 113 | 114 | _Funded(msg.sender, stakeAmount, address(futarchyOracle)); 115 | } 116 | 117 | /// @dev ended returns whether Challenge has ended 118 | function ended() public view returns (bool) { 119 | return futarchyOracle.isOutcomeSet(); 120 | } 121 | 122 | /// @dev passed returns whether Challenge has passed 123 | function passed() public view returns (bool) { 124 | require(ended()); 125 | 126 | // marketIndex 1 == deniedScalar 127 | // if proposal is denied, the challenge has passed. 128 | return futarchyOracle.getOutcome() == 1; 129 | } 130 | 131 | function winnerRewardAmount() public view returns (uint256) { 132 | require(marketsAreClosed); 133 | return token.balanceOf(this); 134 | } 135 | 136 | function close() public { 137 | futarchyOracle.close(); 138 | marketsAreClosed = true; 139 | require(token.approve(registry, token.balanceOf(this))); 140 | } 141 | 142 | function setScalarOutcome() public { 143 | scalarPriceOracle.setOutcome(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /test/unit/parameterizer/claimRewards.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract */ 3 | const fs = require('fs'); 4 | const utils = require('../../utils')(artifacts); 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 | before(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 | const salts = ['420']; 54 | 55 | const aliceVoterReward = await parameterizer.voterReward.call(voterAlice, challengeID, '420'); 56 | 57 | // multi claimRewards, arrays as inputs 58 | await utils.as(voterAlice, parameterizer.claimRewards, challengeIDs, salts); 59 | await utils.as(voterAlice, voting.withdrawVotingRights, '10'); 60 | 61 | // state assertion 62 | const voterAliceFinalBalance = await token.balanceOf.call(voterAlice); 63 | // expected = starting balance + voterReward 64 | const voterAliceExpected = voterAliceStartingBalance.add(aliceVoterReward); 65 | assert.strictEqual( 66 | voterAliceFinalBalance.toString(10), voterAliceExpected.toString(10), 67 | 'A voterAlice\'s token balance is not as expected after claiming a reward', 68 | ); 69 | }); 70 | 71 | it('should transfer an array of 3 rewards once a challenge has been resolved', async () => { 72 | const voterAliceStartingBalance = await token.balanceOf.call(voterAlice); 73 | 74 | // propose reparams 75 | const proposalReceipt1 = await utils.as(proposer, parameterizer.proposeReparameterization, 'pVoteQuorum', '51'); 76 | const proposalReceipt2 = await utils.as(proposer, parameterizer.proposeReparameterization, 'commitStageLen', '601'); 77 | const proposalReceipt3 = await utils.as(proposer, parameterizer.proposeReparameterization, 'applyStageLen', '601'); 78 | 79 | const propID1 = proposalReceipt1.logs[0].args.propID; 80 | const propID2 = proposalReceipt2.logs[0].args.propID; 81 | const propID3 = proposalReceipt3.logs[0].args.propID; 82 | 83 | // challenge reparams 84 | const challengeReceipt1 = 85 | await utils.as(challenger, parameterizer.challengeReparameterization, propID1); 86 | const challengeReceipt2 = 87 | await utils.as(challenger, parameterizer.challengeReparameterization, propID2); 88 | const challengeReceipt3 = 89 | await utils.as(challenger, parameterizer.challengeReparameterization, propID3); 90 | 91 | const challengeID1 = challengeReceipt1.logs[0].args.challengeID; 92 | const challengeID2 = challengeReceipt2.logs[0].args.challengeID; 93 | const challengeID3 = challengeReceipt3.logs[0].args.challengeID; 94 | 95 | // commit votes 96 | await utils.commitVote(challengeID1, '1', '10', '420', voterAlice, voting); 97 | await utils.commitVote(challengeID2, '1', '10', '420', voterAlice, voting); 98 | await utils.commitVote(challengeID3, '1', '10', '420', voterAlice, voting); 99 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 100 | 101 | // reveal votes 102 | await utils.as(voterAlice, voting.revealVote, challengeID1, '1', '420'); 103 | await utils.as(voterAlice, voting.revealVote, challengeID2, '1', '420'); 104 | await utils.as(voterAlice, voting.revealVote, challengeID3, '1', '420'); 105 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 106 | 107 | // process reparams 108 | await parameterizer.processProposal(propID1); 109 | await parameterizer.processProposal(propID2); 110 | await parameterizer.processProposal(propID3); 111 | 112 | // array args 113 | const challengeIDs = [challengeID1, challengeID2, challengeID3]; 114 | const salts = ['420', '420', '420']; 115 | 116 | const aliceVoterReward1 = await parameterizer.voterReward.call(voterAlice, challengeID1, '420'); 117 | const aliceVoterReward2 = await parameterizer.voterReward.call(voterAlice, challengeID2, '420'); 118 | const aliceVoterReward3 = await parameterizer.voterReward.call(voterAlice, challengeID3, '420'); 119 | 120 | // multi claimRewards, arrays as inputs 121 | await utils.as(voterAlice, parameterizer.claimRewards, challengeIDs, salts); 122 | await utils.as(voterAlice, voting.withdrawVotingRights, '30'); 123 | 124 | // state assertion 125 | const voterAliceFinalBalance = await token.balanceOf.call(voterAlice); 126 | // expected = starting balance + voterReward x3 127 | const voterAliceExpected = voterAliceStartingBalance 128 | .add(aliceVoterReward1).add(aliceVoterReward2).add(aliceVoterReward3); 129 | assert.strictEqual( 130 | voterAliceFinalBalance.toString(10), voterAliceExpected.toString(10), 131 | 'A voterAlice\'s token balance is not as expected after claiming a reward', 132 | ); 133 | }); 134 | }); 135 | }); 136 | 137 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 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 | before(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 | 48 | // Verify that the application exists. 49 | const result = await registry.listings.call(listing); 50 | assert.strictEqual(result[2], applicant, 'owner of application != address that applied'); 51 | 52 | try { 53 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 54 | } catch (err) { 55 | assert(utils.isEVMException(err), err.toString()); 56 | return; 57 | } 58 | assert(false, 'application was made for listing with an already pending application'); 59 | }); 60 | 61 | it( 62 | 'should add a listing to the whitelist which went unchallenged in its application period', 63 | async () => { 64 | const listing = utils.getListingHash('nochallenge.net'); 65 | await utils.increaseTime(paramConfig.applyStageLength + 1); 66 | await registry.updateStatus(listing); 67 | const result = await registry.isWhitelisted.call(listing); 68 | assert.strictEqual(result, true, "listing didn't get whitelisted"); 69 | }, 70 | ); 71 | 72 | it('should not allow a listing to apply which is already listed', async () => { 73 | const listing = utils.getListingHash('nochallenge.net'); 74 | 75 | // Verify that the listing is whitelisted. 76 | const result = await registry.isWhitelisted.call(listing); 77 | assert.strictEqual(result, true, 'listing was not already whitelisted.'); 78 | 79 | try { 80 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 81 | } catch (err) { 82 | // TODO: Check if EVM error 83 | const errMsg = err.toString(); 84 | assert(utils.isEVMException(err), errMsg); 85 | return; 86 | } 87 | assert(false, 'application was made for an already-listed entry'); 88 | }); 89 | 90 | describe('token transfer', async () => { 91 | it('should revert if token transfer from user fails', async () => { 92 | const listing = utils.getListingHash('toFewTokens.net'); 93 | 94 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 95 | await token.approve(registry.address, '0', { from: applicant }); 96 | 97 | try { 98 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 99 | } catch (err) { 100 | assert(utils.isEVMException(err), err.toString()); 101 | return; 102 | } 103 | assert(false, 'allowed application with not enough tokens'); 104 | }); 105 | 106 | after(async () => { 107 | const balanceOfUser = await token.balanceOf(applicant); 108 | await token.approve(registry.address, balanceOfUser, { from: applicant }); 109 | }); 110 | }); 111 | 112 | it('should revert if the listing\'s applicationExpiry would overflow', async () => { 113 | // calculate an applyStageLen which when added to the current block time will be greater 114 | // than 2^256 - 1 115 | const blockTimestamp = await utils.getBlockTimestamp(); 116 | const maxEVMuint = new BN('2').pow('256').minus('1'); 117 | const applyStageLen = maxEVMuint.minus(blockTimestamp).plus('1'); 118 | 119 | const receipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'applyStageLen', applyStageLen.toString(10)); 120 | const { propID } = receipt.logs[0].args; 121 | 122 | // wait until the apply stage has elapsed and process the proposal 123 | await utils.increaseTime(paramConfig.pApplyStageLength + 1); 124 | await parameterizer.processProposal(propID); 125 | 126 | // make sure that the reparameterization proposal was processed as expected 127 | const actualApplyStageLen = await parameterizer.get.call('applyStageLen'); 128 | assert.strictEqual(actualApplyStageLen.toString(), applyStageLen.toString(), 'the applyStageLen should have been the proposed value'); 129 | 130 | const listing = utils.getListingHash('overflow.net'); 131 | 132 | try { 133 | await utils.as(applicant, registry.apply, listing, paramConfig.minDeposit, ''); 134 | } catch (err) { 135 | assert(utils.isEVMException(err), err.toString()); 136 | return; 137 | } 138 | assert(false, 'app expiry was allowed to overflow!'); 139 | }); 140 | 141 | it('should revert if the deposit amount is less than the minDeposit', async () => { 142 | const listing = utils.getListingHash('smallDeposit.net'); 143 | 144 | const minDeposit = await parameterizer.get.call('minDeposit'); 145 | const deposit = minDeposit.sub(10); 146 | 147 | try { 148 | await utils.as(applicant, registry.apply, listing, deposit.toString(), ''); 149 | } catch (err) { 150 | assert(utils.isEVMException(err), err.toString()); 151 | return; 152 | } 153 | assert(false, 'allowed an application with deposit less than minDeposit'); 154 | }); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /contracts/Challenge/Oracles/DutchExchangeMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import '@gnosis.pm/dx-contracts/contracts/TokenFRT.sol'; 4 | import "@gnosis.pm/owl-token/contracts/TokenOWL.sol"; 5 | import '@gnosis.pm/dx-contracts/contracts/Oracle/PriceOracleInterface.sol'; 6 | import "../../IDutchExchange.sol"; 7 | 8 | /// @title Dutch Exchange - exchange token pairs with the clever mechanism of the dutch auction 9 | /// @author Alex Herrmann - 10 | /// @author Dominik Teiml - 11 | 12 | contract DutchExchangeMock is IDutchExchange { 13 | 14 | // The price is a rational number, so we need a concept of a fraction 15 | struct fraction { 16 | uint num; 17 | uint den; 18 | } 19 | 20 | uint constant WAITING_PERIOD_NEW_TOKEN_PAIR = 6 hours; 21 | uint constant WAITING_PERIOD_NEW_AUCTION = 10 minutes; 22 | uint constant WAITING_PERIOD_CHANGE_MASTERCOPY_OR_ORACLE = 30 days; 23 | uint constant AUCTION_START_WAITING_FOR_FUNDING = 1; 24 | 25 | // variables for Proxy Construction 26 | // 27 | address masterCopy; 28 | address public newMasterCopy; 29 | // Time when new masterCopy is updatabale 30 | uint public masterCopyCountdown; 31 | 32 | // > Storage 33 | // auctioneer has the power to manage some variables 34 | address public auctioneer; 35 | // Ether ERC-20 token 36 | address public ethToken; 37 | // Price Oracle interface 38 | PriceOracleInterface public ethUSDOracle; 39 | // Price Oracle interface proposals during update process 40 | PriceOracleInterface public newProposalEthUSDOracle; 41 | uint public oracleInterfaceCountdown; 42 | // Minimum required sell funding for adding a new token pair, in USD 43 | uint public thresholdNewTokenPair; 44 | // Minimum required sell funding for starting antoher auction, in USD 45 | uint public thresholdNewAuction; 46 | // Fee reduction token (magnolia, ERC-20 token) 47 | TokenFRT public frtToken; 48 | // Token for paying fees 49 | TokenOWL public owlToken; 50 | 51 | // mapping that stores the tokens, which are approved 52 | // Token => approved 53 | // Only tokens approved by auctioneer generate frtToken tokens 54 | mapping (address => bool) public approvedTokens; 55 | 56 | // For the following two mappings, there is one mapping for each token pair 57 | // The order which the tokens should be called is smaller, larger 58 | // These variables should never be called directly! They have getters below 59 | // Token => Token => index 60 | mapping (address => mapping (address => uint)) public latestAuctionIndices; 61 | // Token => Token => time 62 | mapping (address => mapping (address => uint)) public auctionStarts; 63 | 64 | // Token => Token => auctionIndex => price 65 | mapping (address => mapping (address => mapping (uint => fraction))) public closingPrices; 66 | 67 | // Token => Token => amount 68 | mapping (address => mapping (address => uint)) public sellVolumesCurrent; 69 | // Token => Token => amount 70 | mapping (address => mapping (address => uint)) public sellVolumesNext; 71 | // Token => Token => amount 72 | mapping (address => mapping (address => uint)) public buyVolumes; 73 | 74 | // Token => user => amount 75 | // balances stores a user's balance in the DutchX 76 | mapping (address => mapping (address => uint)) public balances; 77 | 78 | // Token => Token => auctionIndex => amount 79 | mapping (address => mapping (address => mapping (uint => uint))) public extraTokens; 80 | 81 | // Token => Token => auctionIndex => user => amount 82 | mapping (address => mapping (address => mapping (uint => mapping (address => uint)))) public sellerBalances; 83 | mapping (address => mapping (address => mapping (uint => mapping (address => uint)))) public buyerBalances; 84 | mapping (address => mapping (address => mapping (uint => mapping (address => uint)))) public claimedAmounts; 85 | 86 | // > Modifiers 87 | modifier onlyAuctioneer() { 88 | // Only allows auctioneer to proceed 89 | // R1 90 | require(msg.sender == auctioneer); 91 | _; 92 | } 93 | 94 | /// @dev Constructor-Function creates exchange 95 | /// @param _frtToken - address of frtToken ERC-20 token 96 | /// @param _owlToken - address of owlToken ERC-20 token 97 | /// @param _auctioneer - auctioneer for managing interfaces 98 | /// @param _ethToken - address of ETH ERC-20 token 99 | /// @param _ethUSDOracle - address of the oracle contract for fetching feeds 100 | /// @param _thresholdNewTokenPair - Minimum required sell funding for adding a new token pair, in USD 101 | function setupDutchExchange( 102 | TokenFRT _frtToken, 103 | TokenOWL _owlToken, 104 | address _auctioneer, 105 | address _ethToken, 106 | PriceOracleInterface _ethUSDOracle, 107 | uint _thresholdNewTokenPair, 108 | uint _thresholdNewAuction 109 | ) 110 | public 111 | { 112 | // Make sure contract hasn't been initialised 113 | require(ethToken == 0); 114 | 115 | // Validates inputs 116 | require(address(_owlToken) != address(0)); 117 | require(address(_frtToken) != address(0)); 118 | require(_auctioneer != 0); 119 | require(_ethToken != 0); 120 | require(address(_ethUSDOracle) != address(0)); 121 | 122 | frtToken = _frtToken; 123 | owlToken = _owlToken; 124 | auctioneer = _auctioneer; 125 | ethToken = _ethToken; 126 | ethUSDOracle = _ethUSDOracle; 127 | thresholdNewTokenPair = _thresholdNewTokenPair; 128 | thresholdNewAuction = _thresholdNewAuction; 129 | } 130 | 131 | //@ dev returns price in units [token2]/[token1] 132 | //@ param token1 first token for price calculation 133 | //@ param token2 second token for price calculation 134 | //@ param auctionIndex index for the auction to get the averaged price from 135 | function getPriceInPastAuction( 136 | address token1, 137 | address token2, 138 | uint auctionIndex 139 | ) 140 | public 141 | view 142 | // price < 10^31 143 | returns (uint num, uint den) 144 | { 145 | num = 1; 146 | den = 1; 147 | } 148 | 149 | // > Helper fns 150 | function getTokenOrder( 151 | address token1, 152 | address token2 153 | ) 154 | public 155 | pure 156 | returns (address, address) 157 | { 158 | if (token2 < token1) { 159 | (token1, token2) = (token2, token1); 160 | } 161 | 162 | return (token1, token2); 163 | } 164 | 165 | 166 | 167 | function getAuctionIndex( 168 | address token1, 169 | address token2 170 | ) 171 | public 172 | view 173 | returns (uint auctionIndex) 174 | { 175 | (token1, token2) = getTokenOrder(token1, token2); 176 | auctionIndex = latestAuctionIndices[token1][token2]; 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 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 | before(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.sub(new BN(paramConfig.pMinDeposit, 10)); 47 | assert.strictEqual( 48 | proposerFinalBalance.toString(10), proposerExpected.toString(10), 49 | 'The challenge loser\'s token balance is not as expected', 50 | ); 51 | 52 | // Edge case, challenger gets both deposits back because there were no voters 53 | const challengerFinalBalance = await token.balanceOf.call(challenger); 54 | const challengerExpected = challengerStartingBalance.add(new BN(paramConfig.pMinDeposit, 10)); 55 | assert.strictEqual( 56 | challengerFinalBalance.toString(10), challengerExpected.toString(10), 57 | 'The challenge winner\'s token balance is not as expected', 58 | ); 59 | }); 60 | 61 | it('should set new parameters if a proposal wins a challenge', async () => { 62 | const proposerStartingBalance = await token.balanceOf.call(proposer); 63 | const challengerStartingBalance = await token.balanceOf.call(challenger); 64 | 65 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51'); 66 | 67 | const { propID } = proposalReceipt.logs[0].args; 68 | 69 | const challengeReceipt = 70 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 71 | 72 | const { challengeID } = challengeReceipt.logs[0].args; 73 | 74 | await utils.commitVote(challengeID, '1', '10', '420', voter, voting); 75 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 76 | 77 | await utils.as(voter, voting.revealVote, challengeID, '1', '420'); 78 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 79 | 80 | await parameterizer.processProposal(propID); 81 | 82 | const voteQuorum = await parameterizer.get('voteQuorum'); 83 | assert.strictEqual(voteQuorum.toString(10), '51', 'The proposal failed which ' + 84 | 'should have succeeded'); 85 | 86 | const proposerFinalBalance = await token.balanceOf.call(proposer); 87 | const winnings = 88 | utils.multiplyByPercentage(paramConfig.pMinDeposit, paramConfig.pDispensationPct); 89 | const proposerExpected = proposerStartingBalance.add(winnings); 90 | assert.strictEqual( 91 | proposerFinalBalance.toString(10), proposerExpected.toString(10), 92 | 'The challenge winner\'s token balance is not as expected', 93 | ); 94 | 95 | const challengerFinalBalance = await token.balanceOf.call(challenger); 96 | const challengerExpected = challengerStartingBalance.sub(new BN(paramConfig.pMinDeposit, 10)); 97 | assert.strictEqual( 98 | challengerFinalBalance.toString(10), challengerExpected.toString(10), 99 | 'The challenge loser\'s token balance is not as expected', 100 | ); 101 | }); 102 | 103 | it( 104 | 'should have deposits of equal size if a challenge is opened & the pMinDeposit has changed since the proposal was initiated', 105 | async () => { 106 | // make proposal to change pMinDeposit 107 | // this is to induce an error where: 108 | // a challenge could have a different stake than the proposal being challenged 109 | const proposalReceiptOne = await utils.as(proposer, parameterizer.proposeReparameterization, 'pMinDeposit', paramConfig.pMinDeposit + 1); 110 | const propIDOne = proposalReceiptOne.logs[0].args.propID; 111 | 112 | // increase time 113 | // we want the second proposal to get the deposit 114 | // from the original pMinDeposit and NOT the pMinDeposit from the first proposal 115 | await utils.increaseTime(paramConfig.pCommitStageLength + 1); 116 | 117 | // open a proposal to change commitDuration 118 | // this is the proposal that we will test against 119 | const proposalReceiptTwo = await utils.as(proposer, parameterizer.proposeReparameterization, 'commitStageLen', paramConfig.commitStageLength + 1); 120 | const propIDTwo = proposalReceiptTwo.logs[0].args.propID; 121 | 122 | // increase time & update pMinDeposit 123 | // process the first proposal 124 | await utils.increaseTime(paramConfig.pRevealStageLength + 1); 125 | await parameterizer.processProposal(propIDOne); 126 | 127 | // challenge the second proposal 128 | const challengeReceipt = 129 | await utils.as(challenger, parameterizer.challengeReparameterization, propIDTwo); 130 | const { challengeID } = challengeReceipt.logs[0].args; 131 | 132 | // assert that the prop.deposit and the challenge.stake are equal 133 | const challenge = await parameterizer.challenges.call(challengeID.toString()); 134 | const challengeStake = challenge[3]; 135 | const proposal = await parameterizer.proposals.call(propIDTwo.toString()); 136 | const proposalDeposit = proposal[2]; 137 | assert.strictEqual(challengeStake.toString(), proposalDeposit.toString(), 'parties to the challenge have different deposits'); 138 | }, 139 | ); 140 | 141 | it('should not allow a challenges for non-existent proposals', async () => { 142 | try { 143 | await utils.as(challenger, parameterizer.challengeReparameterization, new BN(0).toString()); 144 | } catch (err) { 145 | assert(utils.isEVMException(err), err.toString()); 146 | return; 147 | } 148 | assert(false, 'challenge was made on non-existent poll'); 149 | }); 150 | 151 | it('should revert if token transfer from user fails', async () => { 152 | const proposalReceipt = await utils.as(proposer, parameterizer.proposeReparameterization, 'voteQuorum', '55'); 153 | 154 | const { propID } = proposalReceipt.logs[0].args; 155 | 156 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 157 | await token.approve(parameterizer.address, '0', { from: challenger }); 158 | 159 | try { 160 | await utils.as(challenger, parameterizer.challengeReparameterization, propID); 161 | } catch (err) { 162 | assert(utils.isEVMException(err), err.toString()); 163 | return; 164 | } 165 | assert(false, 'allowed challenge with not enough tokens'); 166 | }); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 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 | before(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), '51', 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), '51', 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), '51', 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/unit/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')(artifacts); 10 | 11 | const toWei = number => new BN(number * 10 ** 18); 12 | 13 | contract('Registry', (accounts) => { 14 | describe('Function: createChallenge', () => { 15 | const [applicant, challenger, voter, proposer] = accounts; 16 | 17 | let token; 18 | let parameterizer; 19 | let registry; 20 | let fcr; 21 | let minDeposit; 22 | 23 | before(async () => { 24 | const { 25 | paramProxy, registryProxy, tokenInstance, fcrjs 26 | } = await utils.getProxies(); 27 | parameterizer = paramProxy; 28 | registry = registryProxy; 29 | token = tokenInstance; 30 | fcr = fcrjs; 31 | minDeposit = toWei(paramConfig.minDeposit) 32 | 33 | await utils.approveProxies(accounts, token, false, parameterizer, registry); 34 | }); 35 | 36 | it('should successfully challenge an application', async () => { 37 | const listingTitle = 'failure.net' 38 | const listingHash = fcr.registry.getListingHash(listingTitle) 39 | 40 | await fcr.registry.apply(applicant, listingTitle, minDeposit, '') 41 | const appWasMade = await registry.appWasMade.call(listingHash); 42 | assert.strictEqual(appWasMade, true, 'An application should have been submitted') 43 | 44 | await utils.createAndStartChallenge(fcr, listingTitle, challenger); 45 | const isWhitelisted = await registry.isWhitelisted.call(listingHash); 46 | assert.strictEqual(isWhitelisted, false, 'An application which should have failed succeeded'); 47 | }); 48 | 49 | it('should successfully challenge a listing', async () => { 50 | const listingTitle = 'failure.net' 51 | const listingHash = fcr.registry.getListingHash(listingTitle) 52 | 53 | const challengerStartingBalance = await token.balanceOf.call(challenger); 54 | 55 | await utils.addToWhitelist(listing, paramConfig.minDeposit, applicant, registry); 56 | 57 | await utils.challengeAndGetPollID(listing, challenger, registry); 58 | await utils.increaseTime(paramConfig.commitStageLength + paramConfig.revealStageLength + 1); 59 | await registry.updateStatus(listing); 60 | 61 | const isWhitelisted = await registry.isWhitelisted.call(listing); 62 | assert.strictEqual(isWhitelisted, false, 'An application which should have failed succeeded'); 63 | 64 | const challengerFinalBalance = await token.balanceOf.call(challenger); 65 | // Note edge case: no voters, so challenger gets entire stake 66 | const expectedFinalBalance = 67 | challengerStartingBalance.add(new BN(paramConfig.minDeposit, 10)); 68 | assert.strictEqual( 69 | challengerFinalBalance.toString(10), expectedFinalBalance.toString(10), 70 | 'Reward not properly disbursed to challenger', 71 | ); 72 | }); 73 | 74 | it('should unsuccessfully challenge an application', async () => { 75 | const listing = utils.getListingHash('winner.net'); 76 | 77 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 78 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 79 | await utils.commitVote(pollID, 1, 10, 420, voter, voting); 80 | await utils.increaseTime(paramConfig.commitStageLength + 1); 81 | await utils.as(voter, voting.revealVote, pollID, 1, 420); 82 | await utils.increaseTime(paramConfig.revealStageLength + 1); 83 | await registry.updateStatus(listing); 84 | 85 | const isWhitelisted = await registry.isWhitelisted.call(listing); 86 | assert.strictEqual( 87 | isWhitelisted, true, 88 | 'An application which should have succeeded failed', 89 | ); 90 | 91 | const unstakedDeposit = await utils.getUnstakedDeposit(listing, registry); 92 | const expectedUnstakedDeposit = 93 | minDeposit.add(minDeposit.mul(bigTen(paramConfig.dispensationPct).div(bigTen(100)))); 94 | 95 | assert.strictEqual( 96 | unstakedDeposit.toString(10), expectedUnstakedDeposit.toString(10), 97 | 'The challenge winner was not properly disbursed their tokens', 98 | ); 99 | }); 100 | 101 | it('should unsuccessfully challenge a listing', async () => { 102 | const listing = utils.getListingHash('winner2.net'); 103 | 104 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 105 | 106 | const pollID = await utils.challengeAndGetPollID(listing, challenger, registry); 107 | await utils.commitVote(pollID, 1, 10, 420, voter, voting); 108 | await utils.increaseTime(paramConfig.commitStageLength + 1); 109 | await utils.as(voter, voting.revealVote, pollID, 1, 420); 110 | await utils.increaseTime(paramConfig.revealStageLength + 1); 111 | await registry.updateStatus(listing); 112 | 113 | const isWhitelisted = await registry.isWhitelisted.call(listing); 114 | assert.strictEqual(isWhitelisted, true, 'An application which should have succeeded failed'); 115 | 116 | const unstakedDeposit = await utils.getUnstakedDeposit(listing, registry); 117 | const expectedUnstakedDeposit = minDeposit.add(minDeposit.mul(new BN(paramConfig.dispensationPct, 10).div(new BN('100', 10)))); 118 | assert.strictEqual( 119 | unstakedDeposit.toString(10), expectedUnstakedDeposit.toString(10), 120 | 'The challenge winner was not properly disbursed their tokens', 121 | ); 122 | }); 123 | 124 | it('should touch-and-remove a listing with a depost below the current minimum', async () => { 125 | const listing = utils.getListingHash('touchandremove.net'); 126 | const newMinDeposit = minDeposit.add(new BN('1', 10)); 127 | 128 | const applicantStartingBal = await token.balanceOf.call(applicant); 129 | 130 | await utils.addToWhitelist(listing, minDeposit, applicant, registry); 131 | 132 | const receipt = await utils.as( 133 | proposer, parameterizer.proposeReparameterization, 134 | 'minDeposit', newMinDeposit, 135 | ); 136 | const propID = utils.getReceiptValue(receipt, 'propID'); 137 | 138 | await utils.increaseTime(paramConfig.pApplyStageLength + 1); 139 | 140 | await parameterizer.processProposal(propID); 141 | 142 | const challengerStartingBal = await token.balanceOf.call(challenger); 143 | utils.as(challenger, registry.challenge, listing, ''); 144 | const challengerFinalBal = await token.balanceOf.call(challenger); 145 | 146 | assert( 147 | challengerStartingBal.eq(challengerFinalBal), 148 | 'Tokens were not returned to challenger', 149 | ); 150 | 151 | const applicantFinalBal = await token.balanceOf.call(applicant); 152 | 153 | assert( 154 | applicantStartingBal.eq(applicantFinalBal), 155 | 'Tokens were not returned to applicant', 156 | ); 157 | 158 | assert(!await registry.isWhitelisted.call(listing), 'Listing was not removed'); 159 | }); 160 | 161 | it('should not be able to challenge a listing hash that doesn\'t exist', async () => { 162 | const listing = utils.getListingHash('doesNotExist.net'); 163 | 164 | try { 165 | await utils.challengeAndGetPollID(listing, challenger, registry); 166 | } catch (err) { 167 | assert(utils.isEVMException(err), err.toString()); 168 | return; 169 | } 170 | assert(false, 'challenge succeeded when listing does not exist'); 171 | }); 172 | 173 | it('should revert if challenge occurs on a listing with an open challenge', async () => { 174 | const listing = utils.getListingHash('doubleChallenge.net'); 175 | 176 | await utils.addToWhitelist(listing, minDeposit.toString(), applicant, registry); 177 | 178 | await utils.challengeAndGetPollID(listing, challenger, registry); 179 | 180 | try { 181 | await utils.as(challenger, registry.challenge, listing, ''); 182 | } catch (err) { 183 | assert(utils.isEVMException(err), err.toString()); 184 | return; 185 | } 186 | assert(false, 'challenge succeeded when challenge is already open'); 187 | }); 188 | 189 | it('should revert if token transfer from user fails', async () => { 190 | const listing = utils.getListingHash('challengerNeedsTokens.net'); 191 | 192 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 193 | 194 | // Approve the contract to transfer 0 tokens from account so the transfer will fail 195 | await token.approve(registry.address, '0', { from: challenger }); 196 | 197 | try { 198 | await utils.as(challenger, registry.challenge, listing, ''); 199 | } catch (err) { 200 | assert(utils.isEVMException(err), err.toString()); 201 | return; 202 | } 203 | assert(false, 'allowed challenge with not enough tokens'); 204 | }); 205 | }); 206 | }); 207 | -------------------------------------------------------------------------------- /test/unit/registry/claimReward.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global assert contract artifacts */ 3 | const Registry = artifacts.require('Registry.sol'); 4 | const Token = artifacts.require('EIP20.sol'); 5 | 6 | const fs = require('fs'); 7 | const BN = require('bignumber.js'); 8 | 9 | const config = JSON.parse(fs.readFileSync('./conf/config.json')); 10 | const paramConfig = config.paramDefaults; 11 | 12 | const utils = require('../../utils.js')(artifacts); 13 | 14 | const bigTen = number => new BN(number.toString(10), 10); 15 | 16 | contract('Registry', (accounts) => { 17 | describe('Function: claimReward', () => { 18 | const [applicant, challenger, voterAlice] = accounts; 19 | const minDeposit = bigTen(paramConfig.minDeposit); 20 | 21 | it('should transfer the correct number of tokens once a challenge has been resolved', async () => { 22 | const registry = await Registry.deployed(); 23 | const voting = await utils.getVoting(); 24 | const token = Token.at(await registry.token.call()); 25 | const listing = utils.getListingHash('claimthis.net'); 26 | 27 | // Apply 28 | await utils.as(applicant, registry.apply, listing, minDeposit, ''); 29 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 30 | 31 | // Challenge 32 | const pollID = await utils.challengeAndGetPollID(listing, challenger); 33 | 34 | // Alice is so committed 35 | await utils.commitVote(pollID, '0', 500, '420', voterAlice); 36 | await utils.increaseTime(paramConfig.commitStageLength + 1); 37 | 38 | // Alice is so revealing 39 | await utils.as(voterAlice, voting.revealVote, pollID, '0', '420'); 40 | await utils.increaseTime(paramConfig.revealStageLength + 1); 41 | 42 | // Update status 43 | await utils.as(applicant, registry.updateStatus, listing); 44 | 45 | // Alice claims reward 46 | const aliceVoterReward = await registry.voterReward(voterAlice, pollID, '420'); 47 | await utils.as(voterAlice, registry.claimReward, pollID, '420'); 48 | 49 | // Alice withdraws her voting rights 50 | await utils.as(voterAlice, voting.withdrawVotingRights, '500'); 51 | 52 | const aliceExpected = aliceStartingBalance.add(aliceVoterReward); 53 | const aliceFinalBalance = await token.balanceOf.call(voterAlice); 54 | 55 | assert.strictEqual( 56 | aliceFinalBalance.toString(10), aliceExpected.toString(10), 57 | 'alice should have the same balance as she started', 58 | ); 59 | }); 60 | 61 | it('should revert if challenge does not exist', async () => { 62 | const registry = await Registry.deployed(); 63 | const listing = utils.getListingHash('reversion.net'); 64 | await utils.addToWhitelist(listing, minDeposit, applicant); 65 | 66 | try { 67 | const nonPollID = '666'; 68 | await utils.as(voterAlice, registry.claimReward, nonPollID, '420'); 69 | assert(false, 'should not have been able to claimReward for non-existant challengeID'); 70 | } catch (err) { 71 | assert(utils.isEVMException(err), err.toString()); 72 | } 73 | }); 74 | 75 | it('should revert if provided salt is incorrect', async () => { 76 | const registry = await Registry.deployed(); 77 | const listing = utils.getListingHash('sugar.net'); 78 | const voting = await utils.getVoting(); 79 | const token = Token.at(await registry.token.call()); 80 | 81 | const applicantStartingBalance = await token.balanceOf.call(applicant); 82 | const aliceStartBal = await token.balanceOf.call(voterAlice); 83 | await utils.addToWhitelist(listing, minDeposit, applicant); 84 | 85 | const pollID = await utils.challengeAndGetPollID(listing, challenger); 86 | 87 | // Alice is so committed 88 | await utils.commitVote(pollID, '0', 500, '420', voterAlice); 89 | await utils.increaseTime(paramConfig.commitStageLength + 1); 90 | 91 | // Alice is so revealing 92 | await utils.as(voterAlice, voting.revealVote, pollID, '0', '420'); 93 | await utils.increaseTime(paramConfig.revealStageLength + 1); 94 | 95 | const applicantFinalBalance = await token.balanceOf.call(applicant); 96 | const aliceFinalBalance = await token.balanceOf.call(voterAlice); 97 | const expectedBalance = applicantStartingBalance.sub(minDeposit); 98 | 99 | assert.strictEqual( 100 | applicantFinalBalance.toString(10), expectedBalance.toString(10), 101 | 'applicants final balance should be what they started with minus the minDeposit', 102 | ); 103 | assert.strictEqual( 104 | aliceFinalBalance.toString(10), (aliceStartBal.sub(bigTen(500))).toString(10), 105 | 'alices final balance should be exactly the same as her starting balance', 106 | ); 107 | 108 | // Update status 109 | await utils.as(applicant, registry.updateStatus, listing); 110 | 111 | try { 112 | await utils.as(voterAlice, registry.claimReward, pollID, '421'); 113 | assert(false, 'should not have been able to claimReward with the wrong salt'); 114 | } catch (err) { 115 | assert(utils.isEVMException(err), err.toString()); 116 | } 117 | }); 118 | 119 | it('should not transfer tokens if msg.sender has already claimed tokens for a challenge', async () => { 120 | const registry = await Registry.deployed(); 121 | const listing = utils.getListingHash('sugar.net'); 122 | const voting = await utils.getVoting(); 123 | const token = Token.at(await registry.token.call()); 124 | 125 | const applicantStartingBalance = await token.balanceOf.call(applicant); 126 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 127 | 128 | await utils.addToWhitelist(listing, minDeposit, applicant); 129 | 130 | // Challenge 131 | const pollID = await utils.challengeAndGetPollID(listing, challenger); 132 | 133 | // Alice is so committed 134 | await utils.commitVote(pollID, '0', 500, '420', voterAlice); 135 | await utils.increaseTime(paramConfig.commitStageLength + 1); 136 | 137 | // Alice is so revealing 138 | await utils.as(voterAlice, voting.revealVote, pollID, '0', '420'); 139 | await utils.increaseTime(paramConfig.revealStageLength + 1); 140 | 141 | // Update status 142 | await utils.as(applicant, registry.updateStatus, listing); 143 | 144 | // Claim reward 145 | await utils.as(voterAlice, registry.claimReward, pollID, '420'); 146 | 147 | try { 148 | await utils.as(voterAlice, registry.claimReward, pollID, '420'); 149 | assert(false, 'should not have been able to call claimReward twice'); 150 | } catch (err) { 151 | assert(utils.isEVMException(err), err.toString()); 152 | } 153 | 154 | const applicantEndingBalance = await token.balanceOf.call(applicant); 155 | const appExpected = applicantStartingBalance.sub(minDeposit); 156 | 157 | const aliceEndingBalance = await token.balanceOf.call(voterAlice); 158 | const aliceExpected = aliceStartingBalance.add(minDeposit.div(bigTen(2))).sub(bigTen(500)); 159 | 160 | assert.strictEqual( 161 | applicantEndingBalance.toString(10), appExpected.toString(10), 162 | 'applicants ending balance is incorrect', 163 | ); 164 | assert.strictEqual( 165 | aliceEndingBalance.toString(10), aliceExpected.toString(10), 166 | 'alices ending balance is incorrect', 167 | ); 168 | }); 169 | 170 | it('should not transfer tokens for an unresolved challenge', async () => { 171 | const registry = await Registry.deployed(); 172 | const listing = utils.getListingHash('unresolved.net'); 173 | const voting = await utils.getVoting(); 174 | const token = Token.at(await registry.token.call()); 175 | 176 | const applicantStartingBalance = await token.balanceOf.call(applicant); 177 | const aliceStartingBalance = await token.balanceOf.call(voterAlice); 178 | 179 | await utils.addToWhitelist(listing, minDeposit, applicant); 180 | 181 | // Challenge 182 | const pollID = await utils.challengeAndGetPollID(listing, challenger); 183 | 184 | // Alice is so committed 185 | await utils.commitVote(pollID, '0', 500, '420', voterAlice); 186 | await utils.increaseTime(paramConfig.commitStageLength + 1); 187 | 188 | // Alice is so revealing 189 | await utils.as(voterAlice, voting.revealVote, pollID, '0', '420'); 190 | await utils.increaseTime(paramConfig.revealStageLength + 1); 191 | 192 | try { 193 | await utils.as(voterAlice, registry.claimReward, pollID, '420'); 194 | assert(false, 'should not have been able to claimReward for unresolved challenge'); 195 | } catch (err) { 196 | assert(utils.isEVMException(err), err.toString()); 197 | } 198 | 199 | const applicantEndingBalance = await token.balanceOf.call(applicant); 200 | const appExpected = applicantStartingBalance.sub(minDeposit); 201 | 202 | const aliceEndingBalance = await token.balanceOf.call(voterAlice); 203 | const aliceExpected = aliceStartingBalance.sub(bigTen(500)); 204 | 205 | assert.strictEqual( 206 | applicantEndingBalance.toString(10), appExpected.toString(10), 207 | 'applicants ending balance is incorrect', 208 | ); 209 | assert.strictEqual( 210 | aliceEndingBalance.toString(10), aliceExpected.toString(10), 211 | 'alices ending balance is incorrect', 212 | ); 213 | }); 214 | }); 215 | }); 216 | 217 | -------------------------------------------------------------------------------- /test/unit/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')(artifacts); 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 | before(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, '420'); 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, '420', 94 | ); 95 | await utils.as(voterAlice, parameterizer.claimReward, challengeID, '420'); 96 | await utils.as(voterAlice, voting.withdrawVotingRights, '10'); 97 | 98 | const voterBobReward = await parameterizer.voterReward.call( 99 | voterBob, 100 | challengeID, '420', 101 | ); 102 | await utils.as(voterBob, parameterizer.claimReward, challengeID, '420'); 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, '420'); 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, '420'); 178 | 179 | try { 180 | await utils.as(voterAlice, parameterizer.claimReward, challengeID, '420'); 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, '420'); 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/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global artifacts */ 3 | 4 | import fcr_js from 'fcr-js' 5 | const Eth = require('ethjs'); 6 | const HttpProvider = require('ethjs-provider-http'); 7 | const EthRPC = require('ethjs-rpc'); 8 | const abi = require('ethereumjs-abi'); 9 | const fs = require('fs'); 10 | const Web3_beta = require('web3') 11 | const BigNumber = require('bignumber.js'); 12 | 13 | const fcrJsConfig = require('./fcrJsConfig.json') 14 | const web3_beta = new Web3_beta(new Web3_beta.providers.HttpProvider(fcrJsConfig.local.web3Url)) 15 | 16 | 17 | const ethRPC = new EthRPC(new HttpProvider('http://localhost:8545')); 18 | const ethQuery = new Eth(new HttpProvider('http://localhost:8545')); 19 | 20 | const config = require('../conf/config.json') 21 | const paramConfig = config.paramDefaults; 22 | 23 | const BN = small => new Eth.BN(small.toString(10), 10); 24 | const toWei = number => new BN(number * 10 ** 18) 25 | 26 | module.exports = (artifacts) => { 27 | const PLCRVoting = artifacts.require('PLCRVoting.sol'); 28 | const FutarchyChallenge = artifacts.require('FutarchyChallenge.sol'); 29 | const FutarchyChallengeFactory = artifacts.require('FutarchyChallengeFactory.sol'); 30 | const FutarchyOracleFactory = artifacts.require('FutarchyOracleFactory') 31 | const FutarchyOracle = artifacts.require('FutarchyOracle') 32 | const CentralizedTimedOracleFactory = artifacts.require('CentralizedTimedOracleFactory') 33 | const StandardMarket = artifacts.require('StandardMarket') 34 | const DutchExchange = artifacts.require('DutchExchangeMock') 35 | const LMSRMarketMaker = artifacts.require('LMSRMarketMaker') 36 | const Parameterizer = artifacts.require('Parameterizer.sol'); 37 | const Registry = artifacts.require('Registry.sol'); 38 | const Token = artifacts.require('EIP20.sol'); 39 | const EtherToken = artifacts.require('EtherToken.sol') 40 | const RegistryFactory = artifacts.require('RegistryFactory.sol'); 41 | 42 | const utils = { 43 | getProxies: async () => { 44 | const registryFactory = await RegistryFactory.deployed(); 45 | const token = await Token.new(config.token.supply, config.token.name, config.token.decimals, config.token.symbol); 46 | const lmsrMarketMaker = await LMSRMarketMaker.deployed() 47 | const challengeFactory = await FutarchyChallengeFactory.new( 48 | token.address, 49 | EtherToken.address, 50 | toWei(config.challengeFactory.stakeAmount), 51 | config.challengeFactory.tradingPeriod, 52 | config.challengeFactory.timeToPriceResolution, 53 | FutarchyOracleFactory.address, 54 | CentralizedTimedOracleFactory.address, 55 | LMSRMarketMaker.address, 56 | DutchExchange.address 57 | ) 58 | const registryReceipt = await registryFactory.newRegistryBYOToken( 59 | token.address, 60 | [ 61 | toWei(paramConfig.minDeposit), 62 | toWei(paramConfig.pMinDeposit), 63 | paramConfig.applyStageLength, 64 | paramConfig.pApplyStageLength, 65 | paramConfig.commitStageLength, 66 | paramConfig.pCommitStageLength, 67 | paramConfig.revealStageLength, 68 | paramConfig.pRevealStageLength, 69 | paramConfig.dispensationPct, 70 | paramConfig.pDispensationPct, 71 | paramConfig.voteQuorum, 72 | paramConfig.pVoteQuorum, 73 | ], 74 | 'Futarchy Curated Registry', 75 | challengeFactory.address 76 | ); 77 | 78 | const { 79 | parameterizer, 80 | registry, 81 | } = registryReceipt.logs[0].args; 82 | 83 | const tokenInstance = token; 84 | const paramProxy = await Parameterizer.at(parameterizer); 85 | const registryProxy = await Registry.at(registry); 86 | const plcr = await paramProxy.voting.call(); 87 | const votingProxy = PLCRVoting.at(plcr); 88 | 89 | const fcrjs = fcr_js(web3_beta, _.merge(fcrJsConfig.local, { 90 | registryAddress: registry, 91 | tokenAddress: token.address, 92 | LMSRMarketMakerAddress: lmsrMarketMaker.address, 93 | })) 94 | 95 | const proxies = { 96 | tokenInstance, 97 | votingProxy, 98 | paramProxy, 99 | registryProxy, 100 | fcrjs, 101 | }; 102 | return proxies; 103 | }, 104 | 105 | getProxiesBYO: async (token) => { 106 | const registryFactory = await RegistryFactory.deployed(); 107 | const challengeFactory = await FutarchyChallengeFactory.deployed(); 108 | const registryReceipt = await registryFactory.newRegistryBYOToken( 109 | token.address, 110 | [ 111 | paramConfig.minDeposit, 112 | paramConfig.pMinDeposit, 113 | paramConfig.applyStageLength, 114 | paramConfig.pApplyStageLength, 115 | paramConfig.commitStageLength, 116 | paramConfig.pCommitStageLength, 117 | paramConfig.revealStageLength, 118 | paramConfig.pRevealStageLength, 119 | paramConfig.dispensationPct, 120 | paramConfig.pDispensationPct, 121 | paramConfig.voteQuorum, 122 | paramConfig.pVoteQuorum, 123 | ], 124 | 'Futarchy Curated Registry', 125 | challengeFactory.address 126 | ); 127 | 128 | const { 129 | parameterizer, 130 | registry 131 | } = registryReceipt.logs[0].args; 132 | 133 | const paramProxy = Parameterizer.at(parameterizer); 134 | const registryProxy = Registry.at(registry); 135 | const plcr = await paramProxy.voting.call(); 136 | const votingProxy = PLCRVoting.at(plcr); 137 | 138 | const proxies = { 139 | votingProxy, 140 | paramProxy, 141 | registryProxy, 142 | }; 143 | return proxies; 144 | }, 145 | 146 | approveProxies: async (accounts, token, plcr, parameterizer, registry) => ( 147 | Promise.all(accounts.map(async (user) => { 148 | await token.transfer(user, 10000000000000000000); 149 | if (plcr) { 150 | await token.approve(plcr.address, 10000000000000000000, { from: user }); 151 | } 152 | if (parameterizer) { 153 | await token.approve(parameterizer.address, 10000000000000000000, { from: user }); 154 | } 155 | if (registry) { 156 | await token.approve(registry.address, 10000000000000000000, { from: user }); 157 | } 158 | })) 159 | ), 160 | 161 | tradeOnChallenge: async (challenge) => { 162 | 163 | }, 164 | 165 | getVoting: async () => { 166 | const plcrVotingChallengeFactory = await PLCRVotingChallengeFactory.deployed(); 167 | const votingAddr = await plcrVotingChallengeFactory.voting.call(); 168 | return PLCRVoting.at(votingAddr); 169 | }, 170 | 171 | increaseTime: async seconds => { 172 | if (typeof(seconds) == 'string') { 173 | seconds = parseInt(seconds) 174 | } 175 | new Promise((resolve, reject) => ethRPC.sendAsync({ 176 | method: 'evm_increaseTime', 177 | params: [seconds], 178 | }, (err) => { 179 | if (err) {console.log('err!! ', err); reject(err)}; 180 | resolve(); 181 | })) 182 | .then(() => 183 | new Promise((resolve, reject) => ethRPC.sendAsync({ 184 | method: 'evm_mine', 185 | params: [], 186 | }, (err) => { 187 | resolve(); 188 | }))) 189 | }, 190 | 191 | getVoteSaltHash: (vote, salt) => ( 192 | `0x${abi.soliditySHA3(['uint', 'uint'], [vote, salt]).toString('hex')}` 193 | ), 194 | 195 | getListingHash: domain => ( 196 | // web3.utils.fromAscii(domain) 197 | `0x${abi.soliditySHA3(['string'], [domain]).toString('hex')}` 198 | ), 199 | 200 | approvePLCR: async (address, adtAmount) => { 201 | const registry = await Registry.deployed(); 202 | const plcrAddr = await registry.voting.call(); 203 | const token = await Token.deployed(); 204 | await token.approve(plcrAddr, adtAmount, { from: address }); 205 | }, 206 | 207 | addToWhitelist: async (domain, deposit, actor, registry) => { 208 | await utils.as(actor, registry.apply, domain, deposit, ''); 209 | await utils.increaseTime(paramConfig.applyStageLength + 1); 210 | await utils.as(actor, registry.updateStatus, domain); 211 | }, 212 | 213 | createAndStartChallenge: async (fcr, listingTitle, challenger) => { 214 | const minDeposit = toWei(paramConfig.minDeposit); 215 | await fcr.registry.createChallenge(challenger, listingTitle, '') 216 | const listing = await fcr.registry.getListing(listingTitle); 217 | const challenge = await fcr.registry.getChallenge(listing.challengeID); 218 | await challenge.start(challenger); 219 | await fcr.token.contract.methods.transfer(challenger, minDeposit).call() 220 | await fcr.token.contract.methods.approve(challenge.address, minDeposit).call({from: challenger}) 221 | await challenge.fund(challenger); 222 | return challenge; 223 | }, 224 | 225 | makeChallengeFail: async (fcr, listingTitle, trader) => { 226 | const listing = await fcr.registry.getListing(listingTitle); 227 | const challenge = await fcr.registry.getChallenge(listing.challengeID); 228 | const tradingPeriod = await challenge.contract.methods.tradingPeriod().call(); 229 | const futAddr = await challenge.contract.methods.futarchyOracle().call() 230 | const futarchyOracle = await FutarchyOracle.at(futAddr); 231 | const marketAddr = await futarchyOracle.markets(0) 232 | const market = await StandardMarket.at(marketAddr) 233 | await challenge.buyOutcome(trader, 'LONG_DENIED', new BigNumber(1 * 10 **18)) 234 | await utils.increaseTime(parseInt(tradingPeriod) + 1); 235 | await challenge.setOutcome(trader); 236 | await fcr.registry.updateStatus(trader, listingTitle) 237 | }, 238 | 239 | as: async (actor, fn, ...args) => { 240 | function detectSendObject(potentialSendObj) { 241 | function hasOwnProperty(obj, prop) { 242 | const proto = obj.constructor.prototype; 243 | return (prop in obj) && 244 | (!(prop in proto) || proto[prop] !== obj[prop]); 245 | } 246 | 247 | if (typeof potentialSendObj !== 'object') { return undefined; } 248 | if ( 249 | hasOwnProperty(potentialSendObj, 'from') || 250 | hasOwnProperty(potentialSendObj, 'to') || 251 | hasOwnProperty(potentialSendObj, 'gas') || 252 | hasOwnProperty(potentialSendObj, 'gasPrice') || 253 | hasOwnProperty(potentialSendObj, 'value') 254 | ) { 255 | throw new Error('It is unsafe to use "as" with custom send objects'); 256 | } 257 | 258 | return undefined; 259 | } 260 | detectSendObject(args[args.length - 1]); 261 | const sendObject = { from: actor }; 262 | let receipt = await fn(...args, sendObject); 263 | return receipt; 264 | }, 265 | 266 | isEVMException: err => ( 267 | err.toString().includes('revert') 268 | ), 269 | 270 | // returns block timestamp 271 | getBlockTimestamp: () => ethQuery.blockNumber() 272 | .then(num => ethQuery.getBlockByNumber(num, true)) 273 | .then(block => block.timestamp.toString(10)), 274 | 275 | getUnstakedDeposit: async (domain, registry) => { 276 | // get the struct in the mapping 277 | const listing = await registry.listings.call(domain); 278 | // get the unstaked deposit amount from the listing struct 279 | const unstakedDeposit = await listing[3]; 280 | return unstakedDeposit.toString(); 281 | }, 282 | 283 | challengeAndGetPollID: async (domain, actor, registry) => { 284 | const receipt = await utils.as(actor, registry.challenge, domain, ''); 285 | return receipt.logs[0].args.challengeID; 286 | }, 287 | 288 | getChallengeID: async (domain, registry) => { 289 | const listing = await registry.listings.call(domain); 290 | const challengeID = listing[4]; 291 | return challengeID; 292 | }, 293 | 294 | 295 | getReceiptValue: (receipt, arg) => receipt.logs[0].args[arg], 296 | 297 | proposeReparamAndGetPropID: async (reParam, value, actor, parameterizer) => { 298 | const receipt = await utils.as(actor, parameterizer.proposeReparameterization, reParam, value); 299 | return receipt.logs[0].args.propID; 300 | }, 301 | 302 | challengeReparamAndGetChallengeID: async (propID, actor, parameterizer) => { 303 | const receipt = await utils.as(actor, parameterizer.challengeReparameterization, propID); 304 | return receipt.logs[0].args.challengeID; 305 | }, 306 | 307 | divideAndGetWei: (numerator, denominator) => { 308 | const weiNumerator = Eth.toWei(BN(numerator), 'gwei'); 309 | return weiNumerator.div(BN(denominator)); 310 | }, 311 | 312 | multiplyFromWei: (x, weiBN) => { 313 | if (!Eth.BN.isBN(weiBN)) { 314 | return false; 315 | } 316 | const weiProduct = BN(x).mul(weiBN); 317 | return BN(Eth.fromWei(weiProduct, 'gwei')); 318 | }, 319 | 320 | multiplyByPercentage: (x, y, z = 100) => { 321 | const weiQuotient = utils.divideAndGetWei(y, z); 322 | return utils.multiplyFromWei(x, weiQuotient); 323 | }, 324 | 325 | runDutchExchangeAuction: async (web3, dutchExchange, token1, token2) => { 326 | const { accounts } = web3.eth 327 | const [ auctionCreator, auctionBuyer ] = accounts 328 | 329 | // TODO: implement dutch exchange auction setup and trading 330 | 331 | const b = await token2.balanceOf(auctionBuyer) 332 | return b 333 | }, 334 | 335 | distributeToken: async (accounts, token) => { 336 | const amount = 100 * 10 ** 18 337 | let receipts = [] 338 | let i = 0; 339 | for(i = 0; i < accounts.length; i++) { 340 | if (i == 0) continue 341 | const account = accounts[i] 342 | console.log(`token.transfer: accounts[0] -> ${amount / 10 ** 18} -> accounts[${i}]`) 343 | const receipt = await token.transfer(account, amount) 344 | receipts.push(receipts) 345 | } 346 | return receipts 347 | } 348 | }; 349 | 350 | return utils 351 | } 352 | --------------------------------------------------------------------------------