├── .gitattributes ├── scripts ├── coverage.sh └── test.sh ├── migrations ├── 1_initial_migration.js ├── 2_deploy_misc.js ├── 3_deploy_nexusmutual.js └── 4_initialize_nexusmutual.js ├── .gitignore ├── test ├── utils │ ├── web3.js │ ├── encoder.js │ ├── latestTime.js │ ├── assertRevert.js │ ├── ethTools.js │ ├── expectEvent.js │ ├── getMCRPerThreshold.js │ ├── advanceToBlock.js │ ├── gvProposal.js │ ├── increaseTime.js │ └── getQuote.js ├── 02_Membership.test.js ├── 20_TokenModule.test.js ├── 05_Staking.test.js ├── 14_ProposalCategory.test.js ├── 19_NewTokenPriceTest.test.js ├── 13_MemberRoles.test.js ├── 04_Locking.test.js └── 11_MCR.test.js ├── contracts ├── mocks │ ├── ClaimsDataMock.sol │ ├── DSValueMock.sol │ ├── TokenDataMock.sol │ ├── FactoryMock.sol │ ├── PoolDataMock.sol │ ├── QuotationDataMock.sol │ ├── Pool1Mock.sol │ ├── TokenFunctionMock.sol │ ├── ExchangeMock.sol │ ├── MockMKR.sol │ └── MockDAI.sol ├── Migrations.sol ├── external │ ├── openzeppelin-solidity │ │ ├── token │ │ │ └── ERC20 │ │ │ │ ├── IERC20.sol │ │ │ │ └── ERC20.sol │ │ ├── math │ │ │ └── SafeMath.sol │ │ └── ownership │ │ │ └── Ownable.sol │ ├── proxy │ │ ├── Proxy.sol │ │ ├── UpgradeabilityProxy.sol │ │ └── OwnedUpgradeabilityProxy.sol │ ├── uniswap │ │ └── solidity-interface.sol │ ├── govblocks-protocol │ │ ├── Governed.sol │ │ └── interfaces │ │ │ ├── IMemberRoles.sol │ │ │ ├── IProposalCategory.sol │ │ │ └── IGovernance.sol │ └── ERC1132 │ │ └── IERC1132.sol ├── Iupgradable.sol ├── INXMMaster.sol ├── NXMToken.sol └── MCR.sol ├── nxdev.sh ├── nxdev.bat ├── .solhint.json ├── .solcover.js ├── .travis.yml ├── truffle-config.js ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SOLIDITY_COVERAGE=true scripts/test.sh 4 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require('Migrations'); 2 | 3 | module.exports = async function(deployer) { 4 | await deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | build 3 | 4 | # txt files 5 | *.txt 6 | 7 | # node modules 8 | node_modules 9 | 10 | # python script 11 | *.py 12 | 13 | # log files 14 | *.log 15 | 16 | # coverage 17 | coverageEnv 18 | coverage 19 | coverage.json 20 | -------------------------------------------------------------------------------- /test/utils/web3.js: -------------------------------------------------------------------------------- 1 | const pify = require('pify'); 2 | 3 | const ethAsync = pify(web3.eth); 4 | 5 | module.exports = { 6 | ethGetBalance: ethAsync.getBalance, 7 | ethSendTransaction: ethAsync.sendTransaction, 8 | ethGetBlock: ethAsync.getBlock 9 | }; 10 | -------------------------------------------------------------------------------- /test/utils/encoder.js: -------------------------------------------------------------------------------- 1 | var abi = require('ethereumjs-abi'); 2 | 3 | function encode(...args) { 4 | var encoded = abi.simpleEncode.apply(this, args); 5 | encoded = encoded.toString('hex'); 6 | return '0x' + encoded; 7 | } 8 | 9 | module.exports = { encode }; 10 | -------------------------------------------------------------------------------- /test/utils/latestTime.js: -------------------------------------------------------------------------------- 1 | const { ethGetBlock } = require('./web3'); 2 | 3 | // Returns the time of the last mined block in seconds 4 | async function latestTime() { 5 | const block = await ethGetBlock('latest'); 6 | return block.timestamp; 7 | } 8 | 9 | module.exports = { 10 | latestTime 11 | }; 12 | -------------------------------------------------------------------------------- /contracts/mocks/ClaimsDataMock.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity 0.5.7; 3 | 4 | import "../ClaimsData.sol"; 5 | 6 | 7 | contract ClaimsDataMock is ClaimsData { 8 | 9 | constructor() public ClaimsData() 10 | { 11 | maxVotingTime = 1800; 12 | minVotingTime = 1200; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /test/utils/assertRevert.js: -------------------------------------------------------------------------------- 1 | async function assertRevert(promise) { 2 | try { 3 | await promise; 4 | throw null; 5 | } catch (error) { 6 | assert(error, `Expected an error but did not get one`); 7 | assert( 8 | error.message.includes('revert'), 9 | `Expected an error containing "revert" but got "${error.message}" instead` 10 | ); 11 | } 12 | } 13 | 14 | module.exports = { 15 | assertRevert 16 | }; 17 | -------------------------------------------------------------------------------- /nxdev.sh: -------------------------------------------------------------------------------- 1 | @echo off 2 | title Nexus Dev Kitchen !!! 3 | echo Loading Resources . . . 4 | cd node_modules/.bin/ 5 | echo starting ganche-cli . . . 6 | ganache-cli --gasLimit 0xfffffffffff -i 5777 -p 8545 -m 'grocery obvious wire insane limit weather parade parrot patrol stock blast ivory' -a 30 -e 10000000 7 | ping 127.0.0.1 -n 5 > nul 8 | echo starting oraclize ethereum bridge . . . 9 | ethereum-bridge -H localhost:8545 -a 20 --dev 10 | -------------------------------------------------------------------------------- /nxdev.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | title Nexus Dev Kitchen !!! 3 | echo Loading Resources . . . 4 | cd node_modules/.bin/ 5 | echo starting ganche-cli . . . 6 | start cmd.exe /k "ganache-cli --gasLimit 0xfffffffffff -i 5777 -p 8545 -m 'grocery obvious wire insane limit weather parade parrot patrol stock blast ivory' -a 30 -e 10000000" 7 | ping 127.0.0.1 -n 5 > nul 8 | echo starting oraclize ethereum bridge . . . 9 | start cmd.exe /k "ethereum-bridge -H localhost:8545 -a 29 --dev" -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "default", 3 | "rules": { 4 | "indent": ["error", 4], 5 | "quotes": ["error", "double"], 6 | "max-line-length": ["error", 120], 7 | "code-complexity": false, 8 | "not-rely-on-time": false, 9 | "check-send-result": false, 10 | "function-max-lines": false, 11 | "max-states-count": false, 12 | "no-empty-blocks": false, 13 | "multiple-sends": false, 14 | "no-simple-event-func-name": false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/utils/ethTools.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); // Hardcoded development port 3 | function ether(n) { 4 | return new web3.BigNumber(web3.toWei(n, 'ether')); 5 | } 6 | 7 | function toWei(value) { 8 | return web3.toWei(value, 'ether'); 9 | } 10 | 11 | function toHex(value) { 12 | return web3.toHex(value); 13 | } 14 | module.exports = { 15 | ether, 16 | toWei, 17 | toHex 18 | }; 19 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 8555, 3 | norpc: true, 4 | deepSkip: true, 5 | skipFiles: [ 6 | 'imports', 7 | // 'Pool1.sol', 8 | // 'Pool2.sol', 9 | 'EventCaller.sol', 10 | // 'Governance.sol', 11 | // 'ProposalCategory.sol', 12 | 'dummyDaiFeed.sol', 13 | // 'MemberRoles.sol', 14 | 'mocks' 15 | ], 16 | forceParse: [ 17 | 'imports/ERC1132', 18 | 'imports/govblocks-protocol', 19 | // 'Governance.sol', 20 | // 'ProposalCategory.sol', 21 | 'mocks' 22 | ] 23 | }; 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | 5 | node_js: 6 | - "10" 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | script: 13 | - npm install 14 | 15 | jobs: 16 | fast_finish: true 17 | allow_failures: 18 | - env: SOLIDITY_COVERAGE=true 19 | include: 20 | - stage: tests 21 | name: "unit tests" 22 | script: npm run test 23 | - stage: tests 24 | name: "unit tests with coverage" 25 | script: travis_wait 90 npm run test 26 | env: SOLIDITY_COVERAGE=true 27 | 28 | -------------------------------------------------------------------------------- /test/utils/expectEvent.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should(); 2 | 3 | function inLogs(logs, eventName, eventArgs = {}) { 4 | const event = logs.find(e => e.event === eventName); 5 | should.exist(event); 6 | for (const [k, v] of Object.entries(eventArgs)) { 7 | should.exist(event.args[k]); 8 | event.args[k].should.equal(v); 9 | } 10 | return event; 11 | } 12 | 13 | async function inTransaction(tx, eventName, eventArgs = {}) { 14 | const { logs } = await tx; 15 | return inLogs(logs, eventName, eventArgs); 16 | } 17 | 18 | module.exports = { 19 | inLogs, 20 | inTransaction 21 | }; 22 | -------------------------------------------------------------------------------- /contracts/mocks/DSValueMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | 4 | contract DSValueMock { 5 | 6 | bytes32 public p; 7 | 8 | constructor() public { 9 | uint val = 120 * 10**18; 10 | p = bytes32(val); 11 | } 12 | 13 | function read() public view returns (bytes32) { 14 | return p; 15 | 16 | } 17 | 18 | function setRate(uint value) public { 19 | p = bytes32(value); 20 | } 21 | 22 | function peek() public pure returns (bytes32, bool) { 23 | return (0x000000000000000000000000000000000000000000000008696a94dfc55d0000, true); 24 | } 25 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public lastCompletedMigration; 7 | 8 | constructor() public { 9 | owner = msg.sender; 10 | } 11 | 12 | modifier restricted() { 13 | if (msg.sender == owner) _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | lastCompletedMigration = completed; 18 | } 19 | 20 | function upgrade(address newAddress) public restricted { 21 | Migrations upgraded = Migrations(newAddress); 22 | upgraded.setCompleted(lastCompletedMigration); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/mocks/TokenDataMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "../TokenData.sol"; 4 | 5 | 6 | contract TokenDataMock is TokenData { 7 | 8 | constructor(address payable _walletAdd) public TokenData(_walletAdd) { 9 | walletAddress = _walletAdd; 10 | bookTime = 60; 11 | joiningFee = 2000000000000000; // 0.002 Ether 12 | lockTokenTimeAfterCoverExp = 35 days; 13 | scValidDays = 250; 14 | lockCADays = 7 days; 15 | lockMVDays = 2 days; 16 | stakerCommissionPer = 20; 17 | stakerMaxCommissionPer = 50; 18 | tokenExponent = 4; 19 | priceStep = 1000; 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: '127.0.0.1', 5 | port: 8545, 6 | gas: 8000000, 7 | network_id: '5777' 8 | }, 9 | coverage: { 10 | host: '127.0.0.1', 11 | network_id: '5777', 12 | port: 8555, 13 | gas: 8000000, 14 | gasPrice: 0x01 15 | }, 16 | ganache: { 17 | host: '127.0.0.1', 18 | port: 8545, 19 | gas: 8000000, 20 | network_id: '5777' 21 | } 22 | }, 23 | compilers: { 24 | solc: { 25 | version: '0.5.7', 26 | settings: { 27 | optimizer: { 28 | enabled: true, 29 | runs: 200 30 | } 31 | } 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /contracts/mocks/FactoryMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | 4 | contract FactoryMock { 5 | 6 | mapping (address => address) internal exchange; 7 | 8 | mapping (address => address) internal token; 9 | 10 | function getExchange(address _tokenAddress) public view returns (address) { 11 | 12 | return exchange[_tokenAddress]; 13 | } 14 | 15 | function getToken(address _exchangeAddress) public view returns (address) { 16 | return token[_exchangeAddress]; 17 | } 18 | 19 | function setFactory(address _tokenAddress, address _exchangeAddress) public { 20 | exchange[_tokenAddress] = _exchangeAddress; 21 | token[_exchangeAddress] = _tokenAddress; 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /test/utils/getMCRPerThreshold.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var BN = require('bn.js'); 3 | var ethABI = require('ethereumjs-abi'); 4 | var util = require('ethereumjs-util'); 5 | async function getValue(...args) { 6 | let vf = args[0]; 7 | let pd = args[1]; 8 | let mcr = args[2]; 9 | let vtp = await mcr.calVtpAndMCRtp(); 10 | let totalSa = await mcr.getAllSumAssurance(); 11 | let mincap = await pd.minCap(); 12 | let val = await mcr.getThresholdValues(vtp[0], vf, totalSa, mincap); 13 | // console.log(val); 14 | // console.log(vtp[0]," ",vf," ",totalSa," ",mincap); 15 | return parseInt((val[0] / 1 + val[1] / 1) / 2); 16 | } 17 | 18 | function bigNumberToBN(value) { 19 | return new BN(value.toString(), 10); 20 | } 21 | 22 | module.exports = { getValue }; 23 | -------------------------------------------------------------------------------- /test/utils/advanceToBlock.js: -------------------------------------------------------------------------------- 1 | function advanceBlock() { 2 | return new Promise((resolve, reject) => { 3 | web3.currentProvider.send( 4 | { 5 | jsonrpc: '2.0', 6 | method: 'evm_mine', 7 | id: Date.now() 8 | }, 9 | (err, res) => { 10 | return err ? reject(err) : resolve(res); 11 | } 12 | ); 13 | }); 14 | } 15 | 16 | // Advances the block number so that the last mined block is `number`. 17 | async function advanceToBlock(number) { 18 | if (web3.eth.blockNumber > number) { 19 | throw Error( 20 | `block number ${number} is in the past (current is ${ 21 | web3.eth.blockNumber 22 | })` 23 | ); 24 | } 25 | 26 | while (web3.eth.blockNumber < number) { 27 | await advanceBlock(); 28 | } 29 | } 30 | 31 | module.exports = { 32 | advanceBlock, 33 | advanceToBlock 34 | }; 35 | -------------------------------------------------------------------------------- /contracts/external/openzeppelin-solidity/token/ERC20/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | 4 | /** 5 | * @title ERC20 interface 6 | * @dev see https://github.com/ethereum/EIPs/issues/20 7 | */ 8 | interface IERC20 { 9 | function transfer(address to, uint256 value) external returns (bool); 10 | 11 | function approve(address spender, uint256 value) 12 | external returns (bool); 13 | 14 | function transferFrom(address from, address to, uint256 value) 15 | external returns (bool); 16 | 17 | function totalSupply() external view returns (uint256); 18 | 19 | function balanceOf(address who) external view returns (uint256); 20 | 21 | function allowance(address owner, address spender) 22 | external view returns (uint256); 23 | 24 | event Transfer( 25 | address indexed from, 26 | address indexed to, 27 | uint256 value 28 | ); 29 | 30 | event Approval( 31 | address indexed owner, 32 | address indexed spender, 33 | uint256 value 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/external/proxy/Proxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | 4 | /** 5 | * @title Proxy 6 | * @dev Gives the possibility to delegate any call to a foreign implementation. 7 | */ 8 | contract Proxy { 9 | /** 10 | * @dev Fallback function allowing to perform a delegatecall to the given implementation. 11 | * This function will return whatever the implementation call returns 12 | */ 13 | function () external payable { 14 | address _impl = implementation(); 15 | require(_impl != address(0)); 16 | 17 | assembly { 18 | let ptr := mload(0x40) 19 | calldatacopy(ptr, 0, calldatasize) 20 | let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0) 21 | let size := returndatasize 22 | returndatacopy(ptr, 0, size) 23 | 24 | switch result 25 | case 0 { revert(ptr, size) } 26 | default { return(ptr, size) } 27 | } 28 | } 29 | 30 | /** 31 | * @dev Tells the address of the implementation where every call will be delegated. 32 | * @return address of the implementation to which it will be delegated 33 | */ 34 | function implementation() public view returns (address); 35 | } -------------------------------------------------------------------------------- /contracts/mocks/PoolDataMock.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity 0.5.7; 3 | 4 | import "../MCR.sol"; 5 | import "../PoolData.sol"; 6 | 7 | 8 | contract PoolDataMock is PoolData { 9 | 10 | constructor(address _notariseAdd, address _daiFeedAdd, address _daiAdd) public 11 | PoolData(_notariseAdd, _daiFeedAdd, _daiAdd) { 12 | uint DECIMAL1E18 = 10 ** 18; 13 | notariseMCR = _notariseAdd; 14 | daiFeedAddress = _daiFeedAdd; 15 | c = 5203349; 16 | a = 1948; 17 | mcrTime = 24 hours; 18 | mcrFailTime = 6 hours; 19 | minCap = 7; 20 | shockParameter = 50; 21 | variationPercX100 = 100; //1% 22 | iaRatesTime = 24 hours; //24 hours in seconds 23 | uniswapDeadline = 20 minutes; 24 | liquidityTradeCallbackTime = 4 hours; 25 | ethVolumeLimit = 4; 26 | capacityLimit = 10; 27 | allCurrencyAssets["ETH"] = CurrencyAssets(address(0), 6 * DECIMAL1E18, 0); 28 | allCurrencyAssets["DAI"] = CurrencyAssets(_daiAdd, 7 * DECIMAL1E18, 0); 29 | allInvestmentAssets["ETH"] = InvestmentAssets(address(0), true, 500, 5000, 18); 30 | allInvestmentAssets["DAI"] = InvestmentAssets(_daiAdd, true, 500, 5000, 18); 31 | } 32 | 33 | function changeCurrencyAssetBaseMin(bytes4 curr, uint baseMin) external { 34 | allCurrencyAssets[curr].baseMin = baseMin; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/Iupgradable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "./INXMMaster.sol"; 4 | 5 | 6 | contract Iupgradable { 7 | 8 | INXMMaster public ms; 9 | address public nxMasterAddress; 10 | 11 | modifier onlyInternal { 12 | require(ms.isInternal(msg.sender)); 13 | _; 14 | } 15 | 16 | modifier isMemberAndcheckPause { 17 | require(ms.isPause() == false && ms.isMember(msg.sender) == true); 18 | _; 19 | } 20 | 21 | modifier onlyOwner { 22 | require(ms.isOwner(msg.sender)); 23 | _; 24 | } 25 | 26 | modifier checkPause { 27 | require(ms.isPause() == false); 28 | _; 29 | } 30 | 31 | modifier isMember { 32 | require(ms.isMember(msg.sender), "Not member"); 33 | _; 34 | } 35 | 36 | /** 37 | * @dev Iupgradable Interface to update dependent contract address 38 | */ 39 | function changeDependentContractAddress() public; 40 | 41 | /** 42 | * @dev change master address 43 | * @param _masterAddress is the new address 44 | */ 45 | function changeMasterAddress(address _masterAddress) public { 46 | if (address(ms) != address(0)) { 47 | require(address(ms) == msg.sender, "Not master"); 48 | } 49 | ms = INXMMaster(_masterAddress); 50 | nxMasterAddress = _masterAddress; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /migrations/2_deploy_misc.js: -------------------------------------------------------------------------------- 1 | const DAI = artifacts.require('MockDAI'); 2 | const MKR = artifacts.require('MockMKR'); 3 | const DSValue = artifacts.require('DSValueMock'); 4 | const FactoryMock = artifacts.require('FactoryMock'); 5 | const ExchangeMock = artifacts.require('ExchangeMock'); 6 | const ExchangeMKRMock = artifacts.require('ExchangeMock'); 7 | 8 | let dai; 9 | let factory; 10 | let exchange; 11 | let mkr; 12 | const EXCHANGE_TOKEN = '10000000000000000000000'; 13 | const EXCHANGE_ETHER = 100000000000000000000; 14 | 15 | module.exports = function(deployer, network, accounts) { 16 | deployer.then(async () => { 17 | dai = await deployer.deploy(DAI); 18 | mkr = await deployer.deploy(MKR); 19 | await deployer.deploy(DSValue); 20 | factory = await deployer.deploy(FactoryMock); 21 | exchange = await deployer.deploy( 22 | ExchangeMock, 23 | dai.address, 24 | factory.address 25 | ); 26 | exchangeMKR = await deployer.deploy( 27 | ExchangeMKRMock, 28 | mkr.address, 29 | factory.address 30 | ); 31 | await factory.setFactory(dai.address, exchange.address); 32 | await factory.setFactory(mkr.address, exchangeMKR.address); 33 | await dai.transfer(exchange.address, EXCHANGE_TOKEN); 34 | await mkr.transfer(exchangeMKR.address, EXCHANGE_TOKEN); 35 | await exchange.recieveEther({ value: EXCHANGE_ETHER }); 36 | await exchangeMKR.recieveEther({ value: EXCHANGE_ETHER }); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /contracts/mocks/QuotationDataMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "../QuotationData.sol"; 4 | import "../PoolData.sol"; 5 | import "../external/proxy/OwnedUpgradeabilityProxy.sol"; 6 | 7 | 8 | contract QuotationDataMock is QuotationData { 9 | 10 | PoolData public pd; 11 | 12 | constructor (address _authQuoteAdd, address _kycAuthAdd) public QuotationData(_authQuoteAdd, _kycAuthAdd) { 13 | 14 | } 15 | 16 | function changeHoldedCoverDetails (uint index, uint[] memory newcoverDetails) public { 17 | allCoverHolded[index].coverDetails = newcoverDetails; 18 | } 19 | 20 | function changeHoldedCoverPeriod (uint index, uint16 newCoverPeriod) public { 21 | allCoverHolded[index].coverPeriod = newCoverPeriod; 22 | } 23 | 24 | function changeHoldedCoverCurrency (uint index, bytes4 newCurr) public { 25 | allCoverHolded[index].coverCurr = newCurr; 26 | } 27 | 28 | function changeCurrencyAssetAddress(bytes4 curr, address currAdd) public { 29 | pd = PoolData(ms.getLatestAddress("PD")); 30 | pd.changeCurrencyAssetAddress(curr, currAdd); 31 | } 32 | 33 | function changeInvestmentAssetAddress(bytes4 curr, address currAdd) public { 34 | pd = PoolData(ms.getLatestAddress("PD")); 35 | pd.changeInvestmentAssetAddressAndDecimal(curr, currAdd, 18); 36 | } 37 | 38 | function getImplementationAdd(bytes2 _contract) public view returns(address) { 39 | 40 | UpgradeabilityProxy up = UpgradeabilityProxy(ms.getLatestAddress(_contract)); 41 | return up.implementation(); 42 | } 43 | } -------------------------------------------------------------------------------- /contracts/external/uniswap/solidity-interface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | 4 | contract Factory { 5 | function getExchange(address token) public view returns (address); 6 | function getToken(address exchange) public view returns (address); 7 | } 8 | 9 | 10 | contract Exchange { 11 | function getEthToTokenInputPrice(uint256 ethSold) public view returns(uint256); 12 | 13 | function getTokenToEthInputPrice(uint256 tokensSold) public view returns(uint256); 14 | 15 | function ethToTokenSwapInput(uint256 minTokens, uint256 deadline) public payable returns (uint256); 16 | 17 | function ethToTokenTransferInput(uint256 minTokens, uint256 deadline, address recipient) 18 | public payable returns (uint256); 19 | 20 | function tokenToEthSwapInput(uint256 tokensSold, uint256 minEth, uint256 deadline) 21 | public payable returns (uint256); 22 | 23 | function tokenToEthTransferInput(uint256 tokensSold, uint256 minEth, uint256 deadline, address recipient) 24 | public payable returns (uint256); 25 | 26 | function tokenToTokenSwapInput( 27 | uint256 tokensSold, 28 | uint256 minTokensBought, 29 | uint256 minEthBought, 30 | uint256 deadline, 31 | address tokenAddress 32 | ) 33 | public returns (uint256); 34 | 35 | function tokenToTokenTransferInput( 36 | uint256 tokensSold, 37 | uint256 minTokensBought, 38 | uint256 minEthBought, 39 | uint256 deadline, 40 | address recipient, 41 | address tokenAddress 42 | ) 43 | public returns (uint256); 44 | } 45 | -------------------------------------------------------------------------------- /contracts/external/govblocks-protocol/Governed.sol: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 GovBlocks.io 2 | This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | This program is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | You should have received a copy of the GNU General Public License 11 | along with this program. If not, see http://www.gnu.org/licenses/ */ 12 | 13 | pragma solidity 0.5.7; 14 | 15 | 16 | contract IMaster { 17 | function getLatestAddress(bytes2 _module) public view returns(address); 18 | } 19 | 20 | 21 | contract Governed { 22 | 23 | address public masterAddress; // Name of the dApp, needs to be set by contracts inheriting this contract 24 | 25 | /// @dev modifier that allows only the authorized addresses to execute the function 26 | modifier onlyAuthorizedToGovern() { 27 | IMaster ms = IMaster(masterAddress); 28 | require(ms.getLatestAddress("GV") == msg.sender, "Not authorized"); 29 | _; 30 | } 31 | 32 | /// @dev checks if an address is authorized to govern 33 | function isAuthorizedToGovern(address _toCheck) public view returns(bool) { 34 | IMaster ms = IMaster(masterAddress); 35 | return (ms.getLatestAddress("GV") == _toCheck); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexusmutual", 3 | "version": "1.0.0", 4 | "description": "Smart Contract Covers", 5 | "files": [ 6 | "contracts", 7 | "test" 8 | ], 9 | "scripts": { 10 | "test": "scripts/test.sh", 11 | "pretty": "prettier --write --single-quote --tab-width 2 \"**/*.js\"", 12 | "precommit": "lint-staged" 13 | }, 14 | "lint-staged": { 15 | "**/*.{js,json}": [ 16 | "prettier --single-quote --write --tab-width 2", 17 | "git add" 18 | ] 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/somish/NexusMutual.git" 23 | }, 24 | "keywords": [ 25 | "solidity", 26 | "ethereum", 27 | "smart", 28 | "contracts", 29 | "insurance" 30 | ], 31 | "author": "nexusmutual.io", 32 | "license": "GPL-3.0", 33 | "bugs": { 34 | "url": "https://github.com/somish/NexusMutual/issues" 35 | }, 36 | "homepage": "https://github.com/somish/NexusMutual#readme", 37 | "dependencies": { 38 | "bitcore-lib": "^0.16.0", 39 | "eth-lightwallet": "^3.0.1", 40 | "web3": "^0.20.0" 41 | }, 42 | "devDependencies": { 43 | "chai": "^4.1.2", 44 | "chai-bignumber": "^2.0.2", 45 | "coveralls": "^3.0.2", 46 | "ethereum-bridge": "^0.6.1", 47 | "ethereumjs-abi": "^0.6.5", 48 | "ganache-cli": "^6.1.8", 49 | "husky": "^1.1.2", 50 | "lint-staged": "^7.2.2", 51 | "mocha": "^5.2.0", 52 | "prettier": "^1.14.2", 53 | "solc": "^0.5.7", 54 | "solhint": "^1.5.1", 55 | "solidity-coverage": "^0.5.11", 56 | "truffle": "^5.0.7", 57 | "truffle-hdwallet-provider": "0.0.5" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /contracts/INXMMaster.sol: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 NexusMutual.io 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see http://www.gnu.org/licenses/ */ 15 | 16 | pragma solidity 0.5.7; 17 | 18 | 19 | contract INXMMaster { 20 | 21 | address public tokenAddress; 22 | 23 | address public owner; 24 | 25 | 26 | uint public pauseTime; 27 | 28 | function delegateCallBack(bytes32 myid) external; 29 | 30 | function masterInitialized() public view returns(bool); 31 | 32 | function isInternal(address _add) public view returns(bool); 33 | 34 | function isPause() public view returns(bool check); 35 | 36 | function isOwner(address _add) public view returns(bool); 37 | 38 | function isMember(address _add) public view returns(bool); 39 | 40 | function checkIsAuthToGoverned(address _add) public view returns(bool); 41 | 42 | function updatePauseTime(uint _time) public; 43 | 44 | function dAppLocker() public view returns(address _add); 45 | 46 | function dAppToken() public view returns(address _add); 47 | 48 | function getLatestAddress(bytes2 _contractName) public view returns(address payable contractAddress); 49 | } -------------------------------------------------------------------------------- /test/utils/gvProposal.js: -------------------------------------------------------------------------------- 1 | const increaseTime = require('./increaseTime.js').increaseTime; 2 | async function gvProposal(...args) { 3 | let catId = args[0]; 4 | let actionHash = args[1]; 5 | let mr = args[2]; 6 | let gv = args[3]; 7 | let seq = args[4]; 8 | let p = await gv.getProposalLength(); 9 | await gv.createProposal('proposal', 'proposal', 'proposal', 0); 10 | await gv.categorizeProposal(p, catId, 0); 11 | await gv.submitProposalWithSolution(p, 'proposal', actionHash); 12 | let members = await mr.members(seq); 13 | let iteration = 0; 14 | for (iteration = 0; iteration < members[1].length; iteration++) 15 | await gv.submitVote(p, 1, { 16 | from: members[1][iteration] 17 | }); 18 | // console.log(await gv.proposalDetails(p)); 19 | if (seq != 3) await gv.closeProposal(p); 20 | let proposal = await gv.proposal(p); 21 | assert.equal(proposal[2].toNumber(), 3); 22 | } 23 | 24 | async function gvProposalWithIncentive(...args) { 25 | let catId = args[0]; 26 | let actionHash = args[1]; 27 | let mr = args[2]; 28 | let gv = args[3]; 29 | let seq = args[4]; 30 | let incentive = args[5]; 31 | let p = await gv.getProposalLength(); 32 | await gv.createProposal('proposal', 'proposal', 'proposal', 0); 33 | await gv.categorizeProposal(p, catId, incentive); 34 | await gv.submitProposalWithSolution(p, 'proposal', actionHash); 35 | let members = await mr.members(seq); 36 | let iteration = 0; 37 | for (iteration = 0; iteration < members[1].length; iteration++) 38 | await gv.submitVote(p, 1, { 39 | from: members[1][iteration] 40 | }); 41 | // console.log(await gv.proposalDetails(p)); 42 | await increaseTime(604800); 43 | if (seq != 3) await gv.closeProposal(p); 44 | let proposal = await gv.proposal(p); 45 | assert.equal(proposal[2].toNumber(), 3); 46 | } 47 | 48 | module.exports = { gvProposalWithIncentive, gvProposal }; 49 | -------------------------------------------------------------------------------- /test/utils/increaseTime.js: -------------------------------------------------------------------------------- 1 | const { latestTime } = require('./latestTime'); 2 | 3 | // Increases ganache time by the passed duration in seconds 4 | function increaseTime(duration) { 5 | const id = Date.now(); 6 | 7 | return new Promise((resolve, reject) => { 8 | web3.currentProvider.send( 9 | { 10 | jsonrpc: '2.0', 11 | method: 'evm_increaseTime', 12 | params: [duration], 13 | id: id 14 | }, 15 | err1 => { 16 | if (err1) return reject(err1); 17 | 18 | web3.currentProvider.send( 19 | { 20 | jsonrpc: '2.0', 21 | method: 'evm_mine', 22 | id: id + 1 23 | }, 24 | (err2, res) => { 25 | return err2 ? reject(err2) : resolve(res); 26 | } 27 | ); 28 | } 29 | ); 30 | }); 31 | } 32 | 33 | /** 34 | * Beware that due to the need of calling two separate ganache methods and rpc calls overhead 35 | * it's hard to increase time precisely to a target point so design your test to tolerate 36 | * small fluctuations from time to time. 37 | * 38 | * @param target time in seconds 39 | */ 40 | async function increaseTimeTo(target) { 41 | const now = await latestTime(); 42 | 43 | if (target < now) 44 | throw Error( 45 | `Cannot increase current time(${now}) to a moment in the past(${target})` 46 | ); 47 | const diff = target - now; 48 | return increaseTime(diff); 49 | } 50 | 51 | const duration = { 52 | seconds: function(val) { 53 | return val; 54 | }, 55 | minutes: function(val) { 56 | return val * this.seconds(60); 57 | }, 58 | hours: function(val) { 59 | return val * this.minutes(60); 60 | }, 61 | days: function(val) { 62 | return val * this.hours(24); 63 | }, 64 | weeks: function(val) { 65 | return val * this.days(7); 66 | }, 67 | years: function(val) { 68 | return val * this.days(365); 69 | } 70 | }; 71 | 72 | module.exports = { 73 | increaseTime, 74 | increaseTimeTo, 75 | duration 76 | }; 77 | -------------------------------------------------------------------------------- /contracts/external/proxy/UpgradeabilityProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "./Proxy.sol"; 4 | 5 | 6 | /** 7 | * @title UpgradeabilityProxy 8 | * @dev This contract represents a proxy where the implementation address to which it will delegate can be upgraded 9 | */ 10 | contract UpgradeabilityProxy is Proxy { 11 | /** 12 | * @dev This event will be emitted every time the implementation gets upgraded 13 | * @param implementation representing the address of the upgraded implementation 14 | */ 15 | event Upgraded(address indexed implementation); 16 | 17 | // Storage position of the address of the current implementation 18 | bytes32 private constant IMPLEMENTATION_POSITION = keccak256("org.govblocks.proxy.implementation"); 19 | 20 | /** 21 | * @dev Constructor function 22 | */ 23 | constructor() public {} 24 | 25 | /** 26 | * @dev Tells the address of the current implementation 27 | * @return address of the current implementation 28 | */ 29 | function implementation() public view returns (address impl) { 30 | bytes32 position = IMPLEMENTATION_POSITION; 31 | assembly { 32 | impl := sload(position) 33 | } 34 | } 35 | 36 | /** 37 | * @dev Sets the address of the current implementation 38 | * @param _newImplementation address representing the new implementation to be set 39 | */ 40 | function _setImplementation(address _newImplementation) internal { 41 | bytes32 position = IMPLEMENTATION_POSITION; 42 | assembly { 43 | sstore(position, _newImplementation) 44 | } 45 | } 46 | 47 | /** 48 | * @dev Upgrades the implementation address 49 | * @param _newImplementation representing the address of the new implementation to be set 50 | */ 51 | function _upgradeTo(address _newImplementation) internal { 52 | address currentImplementation = implementation(); 53 | require(currentImplementation != _newImplementation); 54 | _setImplementation(_newImplementation); 55 | emit Upgraded(_newImplementation); 56 | } 57 | } -------------------------------------------------------------------------------- /contracts/external/openzeppelin-solidity/math/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | 4 | /** 5 | * @title SafeMath 6 | * @dev Math operations with safety checks that revert on error 7 | */ 8 | library SafeMath { 9 | 10 | /** 11 | * @dev Multiplies two numbers, reverts on overflow. 12 | */ 13 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 14 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 15 | // benefit is lost if 'b' is also tested. 16 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 17 | if (a == 0) { 18 | return 0; 19 | } 20 | 21 | uint256 c = a * b; 22 | require(c / a == b); 23 | 24 | return c; 25 | } 26 | 27 | /** 28 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 29 | */ 30 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 31 | require(b > 0); // Solidity only automatically asserts when dividing by 0 32 | uint256 c = a / b; 33 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 34 | 35 | return c; 36 | } 37 | 38 | /** 39 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 40 | */ 41 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 42 | require(b <= a); 43 | uint256 c = a - b; 44 | 45 | return c; 46 | } 47 | 48 | /** 49 | * @dev Adds two numbers, reverts on overflow. 50 | */ 51 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 52 | uint256 c = a + b; 53 | require(c >= a); 54 | 55 | return c; 56 | } 57 | 58 | /** 59 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 60 | * reverts when dividing by zero. 61 | */ 62 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 63 | require(b != 0); 64 | return a % b; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit script as soon as a command fails. 4 | set -o errexit 5 | 6 | # Executes cleanup function at script exit. 7 | trap cleanup EXIT 8 | 9 | cleanup() { 10 | # Kill the ganache instance that we started (if we started one and if it's still running). 11 | if [ -n "$ganache_pid" ] && ps -p $ganache_pid > /dev/null; then 12 | kill -9 $ganache_pid 13 | fi 14 | } 15 | 16 | if [ "$SOLIDITY_COVERAGE" = true ]; then 17 | ganache_port=8555 18 | else 19 | ganache_port=8545 20 | fi 21 | 22 | ganache_running() { 23 | nc -z localhost "$ganache_port" 24 | } 25 | 26 | start_ganache() { 27 | if [ "$SOLIDITY_COVERAGE" = true ]; then 28 | node_modules/.bin/testrpc-sc --gasLimit 0xfffffffffff -p "$ganache_port" -i 5777 -m "grocery obvious wire insane limit weather parade parrot patrol stock blast ivory" -a 30 -e 10000000 > /dev/null & 29 | else 30 | node_modules/.bin/ganache-cli --gasLimit 8000000 -p "$ganache_port" -i 5777 -m "grocery obvious wire insane limit weather parade parrot patrol stock blast ivory" -a 30 -e 10000000 > /dev/null & 31 | fi 32 | 33 | ganache_pid=$! 34 | } 35 | 36 | if ganache_running; then 37 | echo "Using existing ganache instance" 38 | else 39 | echo "Starting our own ganache instance" 40 | start_ganache 41 | sleep 2 42 | fi 43 | if [ "$SOLIDITY_COVERAGE" = true ]; then 44 | curl -o node_modules/solidity-parser-sc/build/parser.js https://nexusmutual.io/js/parser.js 45 | curl -o node_modules/solidity-coverage/lib/app.js https://nexusmutual.io/js/app.js 46 | sleep 2 47 | node_modules/.bin/solidity-coverage 48 | if [ "$CONTINUOUS_INTEGRATION" = true ]; then 49 | cat coverage/lcov.info | node_modules/.bin/coveralls 50 | fi 51 | else 52 | if [ -d "node_modules/eth-lightwallet/node_modules/bitcore-lib" ]; then 53 | rm -r "node_modules/eth-lightwallet/node_modules/bitcore-lib" 54 | echo "Deleted eth bitcore-lib" 55 | fi 56 | if [ -d "node_modules/bitcore-mnemonic/node_modules/bitcore-lib" ]; then 57 | rm -r "node_modules/bitcore-mnemonic/node_modules/bitcore-lib" 58 | echo "Deleted mne bitcore-lib" 59 | fi 60 | echo "Now let's test truffle" 61 | node_modules/.bin/truffle test "$@" 62 | fi 63 | -------------------------------------------------------------------------------- /test/02_Membership.test.js: -------------------------------------------------------------------------------- 1 | const MemberRoles = artifacts.require('MemberRoles'); 2 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 3 | const NXMaster = artifacts.require('NXMaster'); 4 | 5 | const { assertRevert } = require('./utils/assertRevert'); 6 | const { ether } = require('./utils/ethTools'); 7 | 8 | let tf; 9 | let mr; 10 | let nxms; 11 | const fee = ether(0.002); 12 | 13 | const BigNumber = web3.BigNumber; 14 | require('chai') 15 | .use(require('chai-bignumber')(BigNumber)) 16 | .should(); 17 | 18 | contract('NXMToken:Membership', function([owner, member1, member2]) { 19 | before(async function() { 20 | nxms = await NXMaster.deployed(); 21 | tf = await TokenFunctions.deployed(); 22 | mr = await MemberRoles.at(await nxms.getLatestAddress('0x4d52')); 23 | await mr.addMembersBeforeLaunch([], []); 24 | (await mr.launched()).should.be.equal(true); 25 | }); 26 | describe('Buy membership', function() { 27 | describe('if paid joining fee', function() { 28 | it('2.1 should be able to join as member', async function() { 29 | await mr.payJoiningFee(member1, { from: member1, value: fee }); 30 | await mr.kycVerdict(member1, true, { from: owner }); 31 | (await mr.checkRole(member1, 2)).should.equal(true); 32 | }); 33 | }); 34 | describe('if not paid joining fee', function() { 35 | it('2.2 reverts', async function() { 36 | await assertRevert( 37 | mr.payJoiningFee(member2, { from: member2, value: fee - 1e15 }) 38 | ); 39 | (await mr.checkRole(member2, 2)).should.equal(false); 40 | }); 41 | }); 42 | }); 43 | describe('Withdraw membership', function() { 44 | describe('If met Withdraw membership conditions', function() { 45 | it('2.3 should be able to withdraw membership', async function() { 46 | await mr.withdrawMembership({ from: member1 }); 47 | (await mr.checkRole(member1, 2)).should.equal(false); 48 | }); 49 | }); 50 | describe('Cannot withdrawn if already withdrawn', function() { 51 | it('2.4 reverts', async function() { 52 | await assertRevert(mr.withdrawMembership({ from: member1 })); 53 | }); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /migrations/3_deploy_nexusmutual.js: -------------------------------------------------------------------------------- 1 | const Claims = artifacts.require('Claims'); 2 | const ClaimsData = artifacts.require('ClaimsDataMock'); 3 | const ClaimsReward = artifacts.require('ClaimsReward'); 4 | const NXMaster = artifacts.require('NXMaster'); 5 | const MCR = artifacts.require('MCR'); 6 | const NXMToken = artifacts.require('NXMToken'); 7 | const TokenData = artifacts.require('TokenDataMock'); 8 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 9 | const TokenController = artifacts.require('TokenController'); 10 | const Pool1 = artifacts.require('Pool1Mock'); 11 | const Pool2 = artifacts.require('Pool2'); 12 | const PoolData = artifacts.require('PoolDataMock'); 13 | const Quotation = artifacts.require('Quotation'); 14 | const QuotationDataMock = artifacts.require('QuotationDataMock'); 15 | const Governance = artifacts.require('Governance'); 16 | const ProposalCategory = artifacts.require('ProposalCategory'); 17 | const MemberRoles = artifacts.require('MemberRoles'); 18 | const FactoryMock = artifacts.require('FactoryMock'); 19 | const DSValue = artifacts.require('DSValueMock'); 20 | const DAI = artifacts.require('MockDAI'); 21 | const INITIAL_SUPPLY = '1500000000000000000000000'; 22 | const QE = '0x51042c4d8936a7764d18370a6a0762b860bb8e07'; 23 | 24 | module.exports = function(deployer, network, accounts) { 25 | deployer.then(async () => { 26 | let founderAddress = accounts[0]; 27 | let factory = await FactoryMock.deployed(); 28 | let dsv = await DSValue.deployed(); 29 | let cad = await DAI.deployed(); 30 | await deployer.deploy(Claims); 31 | await deployer.deploy(ClaimsData); 32 | await deployer.deploy(ClaimsReward); 33 | await deployer.deploy(Pool1); 34 | await deployer.deploy(Pool2, factory.address); 35 | await deployer.deploy(PoolData, founderAddress, dsv.address, cad.address); 36 | await deployer.deploy(MCR); 37 | const tc = await deployer.deploy(TokenController); 38 | const tk = await deployer.deploy(NXMToken, founderAddress, INITIAL_SUPPLY); 39 | await deployer.deploy(TokenData, founderAddress); 40 | await deployer.deploy(TokenFunctions); 41 | await deployer.deploy(Quotation); 42 | await deployer.deploy(QuotationDataMock, QE, founderAddress); 43 | await deployer.deploy(Governance); 44 | await deployer.deploy(ProposalCategory); 45 | await deployer.deploy(MemberRoles); 46 | await deployer.deploy(NXMaster, tk.address); 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /contracts/external/openzeppelin-solidity/ownership/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | 4 | /** 5 | * @title Ownable 6 | * @dev The Ownable contract has an owner address, and provides basic authorization control 7 | * functions, this simplifies the implementation of "user permissions". 8 | */ 9 | contract Ownable { 10 | address private _owner; 11 | 12 | event OwnershipTransferred( 13 | address indexed previousOwner, 14 | address indexed newOwner 15 | ); 16 | 17 | /** 18 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 19 | * account. 20 | */ 21 | constructor() internal { 22 | _owner = msg.sender; 23 | emit OwnershipTransferred(address(0), _owner); 24 | } 25 | 26 | /** 27 | * @return the address of the owner. 28 | */ 29 | function owner() public view returns(address) { 30 | return _owner; 31 | } 32 | 33 | /** 34 | * @dev Throws if called by any account other than the owner. 35 | */ 36 | modifier onlyOwner() { 37 | require(isOwner()); 38 | _; 39 | } 40 | 41 | /** 42 | * @return true if `msg.sender` is the owner of the contract. 43 | */ 44 | function isOwner() public view returns(bool) { 45 | return msg.sender == _owner; 46 | } 47 | 48 | /** 49 | * @dev Allows the current owner to relinquish control of the contract. 50 | * @notice Renouncing to ownership will leave the contract without an owner. 51 | * It will not be possible to call the functions with the `onlyOwner` 52 | * modifier anymore. 53 | */ 54 | function renounceOwnership() public onlyOwner { 55 | emit OwnershipTransferred(_owner, address(0)); 56 | _owner = address(0); 57 | } 58 | 59 | /** 60 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 61 | * @param newOwner The address to transfer ownership to. 62 | */ 63 | function transferOwnership(address newOwner) public onlyOwner { 64 | _transferOwnership(newOwner); 65 | } 66 | 67 | /** 68 | * @dev Transfers control of the contract to a newOwner. 69 | * @param newOwner The address to transfer ownership to. 70 | */ 71 | function _transferOwnership(address newOwner) internal { 72 | require(newOwner != address(0)); 73 | emit OwnershipTransferred(_owner, newOwner); 74 | _owner = newOwner; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /contracts/external/proxy/OwnedUpgradeabilityProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "./UpgradeabilityProxy.sol"; 4 | 5 | 6 | /** 7 | * @title OwnedUpgradeabilityProxy 8 | * @dev This contract combines an upgradeability proxy with basic authorization control functionalities 9 | */ 10 | contract OwnedUpgradeabilityProxy is UpgradeabilityProxy { 11 | /** 12 | * @dev Event to show ownership has been transferred 13 | * @param previousOwner representing the address of the previous owner 14 | * @param newOwner representing the address of the new owner 15 | */ 16 | event ProxyOwnershipTransferred(address previousOwner, address newOwner); 17 | 18 | // Storage position of the owner of the contract 19 | bytes32 private constant PROXY_OWNER_POSITION = keccak256("org.govblocks.proxy.owner"); 20 | 21 | /** 22 | * @dev the constructor sets the original owner of the contract to the sender account. 23 | */ 24 | constructor(address _implementation) public { 25 | _setUpgradeabilityOwner(msg.sender); 26 | _upgradeTo(_implementation); 27 | } 28 | 29 | /** 30 | * @dev Throws if called by any account other than the owner. 31 | */ 32 | modifier onlyProxyOwner() { 33 | require(msg.sender == proxyOwner()); 34 | _; 35 | } 36 | 37 | /** 38 | * @dev Tells the address of the owner 39 | * @return the address of the owner 40 | */ 41 | function proxyOwner() public view returns (address owner) { 42 | bytes32 position = PROXY_OWNER_POSITION; 43 | assembly { 44 | owner := sload(position) 45 | } 46 | } 47 | 48 | /** 49 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 50 | * @param _newOwner The address to transfer ownership to. 51 | */ 52 | function transferProxyOwnership(address _newOwner) public onlyProxyOwner { 53 | require(_newOwner != address(0)); 54 | _setUpgradeabilityOwner(_newOwner); 55 | emit ProxyOwnershipTransferred(proxyOwner(), _newOwner); 56 | } 57 | 58 | /** 59 | * @dev Allows the proxy owner to upgrade the current version of the proxy. 60 | * @param _implementation representing the address of the new implementation to be set. 61 | */ 62 | function upgradeTo(address _implementation) public onlyProxyOwner { 63 | _upgradeTo(_implementation); 64 | } 65 | 66 | /** 67 | * @dev Sets the address of the owner 68 | */ 69 | function _setUpgradeabilityOwner(address _newProxyOwner) internal { 70 | bytes32 position = PROXY_OWNER_POSITION; 71 | assembly { 72 | sstore(position, _newProxyOwner) 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /contracts/mocks/Pool1Mock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "../Pool1.sol"; 4 | import "../ClaimsData.sol"; 5 | 6 | 7 | contract Pool1Mock is Pool1 { 8 | 9 | 10 | function internalLiquiditySwap(bytes4 curr) external { 11 | p2.internalLiquiditySwap(curr); 12 | } 13 | 14 | function mint(address _to, uint _amount) external { 15 | tc.mint(_to, _amount); 16 | } 17 | 18 | function burnFrom(address _from, uint _amount) external { 19 | tc.burnFrom(_from, _amount); 20 | } 21 | 22 | function setpendingClaimStart(uint _start) external { 23 | ClaimsData cd = ClaimsData(ms.getLatestAddress("CD")); 24 | cd.setpendingClaimStart(_start); 25 | } 26 | 27 | function updateStakerCommissions(address _scAddress, uint _premiumNXM) external { 28 | TokenFunctions tf = TokenFunctions(ms.getLatestAddress("TF")); 29 | tf.updateStakerCommissions(_scAddress, _premiumNXM); 30 | } 31 | 32 | function burnStakerLockedToken(uint coverid, bytes4 curr, uint sumAssured) external { 33 | TokenFunctions tf = TokenFunctions(ms.getLatestAddress("TF")); 34 | tf.burnStakerLockedToken(coverid, curr, sumAssured); 35 | } 36 | 37 | function depositCN(uint coverId) public { 38 | TokenFunctions tf = TokenFunctions(ms.getLatestAddress("TF")); 39 | tf.depositCN(coverId); 40 | } 41 | 42 | function transferFundToOtherAdd(address payable _add, uint amt) public { 43 | 44 | _add.transfer(amt); 45 | 46 | } 47 | 48 | function upgradeInvestmentPool(address payable newPoolAddress) public { 49 | p2.upgradeInvestmentPool(newPoolAddress); 50 | } 51 | 52 | function transferCurrencyAssetToAddress(bytes4 _curr, address payable _address, uint _amount) 53 | public returns(bool succ) { 54 | if (_curr == "ETH") { 55 | if (address(this).balance < _amount) 56 | _amount = address(this).balance; 57 | _address.transfer(_amount); 58 | succ = true; 59 | } else { 60 | IERC20 erc20 = IERC20(pd.getCurrencyAssetAddress(_curr)); //solhint-disable-line 61 | if (erc20.balanceOf(address(this)) < _amount) 62 | _amount = erc20.balanceOf(address(this)); 63 | require(erc20.transfer(address(_address), _amount)); 64 | succ = true; 65 | 66 | } 67 | } 68 | 69 | function _oraclizeQuery ( 70 | uint paramCount, 71 | uint timestamp, 72 | string memory datasource, 73 | string memory arg, 74 | uint gasLimit 75 | ) 76 | internal 77 | 78 | returns (bytes32) 79 | { 80 | // To silence compiler warning :( 81 | return bytes32(keccak256( 82 | abi.encodePacked( 83 | paramCount, 84 | timestamp, 85 | datasource, 86 | arg, 87 | gasLimit, 88 | now 89 | ) 90 | )); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /contracts/external/govblocks-protocol/interfaces/IMemberRoles.sol: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 GovBlocks.io 2 | This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | This program is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | You should have received a copy of the GNU General Public License 11 | along with this program. If not, see http://www.gnu.org/licenses/ */ 12 | 13 | pragma solidity 0.5.7; 14 | 15 | 16 | contract IMemberRoles { 17 | 18 | event MemberRole(uint256 indexed roleId, bytes32 roleName, string roleDescription); 19 | 20 | /// @dev Adds new member role 21 | /// @param _roleName New role name 22 | /// @param _roleDescription New description hash 23 | /// @param _authorized Authorized member against every role id 24 | function addRole(bytes32 _roleName, string memory _roleDescription, address _authorized) public; 25 | 26 | /// @dev Assign or Delete a member from specific role. 27 | /// @param _memberAddress Address of Member 28 | /// @param _roleId RoleId to update 29 | /// @param _active active is set to be True if we want to assign this role to member, False otherwise! 30 | function updateRole(address _memberAddress, uint _roleId, bool _active) public; 31 | 32 | /// @dev Change Member Address who holds the authority to Add/Delete any member from specific role. 33 | /// @param _roleId roleId to update its Authorized Address 34 | /// @param _authorized New authorized address against role id 35 | function changeAuthorized(uint _roleId, address _authorized) public; 36 | 37 | /// @dev Return number of member roles 38 | function totalRoles() public view returns(uint256); 39 | 40 | /// @dev Gets the member addresses assigned by a specific role 41 | /// @param _memberRoleId Member role id 42 | /// @return roleId Role id 43 | /// @return allMemberAddress Member addresses of specified role id 44 | function members(uint _memberRoleId) public view returns(uint, address[] memory allMemberAddress); 45 | 46 | /// @dev Gets all members' length 47 | /// @param _memberRoleId Member role id 48 | /// @return memberRoleData[_memberRoleId].memberAddress.length Member length 49 | function numberOfMembers(uint _memberRoleId) public view returns(uint); 50 | 51 | /// @dev Return member address who holds the right to add/remove any member from specific role. 52 | function authorized(uint _memberRoleId) public view returns(address); 53 | 54 | /// @dev Get All role ids array that has been assigned to a member so far. 55 | function roles(address _memberAddress) public view returns(uint[] memory assignedRoles); 56 | 57 | /// @dev Returns true if the given role id is assigned to a member. 58 | /// @param _memberAddress Address of member 59 | /// @param _roleId Checks member's authenticity with the roleId. 60 | /// i.e. Returns true if this roleId is assigned to member 61 | function checkRole(address _memberAddress, uint _roleId) public view returns(bool); 62 | } -------------------------------------------------------------------------------- /contracts/mocks/TokenFunctionMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "./Pool1Mock.sol"; 4 | import "../ClaimsData.sol"; 5 | 6 | 7 | contract TokenFunctionMock is TokenFunctions { 8 | 9 | /** 10 | * @dev Burns tokens staked against a Smart Contract Cover. 11 | * Called when a claim submitted against this cover is accepted. 12 | */ 13 | function burnStakerLockedToken(address scAddress, uint burnNXMAmount) external { 14 | uint totalStaker = td.getStakedContractStakersLength(scAddress); 15 | address stakerAddress; 16 | uint stakerStakedNXM; 17 | uint toBurn = burnNXMAmount; 18 | for (uint i = td.stakedContractCurrentBurnIndex(scAddress); i < totalStaker; i++) { 19 | if (toBurn > 0) { 20 | stakerAddress = td.getStakedContractStakerByIndex(scAddress, i); 21 | uint stakerIndex = td.getStakedContractStakerIndex( 22 | scAddress, i); 23 | uint v; 24 | (v, stakerStakedNXM) = _unlockableBeforeBurningAndCanBurn(stakerAddress, scAddress, stakerIndex); 25 | td.pushUnlockableBeforeLastBurnTokens(stakerAddress, stakerIndex, v); 26 | // stakerStakedNXM = _getStakerStakedTokensOnSmartContract(stakerAddress, scAddress, i); 27 | if (stakerStakedNXM > 0) { 28 | if (stakerStakedNXM >= toBurn) { 29 | _burnStakerTokenLockedAgainstSmartContract( 30 | stakerAddress, scAddress, i, toBurn); 31 | if (i > 0) 32 | td.setStakedContractCurrentBurnIndex(scAddress, i); 33 | toBurn = 0; 34 | break; 35 | } else { 36 | _burnStakerTokenLockedAgainstSmartContract( 37 | stakerAddress, scAddress, i, stakerStakedNXM); 38 | toBurn = toBurn.sub(stakerStakedNXM); 39 | } 40 | } 41 | } else 42 | break; 43 | } 44 | if (toBurn > 0 && totalStaker > 0) 45 | td.setStakedContractCurrentBurnIndex(scAddress, totalStaker.sub(1)); 46 | } 47 | 48 | function mint(address _member, uint _amount) external { 49 | tc.mint(_member, _amount); 50 | } 51 | 52 | function burnFrom(address _of, uint amount) external { 53 | tc.burnFrom(_of, amount); 54 | } 55 | 56 | function reduceLock(address _of, bytes32 _reason, uint256 _time) external { 57 | tc.reduceLock(_of, _reason, _time); 58 | } 59 | 60 | function burnLockedTokens(address _of, bytes32 _reason, uint256 _amount) external { 61 | tc.burnLockedTokens(_of, _reason, _amount); 62 | } 63 | 64 | function releaseLockedTokens(address _of, bytes32 _reason, uint256 _amount) 65 | external 66 | 67 | { 68 | tc.releaseLockedTokens(_of, _reason, _amount); 69 | } 70 | 71 | function upgradeCapitalPool(address payable newPoolAddress) external { 72 | Pool1 p1 = Pool1(ms.getLatestAddress("P1")); 73 | p1.upgradeCapitalPool(newPoolAddress); 74 | } 75 | 76 | function setClaimSubmittedAtEPTrue(uint _index, bool _submit) external { 77 | ClaimsData cd = ClaimsData(ms.getLatestAddress("CD")); 78 | cd.setClaimSubmittedAtEPTrue(_index, _submit); 79 | } 80 | 81 | function transferCurrencyAsset( 82 | bytes4 _curr, 83 | address payable _address, 84 | uint _amount 85 | ) 86 | public 87 | returns(bool) 88 | { 89 | Pool1Mock p1 = Pool1Mock(ms.getLatestAddress("P1")); 90 | 91 | return p1.transferCurrencyAssetToAddress(_curr, _address, _amount); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/utils/getQuote.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var BN = require('bn.js'); 3 | var ethABI = require('ethereumjs-abi'); 4 | var util = require('ethereumjs-util'); 5 | const wallet = require('eth-lightwallet').keystore; 6 | var lightwallet = require('eth-lightwallet'); 7 | async function getQuoteValues(...args) { 8 | var order = { 9 | amount: args[0][0], 10 | curr: args[1], 11 | CP: args[2], 12 | smartCA: args[3], 13 | Price: args[0][1], 14 | price_nxm: args[0][2], 15 | expire: args[0][3], 16 | generationTime: args[0][4], 17 | quotationContract: args[4] 18 | }; 19 | var orderParts = [ 20 | { value: bigNumberToBN(order.amount), type: 'uint' }, 21 | { value: order.curr, type: 'bytes4' }, 22 | { value: bigNumberToBN(order.CP), type: 'uint16' }, 23 | { value: order.smartCA, type: 'address' }, 24 | { value: bigNumberToBN(order.Price), type: 'uint' }, 25 | { value: bigNumberToBN(order.price_nxm), type: 'uint' }, 26 | { value: bigNumberToBN(order.expire), type: 'uint' }, 27 | { value: bigNumberToBN(order.generationTime), type: 'uint' }, 28 | { value: order.quotationContract, type: 'address' } 29 | ]; 30 | 31 | var types = _.map(orderParts, function(o) { 32 | return o.type; 33 | }); 34 | var values = _.map(orderParts, function(o) { 35 | return o.value; 36 | }); 37 | var hashBuff = ethABI.soliditySHA3(types, values); 38 | var hashHex = util.bufferToHex(hashBuff); 39 | 40 | var ks = { 41 | salt: 'mDSSGi5eePbk3dBG4Ddk79f/Jwi5h3d0jI52F3M3yRg=', 42 | hdPathString: "m/44'/60'/0'/0", 43 | encSeed: { 44 | encStr: 45 | '1yzRGJIM7QTLqHzop5H96Txqpy/4P7DlgkPyPDzY9MsmmX6rT0M/4qNnNDX+wTY/NhZnFT84M6wZ8r8keBa/atNo81Xu84bNSRNk4b+W+9/69rcF3fNilP4GtxXE1X5WQhO7m6xeXDgGguQC9YdErDISAwvsSST8sVYhGkmmEtrp7GhE4xmeTA==', 46 | nonce: 'jsdTS0xT7ijtSljsgZabpktsZtNC633V' 47 | }, 48 | encHdRootPriv: { 49 | encStr: 50 | 'OXi2S5Fka6y4TG894bsagLcIPzfbwZlpq+ZTHjufbfaHccQmHnwEZDyjspTarf/OVc/nRI/qT1lOe68k+7bXSO8BTbnGxLorqYr9Qm+ImCaeexCRMYOdK9/Anm+2Aa2gLnjtlgBEf8dIEaWI8LoQhCKeJYSAFggXysoM31wYNQ==', 51 | nonce: 'XsKB+uXOmeSWzhN/XPQXTvru2Aa6pnob' 52 | }, 53 | version: 3, 54 | hdIndex: 1, 55 | encPrivKeys: { 56 | '51042c4d8936a7764d18370a6a0762b860bb8e07': { 57 | key: 'hGPlIoOYX9PKV0CyHHfC1EKYIibpeLKDkEUfWGWqBz25c9yVIk4TCZvMmkzgEMqD', 58 | nonce: 'cDHUhUEaqkJ6OwB4BPJXi5Vw47tbvFYo' 59 | } 60 | }, 61 | addresses: ['51042c4d8936a7764d18370a6a0762b860bb8e07'] 62 | }; 63 | ks = wallet.deserialize(JSON.stringify(ks)); 64 | var pwDerivedKey; 65 | pwDerivedKey = new Uint8Array([ 66 | 51, 67 | 95, 68 | 185, 69 | 86, 70 | 44, 71 | 101, 72 | 34, 73 | 239, 74 | 87, 75 | 233, 76 | 60, 77 | 63, 78 | 119, 79 | 227, 80 | 100, 81 | 242, 82 | 44, 83 | 242, 84 | 130, 85 | 145, 86 | 0, 87 | 32, 88 | 103, 89 | 29, 90 | 142, 91 | 236, 92 | 147, 93 | 33, 94 | 254, 95 | 230, 96 | 9, 97 | 225 98 | ]); 99 | 100 | const orderHashBuff = util.toBuffer(hashHex); 101 | const msgHashBuff = util.hashPersonalMessage(orderHashBuff); 102 | const sig = lightwallet.signing.signMsgHash( 103 | ks, 104 | pwDerivedKey, 105 | msgHashBuff, 106 | ks.addresses[0] 107 | ); 108 | var vrsData = []; 109 | vrsData.push(sig.v); 110 | vrsData.push('0x' + util.toUnsigned(util.fromSigned(sig.r)).toString('hex')); 111 | vrsData.push('0x' + util.toUnsigned(util.fromSigned(sig.s)).toString('hex')); 112 | return vrsData; 113 | } 114 | 115 | function bigNumberToBN(value) { 116 | return new BN(value.toString(), 10); 117 | } 118 | 119 | module.exports = { getQuoteValues }; 120 | -------------------------------------------------------------------------------- /migrations/4_initialize_nexusmutual.js: -------------------------------------------------------------------------------- 1 | const Claims = artifacts.require('Claims'); 2 | const ClaimsData = artifacts.require('ClaimsDataMock'); 3 | const ClaimsReward = artifacts.require('ClaimsReward'); 4 | const DAI = artifacts.require('MockDAI'); 5 | const DSValue = artifacts.require('DSValueMock'); 6 | const NXMaster = artifacts.require('NXMaster'); 7 | const MCR = artifacts.require('MCR'); 8 | const NXMToken = artifacts.require('NXMToken'); 9 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 10 | const TokenController = artifacts.require('TokenController'); 11 | const TokenData = artifacts.require('TokenDataMock'); 12 | const Pool1 = artifacts.require('Pool1Mock'); 13 | const Pool2 = artifacts.require('Pool2'); 14 | const PoolData = artifacts.require('PoolDataMock'); 15 | const Quotation = artifacts.require('Quotation'); 16 | const QuotationDataMock = artifacts.require('QuotationDataMock'); 17 | const MemberRoles = artifacts.require('MemberRoles'); 18 | const Governance = artifacts.require('Governance'); 19 | const ProposalCategory = artifacts.require('ProposalCategory'); 20 | const FactoryMock = artifacts.require('FactoryMock'); 21 | 22 | const QE = '0x51042c4d8936a7764d18370a6a0762b860bb8e07'; 23 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 24 | const POOL_ETHER = '3500000000000000000000'; 25 | const POOL_ASSET = '50000000000000000000'; 26 | 27 | module.exports = function(deployer, network, accounts) { 28 | deployer.then(async () => { 29 | const Owner = accounts[0]; 30 | const nxms = await NXMaster.deployed(); 31 | const tk = await NXMToken.deployed(); 32 | const td = await TokenData.deployed(); 33 | const tf = await TokenFunctions.deployed(); 34 | const tc = await TokenController.deployed(); 35 | const pl1 = await Pool1.deployed(); 36 | const pl2 = await Pool2.deployed(); 37 | const pd = await PoolData.deployed(); 38 | const qt = await Quotation.deployed(); 39 | const qd = await QuotationDataMock.deployed(); 40 | const cl = await Claims.deployed(); 41 | const cr = await ClaimsReward.deployed(); 42 | const cd = await ClaimsData.deployed(); 43 | const mcr = await MCR.deployed(); 44 | const dsv = await DSValue.deployed(); 45 | const gov = await Governance.deployed(); 46 | let propCat = await ProposalCategory.deployed(); 47 | const mr = await MemberRoles.deployed(); 48 | const factory = await FactoryMock.deployed(); 49 | // let gvAdd = await nxms.getLatestAddress("GV"); 50 | // let mrAdd = await nxms.getLatestAddress("MR"); 51 | // let pcAdd = await nxms.getLatestAddress("PC"); 52 | let addr = [ 53 | qd.address, 54 | td.address, 55 | cd.address, 56 | pd.address, 57 | qt.address, 58 | tf.address, 59 | tc.address, 60 | cl.address, 61 | cr.address, 62 | pl1.address, 63 | pl2.address, 64 | mcr.address, 65 | gov.address, 66 | propCat.address, 67 | mr.address 68 | ]; 69 | await nxms.addNewVersion(addr); 70 | let pcAddress = await nxms.getLatestAddress('0x5043'); 71 | pc = await ProposalCategory.at(pcAddress); 72 | await pc.proposalCategoryInitiate(); 73 | const dai = await DAI.deployed(); 74 | // await qd.changeCurrencyAssetAddress('0x444149', dai.address); 75 | // await qd.changeInvestmentAssetAddress('0x444149', dai.address); 76 | await pl1.sendEther({ from: Owner, value: POOL_ETHER }); 77 | await pl2.sendEther({ from: Owner, value: POOL_ETHER }); // 78 | await mcr.addMCRData( 79 | 13000, 80 | '100000000000000000000', 81 | '7000000000000000000000', 82 | ['0x455448', '0x444149'], 83 | [100, 15517], 84 | 20190103 85 | ); 86 | await pl2.saveIADetails( 87 | ['0x455448', '0x444149'], 88 | [100, 15517], 89 | 20190103, 90 | true 91 | ); //testing 92 | await dai.transfer(pl2.address, POOL_ASSET); 93 | let mrInstance = await MemberRoles.at( 94 | await nxms.getLatestAddress('0x4d52') 95 | ); 96 | await mrInstance.payJoiningFee(Owner, { 97 | from: Owner, 98 | value: '2000000000000000' 99 | }); 100 | await mrInstance.kycVerdict(Owner, true); 101 | await mrInstance.addInitialABMembers([Owner]); 102 | }); 103 | }; 104 | -------------------------------------------------------------------------------- /contracts/external/ERC1132/IERC1132.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | /** 4 | * @title ERC1132 interface 5 | * @dev see https://github.com/ethereum/EIPs/issues/1132 6 | */ 7 | 8 | contract IERC1132 { 9 | /** 10 | * @dev Reasons why a user's tokens have been locked 11 | */ 12 | mapping(address => bytes32[]) public lockReason; 13 | 14 | /** 15 | * @dev locked token structure 16 | */ 17 | struct LockToken { 18 | uint256 amount; 19 | uint256 validity; 20 | bool claimed; 21 | } 22 | 23 | /** 24 | * @dev Holds number & validity of tokens locked for a given reason for 25 | * a specified address 26 | */ 27 | mapping(address => mapping(bytes32 => LockToken)) public locked; 28 | 29 | /** 30 | * @dev Records data of all the tokens Locked 31 | */ 32 | event Locked( 33 | address indexed _of, 34 | bytes32 indexed _reason, 35 | uint256 _amount, 36 | uint256 _validity 37 | ); 38 | 39 | /** 40 | * @dev Records data of all the tokens unlocked 41 | */ 42 | event Unlocked( 43 | address indexed _of, 44 | bytes32 indexed _reason, 45 | uint256 _amount 46 | ); 47 | 48 | /** 49 | * @dev Locks a specified amount of tokens against an address, 50 | * for a specified reason and time 51 | * @param _reason The reason to lock tokens 52 | * @param _amount Number of tokens to be locked 53 | * @param _time Lock time in seconds 54 | */ 55 | function lock(bytes32 _reason, uint256 _amount, uint256 _time) 56 | public returns (bool); 57 | 58 | /** 59 | * @dev Returns tokens locked for a specified address for a 60 | * specified reason 61 | * 62 | * @param _of The address whose tokens are locked 63 | * @param _reason The reason to query the lock tokens for 64 | */ 65 | function tokensLocked(address _of, bytes32 _reason) 66 | public view returns (uint256 amount); 67 | 68 | /** 69 | * @dev Returns tokens locked for a specified address for a 70 | * specified reason at a specific time 71 | * 72 | * @param _of The address whose tokens are locked 73 | * @param _reason The reason to query the lock tokens for 74 | * @param _time The timestamp to query the lock tokens for 75 | */ 76 | function tokensLockedAtTime(address _of, bytes32 _reason, uint256 _time) 77 | public view returns (uint256 amount); 78 | 79 | /** 80 | * @dev Returns total tokens held by an address (locked + transferable) 81 | * @param _of The address to query the total balance of 82 | */ 83 | function totalBalanceOf(address _of) 84 | public view returns (uint256 amount); 85 | 86 | /** 87 | * @dev Extends lock for a specified reason and time 88 | * @param _reason The reason to lock tokens 89 | * @param _time Lock extension time in seconds 90 | */ 91 | function extendLock(bytes32 _reason, uint256 _time) 92 | public returns (bool); 93 | 94 | /** 95 | * @dev Increase number of tokens locked for a specified reason 96 | * @param _reason The reason to lock tokens 97 | * @param _amount Number of tokens to be increased 98 | */ 99 | function increaseLockAmount(bytes32 _reason, uint256 _amount) 100 | public returns (bool); 101 | 102 | /** 103 | * @dev Returns unlockable tokens for a specified address for a specified reason 104 | * @param _of The address to query the the unlockable token count of 105 | * @param _reason The reason to query the unlockable tokens for 106 | */ 107 | function tokensUnlockable(address _of, bytes32 _reason) 108 | public view returns (uint256 amount); 109 | 110 | /** 111 | * @dev Unlocks the unlockable tokens of a specified address 112 | * @param _of Address of user, claiming back unlockable tokens 113 | */ 114 | function unlock(address _of) 115 | public returns (uint256 unlockableTokens); 116 | 117 | /** 118 | * @dev Gets the unlockable tokens of a specified address 119 | * @param _of The address to query the the unlockable token count of 120 | */ 121 | function getUnlockableTokens(address _of) 122 | public view returns (uint256 unlockableTokens); 123 | 124 | } -------------------------------------------------------------------------------- /contracts/external/govblocks-protocol/interfaces/IProposalCategory.sol: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 GovBlocks.io 2 | This program is free software: you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation, either version 3 of the License, or 5 | (at your option) any later version. 6 | This program is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | You should have received a copy of the GNU General Public License 11 | along with this program. If not, see http://www.gnu.org/licenses/ */ 12 | 13 | pragma solidity 0.5.7; 14 | 15 | 16 | contract IProposalCategory { 17 | 18 | event Category( 19 | uint indexed categoryId, 20 | string categoryName, 21 | string actionHash 22 | ); 23 | 24 | /// @dev Adds new category 25 | /// @param _name Category name 26 | /// @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. 27 | /// @param _allowedToCreateProposal Member roles allowed to create the proposal 28 | /// @param _majorityVotePerc Majority Vote threshold for Each voting layer 29 | /// @param _quorumPerc minimum threshold percentage required in voting to calculate result 30 | /// @param _closingTime Vote closing time for Each voting layer 31 | /// @param _actionHash hash of details containing the action that has to be performed after proposal is accepted 32 | /// @param _contractAddress address of contract to call after proposal is accepted 33 | /// @param _contractName name of contract to be called after proposal is accepted 34 | /// @param _incentives rewards to distributed after proposal is accepted 35 | function addCategory( 36 | string calldata _name, 37 | uint _memberRoleToVote, 38 | uint _majorityVotePerc, 39 | uint _quorumPerc, 40 | uint[] calldata _allowedToCreateProposal, 41 | uint _closingTime, 42 | string calldata _actionHash, 43 | address _contractAddress, 44 | bytes2 _contractName, 45 | uint[] calldata _incentives 46 | ) 47 | external; 48 | 49 | /// @dev gets category details 50 | function category(uint _categoryId) 51 | external 52 | view 53 | returns( 54 | uint categoryId, 55 | uint memberRoleToVote, 56 | uint majorityVotePerc, 57 | uint quorumPerc, 58 | uint[] memory allowedToCreateProposal, 59 | uint closingTime, 60 | uint minStake 61 | ); 62 | 63 | ///@dev gets category action details 64 | function categoryAction(uint _categoryId) 65 | external 66 | view 67 | returns( 68 | uint categoryId, 69 | address contractAddress, 70 | bytes2 contractName, 71 | uint defaultIncentive 72 | ); 73 | 74 | /// @dev Gets Total number of categories added till now 75 | function totalCategories() external view returns(uint numberOfCategories); 76 | 77 | /// @dev Updates category details 78 | /// @param _categoryId Category id that needs to be updated 79 | /// @param _name Category name 80 | /// @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. 81 | /// @param _allowedToCreateProposal Member roles allowed to create the proposal 82 | /// @param _majorityVotePerc Majority Vote threshold for Each voting layer 83 | /// @param _quorumPerc minimum threshold percentage required in voting to calculate result 84 | /// @param _closingTime Vote closing time for Each voting layer 85 | /// @param _actionHash hash of details containing the action that has to be performed after proposal is accepted 86 | /// @param _contractAddress address of contract to call after proposal is accepted 87 | /// @param _contractName name of contract to be called after proposal is accepted 88 | /// @param _incentives rewards to distributed after proposal is accepted 89 | function updateCategory( 90 | uint _categoryId, 91 | string memory _name, 92 | uint _memberRoleToVote, 93 | uint _majorityVotePerc, 94 | uint _quorumPerc, 95 | uint[] memory _allowedToCreateProposal, 96 | uint _closingTime, 97 | string memory _actionHash, 98 | address _contractAddress, 99 | bytes2 _contractName, 100 | uint[] memory _incentives 101 | ) 102 | public; 103 | 104 | } -------------------------------------------------------------------------------- /test/20_TokenModule.test.js: -------------------------------------------------------------------------------- 1 | const NXMToken = artifacts.require('NXMToken'); 2 | const TokenController = artifacts.require('TokenController'); 3 | const Pool1 = artifacts.require('Pool1Mock'); 4 | const Pool2 = artifacts.require('Pool2'); 5 | const MemberRoles = artifacts.require('MemberRoles'); 6 | const NXMaster = artifacts.require('NXMaster'); 7 | const TokenData = artifacts.require('TokenDataMock'); 8 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 9 | const DAI = artifacts.require('MockDAI'); 10 | const { ether, toHex, toWei } = require('./utils/ethTools'); 11 | const { assertRevert } = require('./utils/assertRevert'); 12 | const { increaseTimeTo } = require('./utils/increaseTime'); 13 | const { latestTime } = require('./utils/latestTime'); 14 | const expectEvent = require('./utils/expectEvent'); 15 | const { advanceBlock } = require('./utils/advanceToBlock'); 16 | 17 | const ETH = '0x455448'; 18 | const fee = ether(0.002); 19 | let ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 20 | 21 | let dai; 22 | let tk; 23 | let tc; 24 | let p1; 25 | let p2; 26 | let mr; 27 | let nxms; 28 | let td; 29 | let tf; 30 | const BN = web3.utils.BN; 31 | 32 | const BigNumber = web3.BigNumber; 33 | require('chai') 34 | .use(require('chai-bignumber')(BigNumber)) 35 | .should(); 36 | 37 | contract('Token Module', function([owner, member1]) { 38 | const UNLIMITED_ALLOWANCE = 39 | '115792089237316195423570985008687907853269984665640564039457584007913129639935'; 40 | before(async function() { 41 | await advanceBlock(); 42 | tk = await NXMToken.deployed(); 43 | p1 = await Pool1.deployed(); 44 | p2 = await Pool2.deployed(); 45 | nxms = await NXMaster.deployed(); 46 | tf = await TokenFunctions.deployed(); 47 | mr = await MemberRoles.at(await nxms.getLatestAddress('0x4d52')); 48 | td = await TokenData.deployed(); 49 | tc = await TokenController.at(await nxms.getLatestAddress(toHex('TC'))); 50 | dai = await DAI.deployed(); 51 | await mr.addMembersBeforeLaunch([], []); 52 | (await mr.launched()).should.be.equal(true); 53 | await tf.upgradeCapitalPool(dai.address); 54 | await p1.sendEther({ from: owner, value: toWei(50) }); 55 | await p1.upgradeInvestmentPool(dai.address); 56 | await mr.payJoiningFee(member1, { from: member1, value: fee }); 57 | await mr.kycVerdict(member1, true); 58 | // await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member1 }); 59 | await tk.transfer(member1, toWei(30000), { from: owner }); 60 | // console.log(await tk.allowance(owner, tc.address)); 61 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: owner }); 62 | }); 63 | describe('NXMToken: ', function() { 64 | it('20.1 onlyOperator "require" operator - else condition', async function() { 65 | await assertRevert(tk.mint(owner, 1)); // tc.mint is changed to tk.mint hence it needs to assertRevert 66 | }); 67 | 68 | it('20.2 approve function "require" - else ZERO_ADDRESS condition is checked', async function() { 69 | await assertRevert( 70 | tk.approve(ZERO_ADDRESS, UNLIMITED_ALLOWANCE, { from: member1 }) 71 | ); 72 | }); 73 | 74 | it('20.3 decreaseAllowance function is called, ZERO_ADDRESS is also checked', async function() { 75 | await tk.decreaseAllowance(tc.address, UNLIMITED_ALLOWANCE, { 76 | from: owner 77 | }); 78 | await assertRevert( 79 | tk.decreaseAllowance(ZERO_ADDRESS, UNLIMITED_ALLOWANCE, { 80 | from: owner 81 | }) 82 | ); 83 | }); 84 | 85 | it('20.4 increaseAllowance function is called, ZERO_ADDRESS is also checked', async function() { 86 | await assertRevert( 87 | tk.increaseAllowance(ZERO_ADDRESS, UNLIMITED_ALLOWANCE, { 88 | from: owner 89 | }) 90 | ); 91 | await tk.increaseAllowance(tc.address, UNLIMITED_ALLOWANCE, { 92 | from: owner 93 | }); 94 | }); 95 | 96 | it('20.5 transfer function "require" - else conditions are checked', async function() { 97 | // to check that transfer is not made to ZERO_ADDRESS 98 | await assertRevert( 99 | tk.transfer(ZERO_ADDRESS, toWei(30000), { from: owner }) 100 | ); 101 | 102 | // to check that owner is not locked for MV 103 | // await tc.lockForMemberVote(owner, 2); // lock the owner, so that it cannot transfer 104 | // await assertRevert(tk.transfer(member1, towei(30000), { from: owner })); 105 | }); 106 | 107 | it('20.6 _mint function "require" - else ZERO_ADDRESS condition is checked', async function() { 108 | await assertRevert(tf.mint(ZERO_ADDRESS, 1)); 109 | }); 110 | 111 | it('20.7 should not be able to burn more than user balance', async function() { 112 | await assertRevert( 113 | tf.burnFrom(member1, (await tk.balanceOf(member1)).toString()) 114 | ); 115 | }); 116 | 117 | it('20.8 should not be able to reduce lock if no locked tokens', async function() { 118 | await assertRevert( 119 | tf.reduceLock(member1, toHex('random'), await latestTime()) 120 | ); 121 | }); 122 | 123 | it('20.9 should not be able to burn if no locked tokens', async function() { 124 | await assertRevert( 125 | tf.burnLockedTokens(member1, toHex('random'), toWei(10)) 126 | ); 127 | }); 128 | 129 | it('20.10 should not be able to release tokens more than he have locked', async function() { 130 | await assertRevert( 131 | tf.releaseLockedTokens(member1, toHex('random'), toWei(10)) 132 | ); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /test/05_Staking.test.js: -------------------------------------------------------------------------------- 1 | const NXMToken = artifacts.require('NXMToken'); 2 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 3 | const TokenController = artifacts.require('TokenController'); 4 | const TokenData = artifacts.require('TokenDataMock'); 5 | const Pool1 = artifacts.require('Pool1Mock'); 6 | const MemberRoles = artifacts.require('MemberRoles'); 7 | const NXMaster = artifacts.require('NXMaster'); 8 | const ClaimsReward = artifacts.require('ClaimsReward'); 9 | 10 | const { assertRevert } = require('./utils/assertRevert'); 11 | const { advanceBlock } = require('./utils/advanceToBlock'); 12 | const { ether, toHex, toWei } = require('./utils/ethTools'); 13 | const { increaseTimeTo, duration } = require('./utils/increaseTime'); 14 | const { latestTime } = require('./utils/latestTime'); 15 | 16 | const stakedContract = '0xd0a6e6c54dbc68db5db3a091b171a77407ff7ccf'; 17 | 18 | let tk; 19 | let tf; 20 | let tc; 21 | let td; 22 | let P1; 23 | let mr; 24 | let nxms; 25 | let cr; 26 | const BN = web3.utils.BN; 27 | 28 | const BigNumber = web3.BigNumber; 29 | require('chai') 30 | .use(require('chai-bignumber')(BigNumber)) 31 | .should(); 32 | 33 | contract('NXMToken:Staking', function([owner, member1, member2, notMember]) { 34 | const fee = ether(0.002); 35 | const stakeTokens = ether(5); 36 | const tokens = ether(200); 37 | const UNLIMITED_ALLOWANCE = new BN((2).toString()) 38 | .pow(new BN((256).toString())) 39 | .sub(new BN((1).toString())); 40 | before(async function() { 41 | await advanceBlock(); 42 | P1 = await Pool1.deployed(); 43 | tk = await NXMToken.deployed(); 44 | tf = await TokenFunctions.deployed(); 45 | td = await TokenData.deployed(); 46 | nxms = await NXMaster.deployed(); 47 | tc = await TokenController.at(await nxms.getLatestAddress(toHex('TC'))); 48 | mr = await MemberRoles.at(await nxms.getLatestAddress('0x4d52')); 49 | cr = await ClaimsReward.deployed(); 50 | await mr.addMembersBeforeLaunch([], []); 51 | (await mr.launched()).should.be.equal(true); 52 | await mr.payJoiningFee(member1, { from: member1, value: fee }); 53 | await mr.kycVerdict(member1, true); 54 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member1 }); 55 | await mr.payJoiningFee(member2, { from: member2, value: fee }); 56 | await mr.kycVerdict(member2, true); 57 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member2 }); 58 | await tk.transfer(member1, tokens); 59 | await tk.transfer(member2, tokens); 60 | }); 61 | 62 | describe('Stake Tokens', function() { 63 | describe('Staker is not member', function() { 64 | it('5.1 reverts', async function() { 65 | await assertRevert( 66 | tf.addStake(stakedContract, stakeTokens, { from: notMember }) 67 | ); 68 | }); 69 | }); 70 | describe('Staker is member', function() { 71 | describe('Staker does not have enough tokens', function() { 72 | it('5.2 reverts', async function() { 73 | await assertRevert( 74 | tf.addStake( 75 | stakedContract, 76 | new BN(stakeTokens.toString()).add( 77 | new BN(toWei(1000000).toString()) 78 | ), 79 | { 80 | from: member1 81 | } 82 | ) 83 | ); 84 | }); 85 | }); 86 | 87 | describe('Staker does have enough tokens', function() { 88 | let initialTokenBalance; 89 | let initialStakedTokens; 90 | it('5.3 should have zero staked tokens before', async function() { 91 | initialTokenBalance = await tk.balanceOf(member1); 92 | initialStakedTokens = await tf.getStakerAllLockedTokens.call(member1); 93 | initialStakedTokens.toString().should.be.equal((0).toString()); 94 | }); 95 | 96 | it('5.4 should be able to add stake on Smart Contracts', async function() { 97 | await tf.addStake(stakedContract, stakeTokens, { from: member1 }); 98 | const newStakedTokens = new BN(initialStakedTokens.toString()).add( 99 | new BN(stakeTokens.toString()) 100 | ); 101 | newStakedTokens 102 | .toString() 103 | .should.be.equal( 104 | (await tf.getStakerAllLockedTokens.call(member1)).toString() 105 | ); 106 | }); 107 | it('5.5 should decrease balance of member', async function() { 108 | const newTokenBalance = new BN(initialTokenBalance.toString()).sub( 109 | new BN(stakeTokens.toString()) 110 | ); 111 | newTokenBalance 112 | .toString() 113 | .should.be.equal((await tk.balanceOf(member1)).toString()); 114 | }); 115 | it('5.6 should return zero stake amt for non staker', async function() { 116 | initialStakedTokens = await tf.getStakerAllLockedTokens.call(member2); 117 | (await tf.getStakerAllLockedTokens.call(member2)) 118 | .toString() 119 | .should.be.equal(initialStakedTokens.toString()); 120 | }); 121 | describe('after 250 days', function() { 122 | before(async function() { 123 | await tf.addStake(member2, stakeTokens, { from: member2 }); 124 | let time = await latestTime(); 125 | time = time + (await duration.days(251)); 126 | await increaseTimeTo(time); 127 | await cr.claimAllPendingReward(20, { from: member2 }); 128 | }); 129 | it('5.7 staker should have zero total locked nxm tokens against smart contract', async function() { 130 | const lockedTokens = await tf.getStakerAllLockedTokens.call( 131 | member2 132 | ); 133 | lockedTokens.toString().should.be.equal((0).toString()); 134 | }); 135 | }); 136 | }); 137 | }); 138 | }); 139 | //contract block 140 | }); 141 | -------------------------------------------------------------------------------- /contracts/external/govblocks-protocol/interfaces/IGovernance.sol: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 GovBlocks.io 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see http://www.gnu.org/licenses/ */ 15 | 16 | pragma solidity 0.5.7; 17 | 18 | 19 | contract IGovernance { 20 | 21 | event Proposal( 22 | address indexed proposalOwner, 23 | uint256 indexed proposalId, 24 | uint256 dateAdd, 25 | string proposalTitle, 26 | string proposalSD, 27 | string proposalDescHash 28 | ); 29 | 30 | event Solution( 31 | uint256 indexed proposalId, 32 | address indexed solutionOwner, 33 | uint256 indexed solutionId, 34 | string solutionDescHash, 35 | uint256 dateAdd 36 | ); 37 | 38 | event Vote( 39 | address indexed from, 40 | uint256 indexed proposalId, 41 | uint256 indexed voteId, 42 | uint256 dateAdd, 43 | uint256 solutionChosen 44 | ); 45 | 46 | event RewardClaimed( 47 | address indexed member, 48 | uint gbtReward 49 | ); 50 | 51 | /// @dev VoteCast event is called whenever a vote is cast that can potentially close the proposal. 52 | event VoteCast (uint256 proposalId); 53 | 54 | /// @dev ProposalAccepted event is called when a proposal is accepted so that a server can listen that can 55 | /// call any offchain actions 56 | event ProposalAccepted (uint256 proposalId); 57 | 58 | /// @dev CloseProposalOnTime event is called whenever a proposal is created or updated to close it on time. 59 | event CloseProposalOnTime ( 60 | uint256 indexed proposalId, 61 | uint256 time 62 | ); 63 | 64 | /// @dev ActionSuccess event is called whenever an onchain action is executed. 65 | event ActionSuccess ( 66 | uint256 proposalId 67 | ); 68 | 69 | /// @dev Creates a new proposal 70 | /// @param _proposalDescHash Proposal description hash through IPFS having Short and long description of proposal 71 | /// @param _categoryId This id tells under which the proposal is categorized i.e. Proposal's Objective 72 | function createProposal( 73 | string calldata _proposalTitle, 74 | string calldata _proposalSD, 75 | string calldata _proposalDescHash, 76 | uint _categoryId 77 | ) 78 | external; 79 | 80 | /// @dev Edits the details of an existing proposal and creates new version 81 | /// @param _proposalId Proposal id that details needs to be updated 82 | /// @param _proposalDescHash Proposal description hash having long and short description of proposal. 83 | function updateProposal( 84 | uint _proposalId, 85 | string calldata _proposalTitle, 86 | string calldata _proposalSD, 87 | string calldata _proposalDescHash 88 | ) 89 | external; 90 | 91 | /// @dev Categorizes proposal to proceed further. Categories shows the proposal objective. 92 | function categorizeProposal( 93 | uint _proposalId, 94 | uint _categoryId, 95 | uint _incentives 96 | ) 97 | external; 98 | 99 | /// @dev Initiates add solution 100 | /// @param _solutionHash Solution hash having required data against adding solution 101 | function addSolution( 102 | uint _proposalId, 103 | string calldata _solutionHash, 104 | bytes calldata _action 105 | ) 106 | external; 107 | 108 | /// @dev Opens proposal for voting 109 | function openProposalForVoting(uint _proposalId) external; 110 | 111 | /// @dev Submit proposal with solution 112 | /// @param _proposalId Proposal id 113 | /// @param _solutionHash Solution hash contains parameters, values and description needed according to proposal 114 | function submitProposalWithSolution( 115 | uint _proposalId, 116 | string calldata _solutionHash, 117 | bytes calldata _action 118 | ) 119 | external; 120 | 121 | /// @dev Creates a new proposal with solution and votes for the solution 122 | /// @param _proposalDescHash Proposal description hash through IPFS having Short and long description of proposal 123 | /// @param _categoryId This id tells under which the proposal is categorized i.e. Proposal's Objective 124 | /// @param _solutionHash Solution hash contains parameters, values and description needed according to proposal 125 | function createProposalwithSolution( 126 | string calldata _proposalTitle, 127 | string calldata _proposalSD, 128 | string calldata _proposalDescHash, 129 | uint _categoryId, 130 | string calldata _solutionHash, 131 | bytes calldata _action 132 | ) 133 | external; 134 | 135 | /// @dev Casts vote 136 | /// @param _proposalId Proposal id 137 | /// @param _solutionChosen solution chosen while voting. _solutionChosen[0] is the chosen solution 138 | function submitVote(uint _proposalId, uint _solutionChosen) external; 139 | 140 | function closeProposal(uint _proposalId) external; 141 | 142 | function claimReward(address _memberAddress, uint _maxRecords) external returns(uint pendingDAppReward); 143 | 144 | function proposal(uint _proposalId) 145 | external 146 | view 147 | returns( 148 | uint proposalId, 149 | uint category, 150 | uint status, 151 | uint finalVerdict, 152 | uint totalReward 153 | ); 154 | 155 | function canCloseProposal(uint _proposalId) public view returns(uint closeValue); 156 | 157 | function pauseProposal(uint _proposalId) public; 158 | 159 | function resumeProposal(uint _proposalId) public; 160 | 161 | function allowedToCatgorize() public view returns(uint roleId); 162 | 163 | } -------------------------------------------------------------------------------- /test/14_ProposalCategory.test.js: -------------------------------------------------------------------------------- 1 | const MemberRoles = artifacts.require('MemberRoles'); 2 | const Governance = artifacts.require('Governance'); 3 | const ProposalCategory = artifacts.require('ProposalCategory'); 4 | const NXMaster = artifacts.require('NXMaster'); 5 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 6 | const assertRevert = require('./utils/assertRevert').assertRevert; 7 | const { toHex, toWei } = require('./utils/ethTools'); 8 | let pc; 9 | let gv; 10 | let tf; 11 | let mr; 12 | let nullAddress = '0x0000000000000000000000000000000000000000'; 13 | const encode = require('./utils/encoder.js').encode; 14 | 15 | contract('Proposal Category', function([owner, other]) { 16 | before(async function() { 17 | nxms = await NXMaster.deployed(); 18 | let address = await nxms.getLatestAddress(toHex('PC')); 19 | pc = await ProposalCategory.at(address); 20 | address = await nxms.getLatestAddress(toHex('GV')); 21 | gv = await Governance.at(address); 22 | tf = await TokenFunctions.deployed(); 23 | address = await nxms.getLatestAddress(toHex('MR')); 24 | mr = await MemberRoles.at(address); 25 | }); 26 | 27 | it('14.1 Should be initialized', async function() { 28 | await assertRevert(pc.proposalCategoryInitiate()); 29 | const g1 = await pc.totalCategories(); 30 | const g2 = await pc.category(1); 31 | assert.equal(g2[1].toNumber(), 1); 32 | const g5 = await pc.categoryAction(1); 33 | assert.equal(g5[2].toString(), '0x4d52'); 34 | const g6 = await pc.totalCategories(); 35 | assert.equal(g6.toNumber(), 33); 36 | }); 37 | 38 | it('14.2 should not allow unauthorized to change master address', async function() { 39 | await assertRevert(pc.changeMasterAddress(nxms.address, { from: other })); 40 | }); 41 | 42 | it('14.3 Should not add a proposal category if member roles are invalid', async function() { 43 | let c1 = await pc.totalCategories(); 44 | await assertRevert( 45 | pc.addCategory('Yo', 1, 1, 0, [1], 1, '', nullAddress, toHex('EX'), [ 46 | 0, 47 | 0, 48 | 0 49 | ]) 50 | ); 51 | //proposal to add category 52 | let actionHash = encode( 53 | 'addCategory(string,uint,uint,uint,uint[],uint,string,address,bytes2,uint[])', 54 | 'Description', 55 | 1, 56 | 1, 57 | 0, 58 | [5], 59 | 1, 60 | '', 61 | nullAddress, 62 | toHex('EX'), 63 | [0, 0, 0, 1] 64 | ); 65 | let p1 = await gv.getProposalLength(); 66 | await gv.createProposalwithSolution( 67 | 'Add new member', 68 | 'Add new member', 69 | 'Addnewmember', 70 | 3, 71 | 'Add new member', 72 | actionHash 73 | ); 74 | await gv.submitVote(p1.toNumber(), 1); 75 | await gv.closeProposal(p1.toNumber()); 76 | const c2 = await pc.totalCategories(); 77 | assert.equal(c2.toNumber(), c1.toNumber(), 'category added'); 78 | }); 79 | 80 | it('14.3 Should add a proposal category', async function() { 81 | let c1 = await pc.totalCategories(); 82 | await assertRevert( 83 | pc.addCategory('Yo', 1, 1, 0, [1], 1, '', nullAddress, toHex('EX'), [ 84 | 0, 85 | 0, 86 | 0 87 | ]) 88 | ); 89 | //proposal to add category 90 | let actionHash = encode( 91 | 'addCategory(string,uint,uint,uint,uint[],uint,string,address,bytes2,uint[])', 92 | 'Description', 93 | 1, 94 | 1, 95 | 0, 96 | [1], 97 | 1, 98 | '', 99 | nullAddress, 100 | toHex('EX'), 101 | [0, 0, 0, 1] 102 | ); 103 | let p1 = await gv.getProposalLength(); 104 | await gv.createProposalwithSolution( 105 | 'Add new member', 106 | 'Add new member', 107 | 'Addnewmember', 108 | 3, 109 | 'Add new member', 110 | actionHash 111 | ); 112 | await gv.submitVote(p1.toNumber(), 1); 113 | await gv.closeProposal(p1.toNumber()); 114 | }); 115 | 116 | it('14.4 Should update a proposal category', async function() { 117 | let c1 = await pc.totalCategories(); 118 | c1 = c1.toNumber() - 1; 119 | const cat1 = await pc.category(c1); 120 | await assertRevert( 121 | pc.updateCategory( 122 | c1, 123 | 'Yo', 124 | 1, 125 | 1, 126 | 0, 127 | [1], 128 | 1, 129 | '', 130 | nullAddress, 131 | toHex('EX'), 132 | [0, 0, 0] 133 | ) 134 | ); 135 | //proposal to update category 136 | let actionHash = encode( 137 | 'updateCategory(uint,string,uint,uint,uint,uint[],uint,string,address,bytes2,uint[])', 138 | c1, 139 | 'YoYo', 140 | 2, 141 | 1, 142 | 20, 143 | [1], 144 | 1, 145 | '', 146 | nullAddress, 147 | toHex('EX'), 148 | [0, 0, 0] 149 | ); 150 | let p1 = await gv.getProposalLength(); 151 | await gv.createProposalwithSolution( 152 | 'Add new member', 153 | 'Add new member', 154 | 'Addnewmember', 155 | 4, 156 | 'Add new member', 157 | actionHash 158 | ); 159 | await gv.submitVote(p1.toNumber(), 1); 160 | await gv.closeProposal(p1.toNumber()); 161 | let cat2 = await pc.category(c1); 162 | assert.notEqual(cat1[1], cat2[1], 'category not updated'); 163 | }); 164 | 165 | it('14.5 Should not update a proposal category if member roles are invalid', async function() { 166 | let c1 = await pc.totalCategories(); 167 | c1 = c1.toNumber() - 1; 168 | const cat1 = await pc.category(c1); 169 | await assertRevert( 170 | pc.updateCategory( 171 | c1, 172 | 'Yo', 173 | 1, 174 | 1, 175 | 0, 176 | [1], 177 | 1, 178 | '', 179 | nullAddress, 180 | toHex('EX'), 181 | [0, 0, 0] 182 | ) 183 | ); 184 | //proposal to update category 185 | let actionHash = encode( 186 | 'updateCategory(uint,string,uint,uint,uint,uint[],uint,string,address,bytes2,uint[])', 187 | c1, 188 | 'YoYo', 189 | 2, 190 | 1, 191 | 20, 192 | [7], 193 | 1, 194 | '', 195 | nullAddress, 196 | toHex('EX'), 197 | [0, 0, 0] 198 | ); 199 | let p1 = await gv.getProposalLength(); 200 | await gv.createProposalwithSolution( 201 | 'Add new member', 202 | 'Add new member', 203 | 'Addnewmember', 204 | 4, 205 | 'Add new member', 206 | actionHash 207 | ); 208 | await gv.submitVote(p1.toNumber(), 1); 209 | await gv.closeProposal(p1.toNumber()); 210 | let cat2 = await pc.category(c1); 211 | assert.notEqual(cat1[1], cat2[1], 'category not updated'); 212 | }); 213 | }); 214 | -------------------------------------------------------------------------------- /contracts/mocks/ExchangeMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "./FactoryMock.sol"; 4 | import "../external/openzeppelin-solidity/token/ERC20/ERC20.sol"; 5 | 6 | 7 | contract ExchangeMock { 8 | 9 | ERC20 internal token; 10 | FactoryMock internal factory; 11 | 12 | constructor (address tokenAddress, address factoryAddress) public { 13 | token = ERC20(tokenAddress); 14 | factory = FactoryMock(factoryAddress); 15 | } 16 | 17 | function recieveEther() public payable { 18 | 19 | } 20 | 21 | function removeEther(uint val) public { 22 | 23 | (msg.sender).transfer(val); 24 | } 25 | 26 | function sendEther() public payable { 27 | 28 | } 29 | 30 | function rateFactor() public view returns(uint256) { 31 | if (token.id() == 1) { 32 | return 10; 33 | } else 34 | return 5; 35 | } 36 | 37 | function getEthToTokenInputPrice(uint256 ethSold) public view returns(uint256) { 38 | // require(ethSold > 0); 39 | // uint256 tokenReserve = token.balanceOf(address(this)); 40 | // return getInputPrice(ethSold, address(this).balance, tokenReserve); 41 | return ethSold*rateFactor(); 42 | } 43 | 44 | function getTokenToEthInputPrice(uint256 tokensSold) public view returns(uint256) { 45 | // require(tokensSold > 0); 46 | // uint256 tokenReserve = token.balanceOf(address(this)); 47 | // uint256 ethBought = getInputPrice(tokensSold, tokenReserve, address(this).balance); 48 | // return (ethBought * 10**18); 49 | return (tokensSold/rateFactor()); 50 | } 51 | 52 | function ethToTokenSwapInput( 53 | uint256 minTokens, 54 | uint256 deadline 55 | ) 56 | public 57 | payable 58 | returns (uint256) 59 | { 60 | return ethToTokenInput(msg.value, minTokens, deadline, msg.sender, msg.sender); 61 | } 62 | 63 | function ethToTokenTransferInput( 64 | uint256 minTokens, 65 | uint256 deadline, 66 | address recipient 67 | ) 68 | public 69 | payable 70 | returns (uint256) 71 | { 72 | require(recipient != address(this) && recipient != address(0)); 73 | return ethToTokenInput(msg.value, minTokens, deadline, msg.sender, recipient); 74 | } 75 | 76 | function tokenToEthSwapInput( 77 | uint256 tokensSold, 78 | uint256 minEth, 79 | uint256 deadline 80 | ) 81 | public 82 | payable 83 | returns (uint256) 84 | { 85 | return tokenToEthInput(tokensSold, minEth, deadline, msg.sender, msg.sender); 86 | } 87 | 88 | function tokenToEthTransferInput( 89 | uint256 tokensSold, 90 | uint256 minEth, 91 | uint256 deadline, 92 | address payable recipient 93 | ) 94 | public 95 | payable 96 | returns (uint256) 97 | { 98 | require(recipient != address(this) && recipient != address(0)); 99 | return tokenToEthInput(tokensSold, minEth, deadline, msg.sender, recipient); 100 | } 101 | 102 | function tokenToTokenSwapInput( 103 | uint256 tokensSold, 104 | uint256 minTokensBought, 105 | uint256 minEthBought, 106 | uint256 deadline, 107 | address tokenAddress 108 | ) 109 | public 110 | returns (uint256) 111 | { 112 | 113 | address exchangeAddress = factory.getExchange(tokenAddress); 114 | return tokenToTokenInput( 115 | tokensSold, 116 | minTokensBought, 117 | minEthBought, 118 | deadline, 119 | msg.sender, 120 | msg.sender, 121 | exchangeAddress 122 | ); 123 | } 124 | 125 | function tokenToTokenTransferInput( 126 | uint256 tokensSold, 127 | uint256 minTokensBought, 128 | uint256 minEthBought, 129 | uint256 deadline, 130 | address recipient, 131 | address tokenAddress 132 | ) 133 | public 134 | returns (uint256) 135 | { 136 | address exchangeAddress = factory.getExchange(tokenAddress); 137 | return tokenToTokenInput( 138 | tokensSold, 139 | minTokensBought, 140 | minEthBought, 141 | deadline, 142 | msg.sender, 143 | recipient, 144 | exchangeAddress 145 | ); 146 | } 147 | 148 | function getInputPrice( 149 | uint256 inputAmount, 150 | uint256 inputReserve, 151 | uint256 outputReserve 152 | ) 153 | internal 154 | pure 155 | returns(uint256) 156 | { 157 | require(inputReserve > 0 && outputReserve > 0); 158 | uint256 inputAmountWithFee = inputAmount * 997; 159 | uint256 numerator = inputAmountWithFee * outputReserve; 160 | uint256 denominator = (inputReserve * 1000) + inputAmountWithFee; 161 | return (numerator / denominator); 162 | } 163 | 164 | function getOutputPrice( 165 | uint256 outputAmount, 166 | uint256 inputReserve, 167 | uint256 outputReserve 168 | ) 169 | internal 170 | pure 171 | returns(uint256) 172 | { 173 | require(inputReserve > 0 && outputReserve > 0); 174 | uint256 numerator = inputReserve * outputAmount * 1000; 175 | uint256 denominator = (outputReserve - outputAmount) * 997; 176 | return (numerator / denominator + 1); 177 | } 178 | 179 | function ethToTokenInput( 180 | uint256 ethSold, 181 | uint256 minTokens, 182 | uint256 deadline, 183 | address buyer, 184 | address recipient 185 | ) 186 | internal 187 | returns (uint256) 188 | { 189 | require(deadline >= block.timestamp && ethSold > 0 && minTokens > 0); 190 | // uint256 tokenReserve = token.balanceOf(address(this)); 191 | uint256 tokensBought = ethSold*rateFactor(); 192 | require(tokensBought >= minTokens); 193 | require(token.transfer(recipient, tokensBought)); 194 | buyer; 195 | return tokensBought; 196 | } 197 | 198 | function tokenToTokenInput( 199 | uint256 tokensSold, 200 | uint256 minTokensBought, 201 | uint256 minEthBought, 202 | uint256 deadline, 203 | address buyer, 204 | address recipient, 205 | address exchangeAddress 206 | ) 207 | internal 208 | returns (uint256) 209 | { 210 | 211 | require((deadline >= block.timestamp && tokensSold > 0) && (minTokensBought > 0 && minEthBought > 0)); 212 | require(exchangeAddress != address(this) && exchangeAddress != address(0)); 213 | // uint256 tokenReserve = token.balanceOf(address(this)); 214 | uint256 ethBought = tokensSold/rateFactor(); 215 | uint256 weiBought = (ethBought); 216 | require(weiBought >= minEthBought); 217 | require(token.transferFrom(buyer, address(this), tokensSold)); 218 | 219 | 220 | 221 | uint256 tokensBought = ExchangeMock(exchangeAddress).ethToTokenTransferInput.value( 222 | weiBought)(minTokensBought, deadline, recipient); 223 | // log.EthPurchase(buyer, tokensSold, weiBought); 224 | return tokensBought; 225 | } 226 | 227 | function tokenToEthInput( 228 | uint256 tokensSold, 229 | uint256 minEth, 230 | uint256 deadline, 231 | address buyer, 232 | address payable recipient 233 | ) 234 | internal 235 | returns (uint256) 236 | { 237 | require(deadline >= block.timestamp && tokensSold > 0 && minEth > 0); 238 | // uint256 tokenReserve = token.balanceOf(address(this)); 239 | uint256 ethBought = tokensSold/rateFactor(); 240 | uint256 weiBought = ethBought; 241 | require(weiBought >= minEth); 242 | recipient.transfer(weiBought); 243 | require(token.transferFrom(buyer, address(this), tokensSold)); 244 | // log.EthPurchase(buyer, tokens_sold, wei_bought) 245 | return weiBought; 246 | } 247 | } -------------------------------------------------------------------------------- /contracts/mocks/MockMKR.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "../external/openzeppelin-solidity/token/ERC20/IERC20.sol"; 4 | import "../external/openzeppelin-solidity/math/SafeMath.sol"; 5 | 6 | 7 | contract MockMKR is IERC20 { 8 | using SafeMath for uint256; 9 | 10 | string public name = "MKR"; 11 | string public symbol = "MKR"; 12 | uint public id = 2; 13 | uint8 public decimals = 18; 14 | uint256 private _totalSupply; 15 | 16 | mapping (address => uint256) private _balances; 17 | 18 | mapping (address => mapping (address => uint256)) private _allowed; 19 | 20 | constructor() public { 21 | _totalSupply = 999999 * (10**uint(decimals)); 22 | _balances[msg.sender] = _totalSupply; 23 | } 24 | 25 | /** 26 | * @dev Total number of tokens in existence 27 | */ 28 | function totalSupply() public view returns (uint256) { 29 | return _totalSupply; 30 | } 31 | 32 | /** 33 | * @dev Gets the balance of the specified address. 34 | * @param owner The address to query the balance of. 35 | * @return An uint256 representing the amount owned by the passed address. 36 | */ 37 | function balanceOf(address owner) public view returns (uint256) { 38 | return _balances[owner]; 39 | } 40 | 41 | /** 42 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 43 | * @param owner address The address which owns the funds. 44 | * @param spender address The address which will spend the funds. 45 | * @return A uint256 specifying the amount of tokens still available for the spender. 46 | */ 47 | function allowance( 48 | address owner, 49 | address spender 50 | ) 51 | public 52 | view 53 | returns (uint256) 54 | { 55 | return _allowed[owner][spender]; 56 | } 57 | 58 | /** 59 | * @dev Transfer token for a specified address 60 | * @param to The address to transfer to. 61 | * @param value The amount to be transferred. 62 | */ 63 | function transfer(address to, uint256 value) public returns (bool) { 64 | _transfer(msg.sender, to, value); 65 | return true; 66 | } 67 | 68 | /** 69 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 70 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 71 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 72 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 73 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 74 | * @param spender The address which will spend the funds. 75 | * @param value The amount of tokens to be spent. 76 | */ 77 | function approve(address spender, uint256 value) public returns (bool) { 78 | require(spender != address(0)); 79 | 80 | _allowed[msg.sender][spender] = value; 81 | emit Approval(msg.sender, spender, value); 82 | return true; 83 | } 84 | 85 | /** 86 | * @dev Transfer tokens from one address to another 87 | * @param from address The address which you want to send tokens from 88 | * @param to address The address which you want to transfer to 89 | * @param value uint256 the amount of tokens to be transferred 90 | */ 91 | function transferFrom( 92 | address from, 93 | address to, 94 | uint256 value 95 | ) 96 | public 97 | returns (bool) 98 | { 99 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 100 | _transfer(from, to, value); 101 | return true; 102 | } 103 | 104 | /** 105 | * @dev Increase the amount of tokens that an owner allowed to a spender. 106 | * approve should be called when allowed_[_spender] == 0. To increment 107 | * allowed value is better to use this function to avoid 2 calls (and wait until 108 | * the first transaction is mined) 109 | * From MonolithDAO Token.sol 110 | * @param spender The address which will spend the funds. 111 | * @param addedValue The amount of tokens to increase the allowance by. 112 | */ 113 | function increaseAllowance( 114 | address spender, 115 | uint256 addedValue 116 | ) 117 | public 118 | returns (bool) 119 | { 120 | require(spender != address(0)); 121 | 122 | _allowed[msg.sender][spender] = ( 123 | _allowed[msg.sender][spender].add(addedValue)); 124 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 125 | return true; 126 | } 127 | 128 | /** 129 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 130 | * approve should be called when allowed_[_spender] == 0. To decrement 131 | * allowed value is better to use this function to avoid 2 calls (and wait until 132 | * the first transaction is mined) 133 | * From MonolithDAO Token.sol 134 | * @param spender The address which will spend the funds. 135 | * @param subtractedValue The amount of tokens to decrease the allowance by. 136 | */ 137 | function decreaseAllowance( 138 | address spender, 139 | uint256 subtractedValue 140 | ) 141 | public 142 | returns (bool) 143 | { 144 | require(spender != address(0)); 145 | 146 | _allowed[msg.sender][spender] = ( 147 | _allowed[msg.sender][spender].sub(subtractedValue)); 148 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 149 | return true; 150 | } 151 | 152 | /** 153 | * @dev Transfer token for a specified addresses 154 | * @param from The address to transfer from. 155 | * @param to The address to transfer to. 156 | * @param value The amount to be transferred. 157 | */ 158 | function _transfer(address from, address to, uint256 value) internal { 159 | require(to != address(0)); 160 | 161 | _balances[from] = _balances[from].sub(value); 162 | _balances[to] = _balances[to].add(value); 163 | emit Transfer(from, to, value); 164 | } 165 | 166 | /** 167 | * @dev Internal function that mints an amount of the token and assigns it to 168 | * an account. This encapsulates the modification of balances such that the 169 | * proper events are emitted. 170 | * @param account The account that will receive the created tokens. 171 | * @param value The amount that will be created. 172 | */ 173 | function _mint(address account, uint256 value) internal { 174 | require(account != address(0)); 175 | 176 | _totalSupply = _totalSupply.add(value); 177 | _balances[account] = _balances[account].add(value); 178 | emit Transfer(address(0), account, value); 179 | } 180 | 181 | /** 182 | * @dev Internal function that burns an amount of the token of a given 183 | * account. 184 | * @param account The account whose tokens will be burnt. 185 | * @param value The amount that will be burnt. 186 | */ 187 | function _burn(address account, uint256 value) internal { 188 | require(account != address(0)); 189 | 190 | _totalSupply = _totalSupply.sub(value); 191 | _balances[account] = _balances[account].sub(value); 192 | emit Transfer(account, address(0), value); 193 | } 194 | 195 | /** 196 | * @dev Internal function that burns an amount of the token of a given 197 | * account, deducting from the sender's allowance for said account. Uses the 198 | * internal burn function. 199 | * @param account The account whose tokens will be burnt. 200 | * @param value The amount that will be burnt. 201 | */ 202 | function _burnFrom(address account, uint256 value) internal { 203 | // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, 204 | // this function needs to emit an event with the updated approval. 205 | _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( 206 | value); 207 | _burn(account, value); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /contracts/mocks/MockDAI.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "../external/openzeppelin-solidity/token/ERC20/IERC20.sol"; 4 | import "../external/openzeppelin-solidity/math/SafeMath.sol"; 5 | 6 | 7 | contract MockDAI is IERC20 { 8 | using SafeMath for uint256; 9 | 10 | string public name = "DAI"; 11 | uint public id = 1; 12 | string public symbol = "DAI"; 13 | uint8 public decimals = 18; 14 | uint256 private _totalSupply; 15 | 16 | mapping (address => uint256) private _balances; 17 | 18 | mapping (address => mapping (address => uint256)) private _allowed; 19 | 20 | constructor() public { 21 | _totalSupply = 999999 * (10**uint(decimals)); 22 | _balances[msg.sender] = _totalSupply; 23 | } 24 | 25 | function sendEther() public payable { 26 | 27 | } 28 | 29 | /** 30 | * @dev Total number of tokens in existence 31 | */ 32 | function totalSupply() public view returns (uint256) { 33 | return _totalSupply; 34 | } 35 | 36 | /** 37 | * @dev Gets the balance of the specified address. 38 | * @param owner The address to query the balance of. 39 | * @return An uint256 representing the amount owned by the passed address. 40 | */ 41 | function balanceOf(address owner) public view returns (uint256) { 42 | return _balances[owner]; 43 | } 44 | 45 | /** 46 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 47 | * @param owner address The address which owns the funds. 48 | * @param spender address The address which will spend the funds. 49 | * @return A uint256 specifying the amount of tokens still available for the spender. 50 | */ 51 | function allowance( 52 | address owner, 53 | address spender 54 | ) 55 | public 56 | view 57 | returns (uint256) 58 | { 59 | return _allowed[owner][spender]; 60 | } 61 | 62 | /** 63 | * @dev Transfer token for a specified address 64 | * @param to The address to transfer to. 65 | * @param value The amount to be transferred. 66 | */ 67 | function transfer(address to, uint256 value) public returns (bool) { 68 | _transfer(msg.sender, to, value); 69 | return true; 70 | } 71 | 72 | /** 73 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 74 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 75 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 76 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 77 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 78 | * @param spender The address which will spend the funds. 79 | * @param value The amount of tokens to be spent. 80 | */ 81 | function approve(address spender, uint256 value) public returns (bool) { 82 | require(spender != address(0)); 83 | 84 | _allowed[msg.sender][spender] = value; 85 | emit Approval(msg.sender, spender, value); 86 | return true; 87 | } 88 | 89 | /** 90 | * @dev Transfer tokens from one address to another 91 | * @param from address The address which you want to send tokens from 92 | * @param to address The address which you want to transfer to 93 | * @param value uint256 the amount of tokens to be transferred 94 | */ 95 | function transferFrom( 96 | address from, 97 | address to, 98 | uint256 value 99 | ) 100 | public 101 | returns (bool) 102 | { 103 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 104 | _transfer(from, to, value); 105 | return true; 106 | } 107 | 108 | /** 109 | * @dev Increase the amount of tokens that an owner allowed to a spender. 110 | * approve should be called when allowed_[_spender] == 0. To increment 111 | * allowed value is better to use this function to avoid 2 calls (and wait until 112 | * the first transaction is mined) 113 | * From MonolithDAO Token.sol 114 | * @param spender The address which will spend the funds. 115 | * @param addedValue The amount of tokens to increase the allowance by. 116 | */ 117 | function increaseAllowance( 118 | address spender, 119 | uint256 addedValue 120 | ) 121 | public 122 | returns (bool) 123 | { 124 | require(spender != address(0)); 125 | 126 | _allowed[msg.sender][spender] = ( 127 | _allowed[msg.sender][spender].add(addedValue)); 128 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 129 | return true; 130 | } 131 | 132 | /** 133 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 134 | * approve should be called when allowed_[_spender] == 0. To decrement 135 | * allowed value is better to use this function to avoid 2 calls (and wait until 136 | * the first transaction is mined) 137 | * From MonolithDAO Token.sol 138 | * @param spender The address which will spend the funds. 139 | * @param subtractedValue The amount of tokens to decrease the allowance by. 140 | */ 141 | function decreaseAllowance( 142 | address spender, 143 | uint256 subtractedValue 144 | ) 145 | public 146 | returns (bool) 147 | { 148 | require(spender != address(0)); 149 | 150 | _allowed[msg.sender][spender] = ( 151 | _allowed[msg.sender][spender].sub(subtractedValue)); 152 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 153 | return true; 154 | } 155 | 156 | /** 157 | * @dev Transfer token for a specified addresses 158 | * @param from The address to transfer from. 159 | * @param to The address to transfer to. 160 | * @param value The amount to be transferred. 161 | */ 162 | function _transfer(address from, address to, uint256 value) internal { 163 | require(to != address(0)); 164 | 165 | _balances[from] = _balances[from].sub(value); 166 | _balances[to] = _balances[to].add(value); 167 | emit Transfer(from, to, value); 168 | } 169 | 170 | /** 171 | * @dev Internal function that mints an amount of the token and assigns it to 172 | * an account. This encapsulates the modification of balances such that the 173 | * proper events are emitted. 174 | * @param account The account that will receive the created tokens. 175 | * @param value The amount that will be created. 176 | */ 177 | function _mint(address account, uint256 value) internal { 178 | require(account != address(0)); 179 | 180 | _totalSupply = _totalSupply.add(value); 181 | _balances[account] = _balances[account].add(value); 182 | emit Transfer(address(0), account, value); 183 | } 184 | 185 | /** 186 | * @dev Internal function that burns an amount of the token of a given 187 | * account. 188 | * @param account The account whose tokens will be burnt. 189 | * @param value The amount that will be burnt. 190 | */ 191 | function _burn(address account, uint256 value) internal { 192 | require(account != address(0)); 193 | 194 | _totalSupply = _totalSupply.sub(value); 195 | _balances[account] = _balances[account].sub(value); 196 | emit Transfer(account, address(0), value); 197 | } 198 | 199 | /** 200 | * @dev Internal function that burns an amount of the token of a given 201 | * account, deducting from the sender's allowance for said account. Uses the 202 | * internal burn function. 203 | * @param account The account whose tokens will be burnt. 204 | * @param value The amount that will be burnt. 205 | */ 206 | function _burnFrom(address account, uint256 value) internal { 207 | // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, 208 | // this function needs to emit an event with the updated approval. 209 | _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( 210 | value); 211 | _burn(account, value); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /contracts/external/openzeppelin-solidity/token/ERC20/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.7; 2 | 3 | import "./IERC20.sol"; 4 | import "../../math/SafeMath.sol"; 5 | 6 | 7 | /** 8 | * @title Standard ERC20 token 9 | * 10 | * @dev Implementation of the basic standard token. 11 | * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 12 | * Originally based on code by FirstBlood: 13 | * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol 14 | */ 15 | contract ERC20 is IERC20 { 16 | using SafeMath for uint256; 17 | 18 | uint public id; 19 | 20 | mapping (address => uint256) private _balances; 21 | 22 | mapping (address => mapping (address => uint256)) private _allowed; 23 | 24 | uint256 private _totalSupply; 25 | 26 | /** 27 | * @dev Total number of tokens in existence 28 | */ 29 | function totalSupply() public view returns (uint256) { 30 | return _totalSupply; 31 | } 32 | 33 | /** 34 | * @dev Gets the balance of the specified address. 35 | * @param owner The address to query the balance of. 36 | * @return An uint256 representing the amount owned by the passed address. 37 | */ 38 | function balanceOf(address owner) public view returns (uint256) { 39 | return _balances[owner]; 40 | } 41 | 42 | /** 43 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 44 | * @param owner address The address which owns the funds. 45 | * @param spender address The address which will spend the funds. 46 | * @return A uint256 specifying the amount of tokens still available for the spender. 47 | */ 48 | function allowance( 49 | address owner, 50 | address spender 51 | ) 52 | public 53 | view 54 | returns (uint256) 55 | { 56 | return _allowed[owner][spender]; 57 | } 58 | 59 | /** 60 | * @dev Transfer token for a specified address 61 | * @param to The address to transfer to. 62 | * @param value The amount to be transferred. 63 | */ 64 | function transfer(address to, uint256 value) public returns (bool) { 65 | _transfer(msg.sender, to, value); 66 | return true; 67 | } 68 | 69 | /** 70 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 71 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 72 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 73 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 74 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 75 | * @param spender The address which will spend the funds. 76 | * @param value The amount of tokens to be spent. 77 | */ 78 | function approve(address spender, uint256 value) public returns (bool) { 79 | require(spender != address(0)); 80 | 81 | _allowed[msg.sender][spender] = value; 82 | emit Approval(msg.sender, spender, value); 83 | return true; 84 | } 85 | 86 | /** 87 | * @dev Transfer tokens from one address to another 88 | * @param from address The address which you want to send tokens from 89 | * @param to address The address which you want to transfer to 90 | * @param value uint256 the amount of tokens to be transferred 91 | */ 92 | function transferFrom( 93 | address from, 94 | address to, 95 | uint256 value 96 | ) 97 | public 98 | returns (bool) 99 | { 100 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 101 | _transfer(from, to, value); 102 | return true; 103 | } 104 | 105 | /** 106 | * @dev Increase the amount of tokens that an owner allowed to a spender. 107 | * approve should be called when allowed_[_spender] == 0. To increment 108 | * allowed value is better to use this function to avoid 2 calls (and wait until 109 | * the first transaction is mined) 110 | * From MonolithDAO Token.sol 111 | * @param spender The address which will spend the funds. 112 | * @param addedValue The amount of tokens to increase the allowance by. 113 | */ 114 | function increaseAllowance( 115 | address spender, 116 | uint256 addedValue 117 | ) 118 | public 119 | returns (bool) 120 | { 121 | require(spender != address(0)); 122 | 123 | _allowed[msg.sender][spender] = ( 124 | _allowed[msg.sender][spender].add(addedValue) 125 | ); 126 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 127 | return true; 128 | } 129 | 130 | /** 131 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 132 | * approve should be called when allowed_[_spender] == 0. To decrement 133 | * allowed value is better to use this function to avoid 2 calls (and wait until 134 | * the first transaction is mined) 135 | * From MonolithDAO Token.sol 136 | * @param spender The address which will spend the funds. 137 | * @param subtractedValue The amount of tokens to decrease the allowance by. 138 | */ 139 | function decreaseAllowance( 140 | address spender, 141 | uint256 subtractedValue 142 | ) 143 | public 144 | returns (bool) 145 | { 146 | require(spender != address(0)); 147 | 148 | _allowed[msg.sender][spender] = (_allowed[msg.sender][spender].sub(subtractedValue)); 149 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 150 | return true; 151 | } 152 | 153 | /** 154 | * @dev Transfer token for a specified addresses 155 | * @param from The address to transfer from. 156 | * @param to The address to transfer to. 157 | * @param value The amount to be transferred. 158 | */ 159 | function _transfer(address from, address to, uint256 value) internal { 160 | require(to != address(0)); 161 | 162 | _balances[from] = _balances[from].sub(value); 163 | _balances[to] = _balances[to].add(value); 164 | emit Transfer(from, to, value); 165 | } 166 | 167 | /** 168 | * @dev Internal function that mints an amount of the token and assigns it to 169 | * an account. This encapsulates the modification of balances such that the 170 | * proper events are emitted. 171 | * @param account The account that will receive the created tokens. 172 | * @param value The amount that will be created. 173 | */ 174 | function _mint(address account, uint256 value) internal { 175 | require(account != address(0)); 176 | 177 | _totalSupply = _totalSupply.add(value); 178 | _balances[account] = _balances[account].add(value); 179 | emit Transfer(address(0), account, value); 180 | } 181 | 182 | /** 183 | * @dev Internal function that burns an amount of the token of a given 184 | * account. 185 | * @param account The account whose tokens will be burnt. 186 | * @param value The amount that will be burnt. 187 | */ 188 | function _burn(address account, uint256 value) internal { 189 | require(account != address(0)); 190 | 191 | _totalSupply = _totalSupply.sub(value); 192 | _balances[account] = _balances[account].sub(value); 193 | emit Transfer(account, address(0), value); 194 | } 195 | 196 | /** 197 | * @dev Internal function that burns an amount of the token of a given 198 | * account, deducting from the sender's allowance for said account. Uses the 199 | * internal burn function. 200 | * @param account The account whose tokens will be burnt. 201 | * @param value The amount that will be burnt. 202 | */ 203 | function _burnFrom(address account, uint256 value) internal { 204 | // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, 205 | // this function needs to emit an event with the updated approval. 206 | _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( 207 | value); 208 | _burn(account, value); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /test/19_NewTokenPriceTest.test.js: -------------------------------------------------------------------------------- 1 | const MCR = artifacts.require('MCR'); 2 | const Pool1 = artifacts.require('Pool1Mock'); 3 | const Pool2 = artifacts.require('Pool2'); 4 | const PoolData = artifacts.require('PoolDataMock'); 5 | const DAI = artifacts.require('MockDAI'); 6 | const NXMToken = artifacts.require('NXMToken'); 7 | const MemberRoles = artifacts.require('MemberRoles'); 8 | const NXMaster = artifacts.require('NXMaster'); 9 | const TokenController = artifacts.require('TokenController'); 10 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 11 | 12 | const { assertRevert } = require('./utils/assertRevert'); 13 | const { advanceBlock } = require('./utils/advanceToBlock'); 14 | const { ether, toHex, toWei } = require('./utils/ethTools'); 15 | const getValue = require('./utils/getMCRPerThreshold.js').getValue; 16 | 17 | const CA_ETH = '0x45544800'; 18 | const CA_DAI = '0x44414900'; 19 | const UNLIMITED_ALLOWANCE = toWei(4500); 20 | 21 | let mcr; 22 | let pd; 23 | let tk; 24 | let p1; 25 | let balance_DAI; 26 | let balance_ETH; 27 | let nxms; 28 | let mr; 29 | let cad; 30 | let p2; 31 | let tc; 32 | let tf; 33 | const BN = web3.utils.BN; 34 | 35 | const BigNumber = web3.BigNumber; 36 | require('chai') 37 | .use(require('chai-bignumber')(BigNumber)) 38 | .should(); 39 | 40 | contract('MCR', function([owner, notOwner]) { 41 | before(async function() { 42 | await advanceBlock(); 43 | mcr = await MCR.deployed(); 44 | tk = await NXMToken.deployed(); 45 | p2 = await Pool2.deployed(); 46 | p1 = await Pool1.deployed(); 47 | pd = await PoolData.deployed(); 48 | cad = await DAI.deployed(); 49 | nxms = await NXMaster.deployed(); 50 | tf = await TokenFunctions.deployed(); 51 | mr = await MemberRoles.at(await nxms.getLatestAddress('0x4d52')); 52 | tc = await TokenController.at(await nxms.getLatestAddress(toHex('TC'))); 53 | }); 54 | 55 | describe('Token Price Calculation', function() { 56 | let tp_eth; 57 | let tp_dai; 58 | 59 | before(async function() { 60 | await mr.addMembersBeforeLaunch([], []); 61 | (await mr.launched()).should.be.equal(true); 62 | await mr.payJoiningFee(notOwner, { 63 | from: notOwner, 64 | value: toWei(0.002) 65 | }); 66 | await p1.upgradeInvestmentPool(DAI.address); 67 | await tf.upgradeCapitalPool(DAI.address); 68 | await p1.sendEther({ from: owner, value: toWei(5500) }); 69 | await mr.kycVerdict(notOwner, true); 70 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: owner }); 71 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: notOwner }); 72 | await mcr.addMCRData( 73 | 9000, 74 | toWei(100), 75 | toWei(90), 76 | ['0x455448', '0x444149'], 77 | [100, 15517], 78 | 20190219 79 | ); 80 | await tf.transferCurrencyAsset(toHex('ETH'), owner, toWei(5500 - 90)); 81 | }); 82 | it('19.1 single tranche 0.1ETH', async function() { 83 | let dataaa = await pd.getTokenPriceDetails(toHex('ETH')); 84 | let x = await tk.balanceOf(notOwner); 85 | let expectedNXM = await p1.getToken(toWei(0.1)); 86 | await p1.buyToken({ from: notOwner, value: toWei(0.1) }); 87 | let y = await tk.balanceOf(notOwner); 88 | console.log('single tranche 0.1ETH ==> ', parseFloat(y - x) / toWei(1)); 89 | ((y - x) / toWei(1)) 90 | .toFixed(2) 91 | .toString() 92 | .should.be.equal((5.13).toString()); 93 | }); 94 | it('19.2 multiple tranches 100ETH', async function() { 95 | let x = await tk.balanceOf(notOwner); 96 | await p1.buyToken({ 97 | from: notOwner, 98 | value: toWei(100) 99 | }); 100 | let y = await tk.balanceOf(notOwner); 101 | console.log( 102 | 'multiple tranches 100ETH ==> ', 103 | parseFloat(y - x) / toWei(1) 104 | ); 105 | ((y - x) / toWei(1)) 106 | .toFixed(2) 107 | .toString() 108 | .should.be.equal((5114.54).toString()); 109 | }); 110 | }); 111 | 112 | describe('Token Price Calculation2', function() { 113 | let tp_eth; 114 | let tp_dai; 115 | 116 | before(async function() { 117 | await p1.upgradeInvestmentPool(DAI.address); 118 | await tf.upgradeCapitalPool(DAI.address); 119 | await p1.sendEther({ from: owner, value: toWei(10) }); 120 | await mcr.addMCRData( 121 | 1000, 122 | toWei(100), 123 | toWei(10), 124 | ['0x455448', '0x444149'], 125 | [100, 14800], 126 | 20190219 127 | ); 128 | }); 129 | it('19.3 single tranches 15 times Buy tokens', async function() { 130 | let x; 131 | let y; 132 | let cost = toWei(10); 133 | for (let i = 0; cost < toWei(180); i++) { 134 | cost = cost / 1 + (i / 1) * toWei(10); 135 | console.log( 136 | 'token rate 1ETH = ', 137 | toWei(1) / parseFloat(await mcr.calculateTokenPrice(toHex('ETH'))) 138 | ); 139 | x = await tk.balanceOf(notOwner); 140 | await p1.buyToken({ from: notOwner, value: cost }); 141 | y = await tk.balanceOf(notOwner); 142 | console.log( 143 | 'tranche ', 144 | cost / toWei(1), 145 | ' ETH ==> ', 146 | parseFloat(y - x) / toWei(1) 147 | ); 148 | } 149 | }); 150 | it('19.4 tranches Buy more tokens', async function() { 151 | await p1.upgradeInvestmentPool(DAI.address); 152 | await tf.upgradeCapitalPool(DAI.address); 153 | await p1.sendEther({ from: owner, value: toWei(607.7406473491) }); 154 | await mcr.addMCRData( 155 | 202, 156 | toWei(30000), 157 | toWei(607.7406473491), 158 | ['0x455448', '0x444149'], 159 | [100, 15517], 160 | 20190219 161 | ); 162 | let x; 163 | let y; 164 | let cost = toWei(15); 165 | console.log( 166 | 'token rate 1ETH = ', 167 | toWei(1) / parseFloat(await mcr.calculateTokenPrice(toHex('ETH'))) 168 | ); 169 | x = await tk.balanceOf(notOwner); 170 | await p1.buyToken({ from: notOwner, value: cost }); 171 | y = await tk.balanceOf(notOwner); 172 | console.log( 173 | 'tranche ', 174 | cost / toWei(1), 175 | ' ETH ==> ', 176 | parseFloat(y - x) / toWei(1) 177 | ); 178 | 179 | cost = toWei(35); 180 | console.log( 181 | 'token rate 1ETH = ', 182 | toWei(1) / parseFloat(await mcr.calculateTokenPrice(toHex('ETH'))) 183 | ); 184 | x = await tk.balanceOf(notOwner); 185 | await p1.buyToken({ from: notOwner, value: cost }); 186 | y = await tk.balanceOf(notOwner); 187 | console.log( 188 | 'tranche ', 189 | cost / toWei(1), 190 | ' ETH ==> ', 191 | parseFloat(y - x) / toWei(1) 192 | ); 193 | 194 | cost = toWei(600); 195 | console.log( 196 | 'token rate 1ETH = ', 197 | toWei(1) / parseFloat(await mcr.calculateTokenPrice(toHex('ETH'))) 198 | ); 199 | x = await tk.balanceOf(notOwner); 200 | await p1.buyToken({ from: notOwner, value: cost }); 201 | y = await tk.balanceOf(notOwner); 202 | console.log( 203 | 'tranche ', 204 | cost / toWei(1), 205 | ' ETH ==> ', 206 | parseFloat(y - x) / toWei(1) 207 | ); 208 | 209 | cost = toWei(5000); 210 | console.log( 211 | 'token rate 1ETH = ', 212 | toWei(1) / parseFloat(await mcr.calculateTokenPrice(toHex('ETH'))) 213 | ); 214 | }); 215 | it('19.5 Should revert while buying or 0 ETH', async function() { 216 | await assertRevert(p1.buyToken({ value: 0 })); 217 | }); 218 | }); 219 | 220 | describe('Token Selling', function() { 221 | it('19.6 Max sellable token will 0 if mcr percentage is less than 100', async function() { 222 | parseFloat(await mcr.getMaxSellTokens()).should.be.equal(0); 223 | }); 224 | it('19.7 sell more than 1000 NXMs', async function() { 225 | await p1.sendEther({ from: owner, value: toWei(11000) }); 226 | let poolBal = await mcr.calVtpAndMCRtp(); 227 | await mcr.addMCRData( 228 | 20000, 229 | toWei(100), 230 | poolBal[0], 231 | ['0x455448', '0x444149'], 232 | [100, 15517], 233 | 20190219 234 | ); 235 | await tf.transferCurrencyAsset(toHex('ETH'), owner, toWei(11000)); 236 | let initialBalNXM = await tk.balanceOf(owner); 237 | await p1.sellNXMTokens(toWei(1500)); 238 | let finalBalNXM = await tk.balanceOf(owner); 239 | 240 | (finalBalNXM / 1).should.be.equal(initialBalNXM / 1 - toWei(1500)); 241 | }); 242 | it('19.6 Max sellable token will 0 if pool balance is less than 1.5 times of basemin', async function() { 243 | await tf.upgradeCapitalPool(DAI.address); 244 | parseFloat(await mcr.getMaxSellTokens()).should.be.equal(0); 245 | }); 246 | }); 247 | }); 248 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/somish/NexusMutual.svg?branch=master)](https://travis-ci.org/somish/NexusMutual?branch=master) 2 | 3 |

NEXUS MUTUAL

4 |

Nexus Mutual uses blockchain technology to bring the mutual ethos back to insurance by creating aligned incentives through smart contract code on the Ethereum blockchain.

5 |

Description

6 |

Nexus Mutual is built on the Ethereum blockchain and uses a modular system for grouping of Ethereum smart contracts, allowing logical components of the system to be upgraded without effecting the other components. Following are the key modules of Nexus Mutual.

7 |

Token Module

8 |

Token contracts maintain details of NXM Members and the NXM Tokens held by each of them. A member of the mutual can buy/sell tokens anytime. NXM tokens can be used to purchase a cover, submit a claim, underwrite smart contracts, assess a claim or transfer tokens to other addresses.

9 |

Core Contracts

10 | 16 |
17 |

Note: The smart contracts of this module had to be split in multiple smart contracts to cater to the Ethereum Gas limits. The above mentioned contracts need to be seen in conjunction

18 |
19 |
Some important functions
20 | 26 | 27 |

Quotation Module

28 |

Quotation contracts contain all logic associated with creating and expiring covers. Smart contract cover is the first insurance product supported by the mutual. A member can generate a quotation offchain , and fund the same via NXM tokens / currency assets(currently ETH and DAI). This creates a cover on-chain. Quotation contracts interact with Token Contracts to lock NXM tokens against a cover which are then used at the time of claim submission.

29 |

Core Contracts

30 | 34 |
Some important functions
35 | 39 |

Claim Module

40 |

Claim contracts manages the entire claim lifecycle starting from submitting a claim against a cover note to taking part in claims assessment to closing a claim.

41 |

Core Contracts

42 | 46 |
Some important functions
47 | 52 | 53 |

Claim Reward Module

54 |

Claims Reward Contract contains the methods for rewarding or punishing the Claim assessors/Members based on the vote cast and the final verdict. All rewards in Nexus Mutual, commission to stakers, rewards to Cliams assessors/members for claims assessment, participants in governance are given via this module.

55 |

Core Contract

56 | 59 |
Some important functions
60 | 65 | 66 |

Pool Module

67 |

Pool contracts contain all logic associated with calling External oracles through Oraclize and processing the results retrieved from the same. The module also encompasses on-chain investment asset management using 0x-protocol.

68 |

Core Contract

69 | 74 |
75 |

Note: The smart contracts of this module had to be split in multiple smart contracts to cater to the Ethereum Gas limits. The above mentioned contracts need to be seen in conjunction

76 |
77 |
Some important functions
78 | 87 |

MCR Module

88 |

MCR contracts contain functions for recording the Minimum Capital Requirement (MCR) of the system, each day, thus determining the NXM token price.

89 |

Core Contract

90 | 93 |
Some important function
94 | 98 |

Governance Module:

99 |

Governance contracts contain the logic for creating, editing, categorizing and voting on proposals followed by action implementation, code upgradability. These governance contracts are generated in line with the GovBlocks Protocol.

100 |

Core Contract

101 | 106 |
Some important functions
107 | 118 | 119 | ## Getting Started 120 | 121 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 122 | 123 | 124 | ### Requirements 125 | ``` 126 | Node >= 7.6 127 | ``` 128 | 129 | 130 | ### Installing 131 | Firstly, you need to clone this repo. You can do so by downloading the repo as a zip and unpacking or using the following git command 132 | 133 | ``` 134 | git clone https://github.com/somish/NexusMutual.git 135 | ``` 136 | 137 | Now, It's time to install the dependencies. Enter the NexusMutual directory and use 138 | 139 | ``` 140 | npm install 141 | ``` 142 | Make sure you delete folder `bitcore-lib` from node_modules inside modules `eth-lightwallet` and `bitcore-mnemonic` 143 | 144 | We need to compile the contracts before deploying. We'll be using truffle for that (You can use Remix or solc directly). 145 | ``` 146 | truffle compile 147 | ``` 148 | Now, You should start a private network on port 7545 using Ganache or something similar. To run the private network -
149 | On Windows, Execute file nxdev.bat present in NexusMutual directory
150 | On Linux or Mac OS Systems, run the nxdev.sh file while in NexusMutual directory 151 | ``` 152 | ./nxdev.sh 153 | ``` 154 | 155 | Then, you can deploy your Nexus Mutual dApp using the migrate script. 156 | ``` 157 | truffle deploy 158 | ``` 159 | 160 | If you want, you can run the test cases using 161 | ``` 162 | truffle test 163 | ``` 164 | -------------------------------------------------------------------------------- /test/13_MemberRoles.test.js: -------------------------------------------------------------------------------- 1 | const MemberRoles = artifacts.require('MemberRoles'); 2 | const Governance = artifacts.require('Governance'); 3 | const TokenController = artifacts.require('TokenController'); 4 | const ProposalCategory = artifacts.require('ProposalCategory'); 5 | const NXMaster = artifacts.require('NXMaster'); 6 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 7 | const NXMToken = artifacts.require('NXMToken'); 8 | const assertRevert = require('./utils/assertRevert').assertRevert; 9 | const encode = require('./utils/encoder.js').encode; 10 | const { toHex, toWei } = require('./utils/ethTools'); 11 | const QuotationDataMock = artifacts.require('QuotationDataMock'); 12 | 13 | let mr; 14 | let gv; 15 | let pc; 16 | let gbt; 17 | let address; 18 | let gvAddress; 19 | let p1; 20 | let mrLength; 21 | let p2; 22 | let tk; 23 | let mrLength1; 24 | let qd; 25 | 26 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 27 | const fee = toWei(0.002); 28 | 29 | contract('MemberRoles', function([ 30 | owner, 31 | member, 32 | other, 33 | user1, 34 | user2, 35 | user3, 36 | member2 37 | ]) { 38 | before(async function() { 39 | nxms = await NXMaster.deployed(); 40 | address = await nxms.getLatestAddress(toHex('MR')); 41 | mr = await MemberRoles.at(address); 42 | address = await nxms.getLatestAddress(toHex('GV')); 43 | gv = await Governance.at(address); 44 | tf = await TokenFunctions.deployed(); 45 | tk = await NXMToken.deployed(); 46 | qd = await QuotationDataMock.deployed(); 47 | }); 48 | it('13.1 Should not be able to pay joining fee using ZERO_ADDRESS', async function() { 49 | await assertRevert( 50 | mr.payJoiningFee(ZERO_ADDRESS, { from: owner, value: fee }) 51 | ); 52 | }); 53 | it('13.2 Should not allow a member(who has refund eligible) to pay joining fee', async function() { 54 | mr.payJoiningFee(member2, { from: member2, value: fee }); 55 | await assertRevert( 56 | mr.payJoiningFee(member2, { from: member2, value: fee }) 57 | ); 58 | await mr.kycVerdict(member2, false); 59 | }); 60 | it('13.3 Should not be able to pay joining fee for already a member', async function() { 61 | await assertRevert(mr.payJoiningFee(owner, { value: '2000000000000000' })); 62 | }); 63 | it('13.4 Should not be able to trigger kyc using ZERO_ADDRESS', async function() { 64 | await assertRevert(mr.kycVerdict(ZERO_ADDRESS, true)); 65 | }); 66 | it('13.5 Should not be able to trigger kyc for already a member', async function() { 67 | await assertRevert(mr.kycVerdict(owner, true)); 68 | }); 69 | it('13.6 Should not allow a member(who has not refund eligible) to trigger kyc', async function() { 70 | await assertRevert(mr.kycVerdict(member2, true)); 71 | }); 72 | it('13.7 Kyc declined, refund will be done', async function() { 73 | await mr.payJoiningFee(member2, { from: member2, value: fee }); 74 | await mr.kycVerdict(member2, false); 75 | }); 76 | it('13.8 Should not be able to initiate member roles twice', async function() { 77 | let nxmToken = await nxms.dAppToken(); 78 | await assertRevert(mr.memberRolesInitiate(owner, owner)); 79 | }); 80 | 81 | it('13.9 Should not allow unauthorized to change master address', async function() { 82 | await assertRevert(mr.changeMasterAddress(nxms.address, { from: other })); 83 | }); 84 | 85 | it('13.10 Should have added initial member roles', async function() { 86 | const ab = await mr.totalRoles.call(); 87 | assert.equal(ab.toNumber(), 4, 'Initial member roles not created'); 88 | }); 89 | 90 | it('13.11 Should have assigned Owner roles to owner', async function() { 91 | assert.equal( 92 | await mr.checkRole(owner, 3), 93 | true, 94 | 'Owner not added to role Owner' 95 | ); 96 | }); 97 | 98 | it('13.12 Should not be able to update max AB count', async function() { 99 | await assertRevert(mr.changeMaxABCount(1, { from: other })); 100 | }); 101 | 102 | it('13.13 Should not add initial AB members more than defined max AB count', async function() { 103 | let memberArray = [member, other, user1, user2, user3, member2]; 104 | await assertRevert(mr.addInitialABMembers(memberArray)); 105 | }); 106 | 107 | it('13.14 should have added owner to AB', async function() { 108 | const roles = await mr.roles(owner); 109 | assert.equal(await mr.checkRole(owner, 1), true, 'Owner not added to AB'); 110 | assert.equal( 111 | await mr.checkRole(member, 1), 112 | false, 113 | 'user added to AB incorrectly' 114 | ); 115 | assert.equal(roles[0].toNumber(), 1, 'Owner added to AB'); 116 | }); 117 | 118 | it('13.15 should add new role', async function() { 119 | let actionHash = encode( 120 | 'addRole(bytes32,string,address)', 121 | '0x41647669736f727920426f617265000000000000000000000000000000000000', 122 | 'New member role', 123 | owner 124 | ); 125 | p1 = await gv.getProposalLength(); 126 | mrLength = await mr.totalRoles(); 127 | await gv.createProposalwithSolution( 128 | 'Add new member', 129 | 'Add new member', 130 | 'Addnewmember', 131 | 1, 132 | 'Add new member', 133 | actionHash 134 | ); 135 | p2 = await gv.getProposalLength(); 136 | await gv.submitVote(p1.toNumber(), 1); 137 | await gv.closeProposal(p1.toNumber()); 138 | mrLength1 = await mr.totalRoles(); 139 | assert.isAbove(mrLength1.toNumber(), mrLength.toNumber(), 'Role not added'); 140 | }); 141 | 142 | it('13.16 should add a member to a role', async function() { 143 | var transaction = await mr.updateRole(member, 4, true); 144 | await assertRevert(mr.updateRole(member, 2, true)); 145 | await assertRevert(mr.updateRole(member, 4, true)); 146 | await assertRevert(mr.updateRole(member, 2, false, { from: other })); 147 | assert.equal(await mr.checkRole(member, 4), true, 'user not added to AB'); 148 | }); 149 | 150 | it('13.17 Should fetch all address by role id', async function() { 151 | const g3 = await mr.members(1); 152 | assert.equal(g3[1][0], owner); 153 | }); 154 | 155 | it('13.18 Should fetch total number of members by role id', async function() { 156 | const g4 = await mr.numberOfMembers(4); 157 | assert.equal(g4.toNumber(), 1); 158 | }); 159 | 160 | it('13.19 Should fetch member count of all roles', async function() { 161 | const g6 = await mr.getMemberLengthForAllRoles(); 162 | assert.equal(g6.length, 5); 163 | assert.equal(g6[0].toNumber(), 0); 164 | assert.equal(g6[1].toNumber(), 1); 165 | assert.equal(g6[3].toNumber(), 1); 166 | assert.equal(g6[4].toNumber(), 1); 167 | }); 168 | 169 | it('13.20 Should follow the upgradable interface', async function() { 170 | await mr.changeDependentContractAddress(); // just for interface, they do nothing 171 | }); 172 | 173 | it('13.21 Should not list invalid member as valid', async function() { 174 | var a = await mr.checkRole(member, 1); 175 | await mr.updateRole(member, 4, false); 176 | assert.equal( 177 | await mr.checkRole(member, 4), 178 | false, 179 | 'user incorrectly added to AB' 180 | ); 181 | await mr.updateRole(member, 4, true); 182 | let members = await mr.members(4); 183 | assert.equal(members[1].length, 1); 184 | assert.equal(await mr.checkRole(member, 4), true, 'user not added to AB'); 185 | }); 186 | 187 | it('13.22 Should be able to remove member from a role', async function() { 188 | await mr.updateRole(member, 4, false); 189 | assert.equal( 190 | await mr.checkRole(member, 4), 191 | false, 192 | 'user not removed from AB' 193 | ); 194 | const g3 = await mr.members(4); 195 | await assertRevert(mr.updateRole(member, 4, false)); 196 | }); 197 | 198 | it('13.23 Should not allow unauthorized people to update member roles', async function() { 199 | await mr.changeAuthorized(4, owner); 200 | await assertRevert(mr.changeAuthorized(4, owner, { from: other })); 201 | await assertRevert(mr.changeAuthorized(1, owner)); 202 | await assertRevert(mr.updateRole(member, 4, true, { from: other })); 203 | }); 204 | 205 | it('13.24 Should change authorizedAddress when rquested by authorizedAddress', async function() { 206 | await mr.changeAuthorized(4, member); 207 | assert.equal( 208 | await mr.authorized(4), 209 | member, 210 | 'Authorized address not changed' 211 | ); 212 | }); 213 | 214 | it('13.25 Should get proper Roles', async () => { 215 | const mrs = await mr.roles(owner); 216 | assert.equal(await mr.checkRole(owner, 1), true, 'Owner not added to AB'); 217 | assert.equal(mrs[0].toNumber(), 1); 218 | const mrs2 = await mr.roles(other); 219 | }); 220 | 221 | it('13.26 Should allow anyone to be of member role 0', async () => { 222 | assert.equal(await mr.checkRole(owner, 0), true); 223 | }); 224 | 225 | it('13.27 Should check role if user buys membership', async () => { 226 | await mr.payJoiningFee(member, { value: '2000000000000000', from: member }); 227 | await mr.kycVerdict(member, true); 228 | assert.equal(await mr.checkRole(member, 2), true); 229 | assert.equal(await mr.checkRole(other, 2), false); 230 | }); 231 | 232 | it('13.28 Should not able to add members before launch by non-owner', async () => { 233 | await assertRevert( 234 | mr.addMembersBeforeLaunch( 235 | [user1, user2, user3], 236 | [toWei(100), toWei(200), toWei(300)], 237 | { from: member } 238 | ) 239 | ); 240 | }); 241 | 242 | it('13.29 Should not able to add members before launch if one of user is already member', async () => { 243 | await assertRevert( 244 | mr.addMembersBeforeLaunch( 245 | [owner, user2, user3], 246 | [toWei(100), toWei(200), toWei(300)], 247 | { from: owner } 248 | ) 249 | ); 250 | }); 251 | 252 | it('13.30 Should able to add members before launch', async () => { 253 | await mr.addMembersBeforeLaunch( 254 | [user1, user2, user3], 255 | [toWei(100), toWei(200), toWei(300)], 256 | { from: owner } 257 | ); 258 | assert.equal(await mr.checkRole(user1, 2), true); 259 | assert.equal(await mr.checkRole(user2, 2), true); 260 | assert.equal(await mr.checkRole(user3, 2), true); 261 | assert.equal(await tk.whiteListed(user1), true); 262 | assert.equal(await tk.whiteListed(user2), true); 263 | assert.equal(await tk.whiteListed(user3), true); 264 | assert.equal(await tk.balanceOf(user1), toWei(100)); 265 | assert.equal(await tk.balanceOf(user2), toWei(200)); 266 | assert.equal(await tk.balanceOf(user3), toWei(300)); 267 | assert.equal(await mr.launched(), true); 268 | }); 269 | 270 | it('13.31 Should not able to add members before launch more than once', async () => { 271 | await assertRevert( 272 | mr.addMembersBeforeLaunch( 273 | [user1, user2, user3], 274 | [toWei(100), toWei(200), toWei(300)], 275 | { from: owner } 276 | ) 277 | ); 278 | }); 279 | it('13.32 Should not be able to swap owner manually', async () => { 280 | await assertRevert(mr.swapOwner(member)); 281 | }); 282 | it('13.33 Should not allow unauthorized address to set kyc status', async function() { 283 | await assertRevert(mr.kycVerdict(member2, true, { from: member })); 284 | }); 285 | }); 286 | -------------------------------------------------------------------------------- /contracts/NXMToken.sol: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 NexusMutual.io 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see http://www.gnu.org/licenses/ */ 15 | 16 | pragma solidity 0.5.7; 17 | 18 | import "./external/openzeppelin-solidity/token/ERC20/IERC20.sol"; 19 | import "./external/openzeppelin-solidity/math/SafeMath.sol"; 20 | 21 | 22 | contract NXMToken is IERC20 { 23 | using SafeMath for uint256; 24 | 25 | event WhiteListed(address indexed member); 26 | 27 | event BlackListed(address indexed member); 28 | 29 | mapping (address => uint256) private _balances; 30 | 31 | mapping (address => mapping (address => uint256)) private _allowed; 32 | 33 | mapping (address => bool) public whiteListed; 34 | 35 | mapping(address => uint) public isLockedForMV; 36 | 37 | uint256 private _totalSupply; 38 | 39 | string public name = "NXM"; 40 | string public symbol = "NXM"; 41 | uint8 public decimals = 18; 42 | address public operator; 43 | 44 | modifier canTransfer(address _to) { 45 | require(whiteListed[_to]); 46 | _; 47 | } 48 | 49 | modifier onlyOperator() { 50 | if (operator != address(0)) 51 | require(msg.sender == operator); 52 | _; 53 | } 54 | 55 | constructor(address _founderAddress, uint _initialSupply) public { 56 | _mint(_founderAddress, _initialSupply); 57 | } 58 | 59 | /** 60 | * @dev Total number of tokens in existence 61 | */ 62 | function totalSupply() public view returns (uint256) { 63 | return _totalSupply; 64 | } 65 | 66 | /** 67 | * @dev Gets the balance of the specified address. 68 | * @param owner The address to query the balance of. 69 | * @return An uint256 representing the amount owned by the passed address. 70 | */ 71 | function balanceOf(address owner) public view returns (uint256) { 72 | return _balances[owner]; 73 | } 74 | 75 | /** 76 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 77 | * @param owner address The address which owns the funds. 78 | * @param spender address The address which will spend the funds. 79 | * @return A uint256 specifying the amount of tokens still available for the spender. 80 | */ 81 | function allowance( 82 | address owner, 83 | address spender 84 | ) 85 | public 86 | view 87 | returns (uint256) 88 | { 89 | return _allowed[owner][spender]; 90 | } 91 | 92 | /** 93 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 94 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 95 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 96 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 97 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 98 | * @param spender The address which will spend the funds. 99 | * @param value The amount of tokens to be spent. 100 | */ 101 | function approve(address spender, uint256 value) public returns (bool) { 102 | require(spender != address(0)); 103 | 104 | _allowed[msg.sender][spender] = value; 105 | emit Approval(msg.sender, spender, value); 106 | return true; 107 | } 108 | 109 | /** 110 | * @dev Increase the amount of tokens that an owner allowed to a spender. 111 | * approve should be called when allowed_[_spender] == 0. To increment 112 | * allowed value is better to use this function to avoid 2 calls (and wait until 113 | * the first transaction is mined) 114 | * From MonolithDAO Token.sol 115 | * @param spender The address which will spend the funds. 116 | * @param addedValue The amount of tokens to increase the allowance by. 117 | */ 118 | function increaseAllowance( 119 | address spender, 120 | uint256 addedValue 121 | ) 122 | public 123 | returns (bool) 124 | { 125 | require(spender != address(0)); 126 | 127 | _allowed[msg.sender][spender] = ( 128 | _allowed[msg.sender][spender].add(addedValue)); 129 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 130 | return true; 131 | } 132 | 133 | /** 134 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 135 | * approve should be called when allowed_[_spender] == 0. To decrement 136 | * allowed value is better to use this function to avoid 2 calls (and wait until 137 | * the first transaction is mined) 138 | * From MonolithDAO Token.sol 139 | * @param spender The address which will spend the funds. 140 | * @param subtractedValue The amount of tokens to decrease the allowance by. 141 | */ 142 | function decreaseAllowance( 143 | address spender, 144 | uint256 subtractedValue 145 | ) 146 | public 147 | returns (bool) 148 | { 149 | require(spender != address(0)); 150 | 151 | _allowed[msg.sender][spender] = ( 152 | _allowed[msg.sender][spender].sub(subtractedValue)); 153 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 154 | return true; 155 | } 156 | 157 | /** 158 | * @dev Adds a user to whitelist 159 | * @param _member address to add to whitelist 160 | */ 161 | function addToWhiteList(address _member) public onlyOperator returns (bool) { 162 | whiteListed[_member] = true; 163 | emit WhiteListed(_member); 164 | return true; 165 | } 166 | 167 | /** 168 | * @dev removes a user from whitelist 169 | * @param _member address to remove from whitelist 170 | */ 171 | function removeFromWhiteList(address _member) public onlyOperator returns (bool) { 172 | whiteListed[_member] = false; 173 | emit BlackListed(_member); 174 | return true; 175 | } 176 | 177 | /** 178 | * @dev change operator address 179 | * @param _newOperator address of new operator 180 | */ 181 | function changeOperator(address _newOperator) public onlyOperator returns (bool) { 182 | operator = _newOperator; 183 | return true; 184 | } 185 | 186 | /** 187 | * @dev burns an amount of the tokens of the message sender 188 | * account. 189 | * @param amount The amount that will be burnt. 190 | */ 191 | function burn(uint256 amount) public returns (bool) { 192 | _burn(msg.sender, amount); 193 | return true; 194 | } 195 | 196 | /** 197 | * @dev Burns a specific amount of tokens from the target address and decrements allowance 198 | * @param from address The address which you want to send tokens from 199 | * @param value uint256 The amount of token to be burned 200 | */ 201 | function burnFrom(address from, uint256 value) public returns (bool) { 202 | _burnFrom(from, value); 203 | return true; 204 | } 205 | 206 | /** 207 | * @dev function that mints an amount of the token and assigns it to 208 | * an account. 209 | * @param account The account that will receive the created tokens. 210 | * @param amount The amount that will be created. 211 | */ 212 | function mint(address account, uint256 amount) public onlyOperator { 213 | _mint(account, amount); 214 | } 215 | 216 | /** 217 | * @dev Transfer token for a specified address 218 | * @param to The address to transfer to. 219 | * @param value The amount to be transferred. 220 | */ 221 | function transfer(address to, uint256 value) public canTransfer(to) returns (bool) { 222 | 223 | require(isLockedForMV[msg.sender] < now); // if not voted under governance 224 | require(value <= _balances[msg.sender]); 225 | _transfer(to, value); 226 | return true; 227 | } 228 | 229 | /** 230 | * @dev Transfer tokens to the operator from the specified address 231 | * @param from The address to transfer from. 232 | * @param value The amount to be transferred. 233 | */ 234 | function operatorTransfer(address from, uint256 value) public onlyOperator returns (bool) { 235 | require(value <= _balances[from]); 236 | _transferFrom(from, operator, value); 237 | return true; 238 | } 239 | 240 | /** 241 | * @dev Transfer tokens from one address to another 242 | * @param from address The address which you want to send tokens from 243 | * @param to address The address which you want to transfer to 244 | * @param value uint256 the amount of tokens to be transferred 245 | */ 246 | function transferFrom( 247 | address from, 248 | address to, 249 | uint256 value 250 | ) 251 | public 252 | canTransfer(to) 253 | returns (bool) 254 | { 255 | require(isLockedForMV[from] < now); // if not voted under governance 256 | require(value <= _balances[from]); 257 | require(value <= _allowed[from][msg.sender]); 258 | _transferFrom(from, to, value); 259 | return true; 260 | } 261 | 262 | /** 263 | * @dev Lock the user's tokens 264 | * @param _of user's address. 265 | */ 266 | function lockForMemberVote(address _of, uint _days) public onlyOperator { 267 | if (_days.add(now) > isLockedForMV[_of]) 268 | isLockedForMV[_of] = _days.add(now); 269 | } 270 | 271 | /** 272 | * @dev Transfer token for a specified address 273 | * @param to The address to transfer to. 274 | * @param value The amount to be transferred. 275 | */ 276 | function _transfer(address to, uint256 value) internal { 277 | _balances[msg.sender] = _balances[msg.sender].sub(value); 278 | _balances[to] = _balances[to].add(value); 279 | emit Transfer(msg.sender, to, value); 280 | } 281 | 282 | /** 283 | * @dev Transfer tokens from one address to another 284 | * @param from address The address which you want to send tokens from 285 | * @param to address The address which you want to transfer to 286 | * @param value uint256 the amount of tokens to be transferred 287 | */ 288 | function _transferFrom( 289 | address from, 290 | address to, 291 | uint256 value 292 | ) 293 | internal 294 | { 295 | _balances[from] = _balances[from].sub(value); 296 | _balances[to] = _balances[to].add(value); 297 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 298 | emit Transfer(from, to, value); 299 | } 300 | 301 | /** 302 | * @dev Internal function that mints an amount of the token and assigns it to 303 | * an account. This encapsulates the modification of balances such that the 304 | * proper events are emitted. 305 | * @param account The account that will receive the created tokens. 306 | * @param amount The amount that will be created. 307 | */ 308 | function _mint(address account, uint256 amount) internal { 309 | require(account != address(0)); 310 | _totalSupply = _totalSupply.add(amount); 311 | _balances[account] = _balances[account].add(amount); 312 | emit Transfer(address(0), account, amount); 313 | } 314 | 315 | /** 316 | * @dev Internal function that burns an amount of the token of a given 317 | * account. 318 | * @param account The account whose tokens will be burnt. 319 | * @param amount The amount that will be burnt. 320 | */ 321 | function _burn(address account, uint256 amount) internal { 322 | require(amount <= _balances[account]); 323 | 324 | _totalSupply = _totalSupply.sub(amount); 325 | _balances[account] = _balances[account].sub(amount); 326 | emit Transfer(account, address(0), amount); 327 | } 328 | 329 | /** 330 | * @dev Internal function that burns an amount of the token of a given 331 | * account, deducting from the sender's allowance for said account. Uses the 332 | * internal burn function. 333 | * @param account The account whose tokens will be burnt. 334 | * @param value The amount that will be burnt. 335 | */ 336 | function _burnFrom(address account, uint256 value) internal { 337 | require(value <= _allowed[account][msg.sender]); 338 | 339 | // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, 340 | // this function needs to emit an event with the updated approval. 341 | _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( 342 | value); 343 | _burn(account, value); 344 | } 345 | } -------------------------------------------------------------------------------- /test/04_Locking.test.js: -------------------------------------------------------------------------------- 1 | const NXMToken = artifacts.require('NXMToken'); 2 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 3 | const TokenController = artifacts.require('TokenController'); 4 | const TokenData = artifacts.require('TokenDataMock'); 5 | const Pool1 = artifacts.require('Pool1Mock'); 6 | const MemberRoles = artifacts.require('MemberRoles'); 7 | const NXMaster = artifacts.require('NXMaster'); 8 | 9 | const { assertRevert } = require('./utils/assertRevert'); 10 | const { advanceBlock } = require('./utils/advanceToBlock'); 11 | const { ether, toHex, toWei } = require('./utils/ethTools'); 12 | const expectEvent = require('./utils/expectEvent'); 13 | const { increaseTimeTo, duration } = require('./utils/increaseTime'); 14 | const { latestTime } = require('./utils/latestTime'); 15 | 16 | // const ETH = '0x455448'; 17 | const CLA = '0x434c41'; 18 | const CLA2 = '0x434c412'; 19 | let tk; 20 | let tf; 21 | let tc; 22 | let td; 23 | let P1; 24 | let mr; 25 | let nxms; 26 | const BN = web3.utils.BN; 27 | 28 | const BigNumber = web3.BigNumber; 29 | require('chai') 30 | .use(require('chai-bignumber')(BigNumber)) 31 | .should(); 32 | 33 | contract('NXMToken:Locking', function([owner, member1, member2, member3]) { 34 | const fee = ether(0.002); 35 | const tokens = ether(200); 36 | const UNLIMITED_ALLOWANCE = new BN((2).toString()) 37 | .pow(new BN((256).toString())) 38 | .sub(new BN((1).toString())); 39 | before(async function() { 40 | await advanceBlock(); 41 | P1 = await Pool1.deployed(); 42 | tk = await NXMToken.deployed(); 43 | tf = await TokenFunctions.deployed(); 44 | td = await TokenData.deployed(); 45 | nxms = await NXMaster.deployed(); 46 | tc = await TokenController.at(await nxms.getLatestAddress(toHex('TC'))); 47 | mr = await MemberRoles.at(await nxms.getLatestAddress('0x4d52')); 48 | await mr.addMembersBeforeLaunch([], []); 49 | (await mr.launched()).should.be.equal(true); 50 | await mr.payJoiningFee(member1, { from: member1, value: fee }); 51 | await mr.kycVerdict(member1, true); 52 | await mr.payJoiningFee(member2, { from: member2, value: fee }); 53 | await mr.kycVerdict(member2, true); 54 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member1 }); 55 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member2 }); 56 | await tk.transfer(member1, tokens); 57 | await tk.transfer(member2, tokens); 58 | }); 59 | describe('Lock Tokens', function() { 60 | const lockTokens = ether(1); 61 | const validity = duration.days(30); 62 | const extendLockTokens = ether(2); 63 | describe('Lock Tokens under Claim Assesment', function() { 64 | let initialLockedTokens; 65 | let initialTokenBalance; 66 | //let eventlogs; 67 | it('4.1 should have zero initialLockedTokens', async function() { 68 | initialLockedTokens = await tc.tokensLocked(member1, CLA); 69 | initialTokenBalance = await tk.balanceOf(member1); 70 | initialLockedTokens.toString().should.be.equal((0).toString()); 71 | }); 72 | it('4.2 should not be able to lock tokens more than balance', async function() { 73 | await assertRevert( 74 | tc.lock( 75 | CLA, 76 | new BN(initialTokenBalance.toString()).add( 77 | new BN(toWei(1).toString()) 78 | ), 79 | validity, 80 | { 81 | from: member1 82 | } 83 | ) 84 | ); 85 | }); 86 | it('4.3 should not be able to lock 0 tokens', async function() { 87 | await assertRevert( 88 | tc.lock(CLA, 0, validity, { 89 | from: member1 90 | }) 91 | ); 92 | }); 93 | it('4.4 should be able to lock tokens', async function() { 94 | await tc.lock(CLA, lockTokens, validity, { 95 | from: member1 96 | }); 97 | eventlogs = this.logs; 98 | const lockedTokens = new BN(initialLockedTokens.toString()).add( 99 | new BN(lockTokens.toString()) 100 | ); 101 | const newTokenBalance = new BN(initialTokenBalance.toString()).sub( 102 | new BN(lockTokens.toString()) 103 | ); 104 | newTokenBalance 105 | .toString() 106 | .should.be.equal((await tk.balanceOf(member1)).toString()); 107 | lockedTokens 108 | .toString() 109 | .should.be.equal((await tc.tokensLocked(member1, CLA)).toString()); 110 | }); 111 | it('4.5 emits Lock event', async function() { 112 | const lockTokens = ether(2); 113 | const { logs } = await tc.lock(CLA, lockTokens, validity, { 114 | from: member2 115 | }); 116 | const event = expectEvent.inLogs(logs, 'Locked', { 117 | _of: member2 118 | }); 119 | event.args._amount.toString().should.be.equal(lockTokens.toString()); 120 | event.args._validity 121 | .toString() 122 | .should.be.equal(((await latestTime()) + validity).toString()); 123 | }); 124 | it('4.6 should not have locked tokens for other reason', async function() { 125 | (await tc.tokensLocked(member1, toHex('YOLO'))) 126 | .toString() 127 | .should.be.equal((0).toString()); 128 | }); 129 | }); 130 | describe('Lock Tokens under CA more than once', function() { 131 | it('4.7 reverts', async function() { 132 | await assertRevert( 133 | tc.lock(CLA, 5000, await latestTime(), { from: member1 }) 134 | ); 135 | }); 136 | }); 137 | //end of first describe 138 | describe('Extend validity of Locked Tokens', function() { 139 | const extendValidity = duration.days(2); 140 | const extendValidity2 = duration.days(5); 141 | let initialLockedTokens; 142 | describe('Before validity expires', function() { 143 | it('4.8 should have some locked tokens', async function() { 144 | initialLockedTokens = await tc.tokensLocked(member1, CLA); 145 | initialLockedTokens.toString().should.be.not.equal((0).toString()); 146 | }); 147 | it('4.9 should be able to extend locked tokens validity', async function() { 148 | const initialValidity = await tc.getLockedTokensValidity( 149 | member1, 150 | CLA 151 | ); 152 | await tc.extendLock(CLA, extendValidity, { from: member1 }); 153 | (await tc.getLockedTokensValidity(member1, CLA)) 154 | .toString() 155 | .should.be.equal( 156 | new BN(initialValidity.toString()) 157 | .add(new BN(extendValidity.toString())) 158 | .toString() 159 | ); 160 | }); 161 | it('4.10 should not be able to extend lock if already unlocked all', async function() { 162 | await assertRevert( 163 | tc.extendLock(CLA2, extendValidity, { from: member1 }) 164 | ); 165 | }); 166 | }); 167 | describe('After validity expires if tokens not claimed', function() { 168 | beforeEach(async function() { 169 | const validity = await tc.getLockedTokensValidity(member1, CLA); 170 | await increaseTimeTo( 171 | new BN(validity.toString).add(new BN((2).toString())) 172 | ); 173 | }); 174 | it('4.11 increase validity', async function() { 175 | await tc.extendLock(CLA, extendValidity, { from: member1 }); 176 | }); 177 | }); 178 | }); 179 | //end of second describe 180 | 181 | describe('Increase amount of locked Tokens', function() { 182 | describe('Before validity expires', function() { 183 | before(async function() { 184 | await mr.payJoiningFee(member3, { from: member3, value: fee }); 185 | await mr.kycVerdict(member3, true); 186 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member3 }); 187 | await tk.transfer(member3, tokens); 188 | await tc.lock(CLA, lockTokens, validity, { 189 | from: member3 190 | }); 191 | await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: owner }); 192 | await tk.transfer(owner, tokens); 193 | await tc.lock(CLA, lockTokens, validity, { 194 | from: owner 195 | }); 196 | }); 197 | let initialLockedTokens; 198 | 199 | it('4.12 should have some locked tokens', async function() { 200 | initialLockedTokens = await tc.tokensLocked(member3, CLA); 201 | initialLockedTokens.toString().should.be.not.equal((0).toString()); 202 | }); 203 | 204 | it('4.13 should be able to increase amount of lock tokens of member', async function() { 205 | const initialTokenBalance = await tk.balanceOf(member3); 206 | await tc.increaseLockAmount(CLA, extendLockTokens, { 207 | from: member3 208 | }); 209 | const newTokenBalance = new BN(initialTokenBalance.toString()).sub( 210 | new BN(extendLockTokens.toString()) 211 | ); 212 | const newLockedTokens = new BN(initialLockedTokens.toString()).add( 213 | new BN(extendLockTokens.toString()) 214 | ); 215 | newLockedTokens 216 | .toString() 217 | .should.be.equal((await tc.tokensLocked(member3, CLA)).toString()); 218 | newTokenBalance 219 | .toString() 220 | .should.be.equal((await tk.balanceOf(member3)).toString()); 221 | }); 222 | }); 223 | 224 | describe('After claiming tokens on validity expire', function() { 225 | before(async function() { 226 | const validity = await tc.getLockedTokensValidity(member1, CLA); 227 | await increaseTimeTo( 228 | new BN(validity.toString()).add(new BN((2).toString())) 229 | ); 230 | await tc.unlock(member1); 231 | }); 232 | it('4.15 reverts', async function() { 233 | await assertRevert( 234 | tc.increaseLockAmount(CLA, extendLockTokens, { from: member1 }) 235 | ); 236 | }); 237 | }); 238 | }); 239 | //end of increase lock token describe 240 | }); 241 | 242 | describe('Unlock Tokens', function() { 243 | describe('After validity expires', function() { 244 | let initialTokenBalance; 245 | let initialLockedTokens; 246 | before(async function() { 247 | initialTokenBalance = await tk.balanceOf(member2); 248 | initialLockedTokens = await tc.tokensLocked(member2, CLA); 249 | }); 250 | it('4.16 should return unlockable tokens for a specific reason', async function() { 251 | const tokensUnlockableForAReason = await tc.tokensUnlockable( 252 | member2, 253 | CLA 254 | ); 255 | assert.equal( 256 | parseFloat(tokensUnlockableForAReason), 257 | parseFloat(initialLockedTokens) 258 | ); 259 | }); 260 | it('4.17 should return 0 locked token', async function() { 261 | const tokensUnlockable = await tc.getUnlockableTokens(member2); 262 | assert.equal( 263 | parseFloat(tokensUnlockable), 264 | parseFloat(initialLockedTokens) 265 | ); 266 | await tc.unlock(member2); 267 | const lockedTokens = await tc.tokensLockedAtTime( 268 | member2, 269 | CLA, 270 | await latestTime() 271 | ); 272 | lockedTokens.toString().should.be.equal((0).toString()); 273 | }); 274 | it('4.18 checking that 0 tokens is unlocked and unlockable for 0 lock tokens of member', async function() { 275 | const unlockTransaction = await tc.unlock(member2); 276 | // if no tokens unlocked, following array is empty 277 | assert.equal(unlockTransaction['receipt']['logs'].length, 0); 278 | 279 | assert.equal(await tc.getUnlockableTokens(member2), 0); 280 | 281 | // tokens unlockable for a specific reason CLA is also 0 for cross check and to pass branch of tokenController contract 282 | assert.equal(await tc.tokensUnlockable(member2, CLA), 0); 283 | }); 284 | it('4.19 balance of member should increase', async function() { 285 | (await tk.balanceOf(member2)) 286 | .toString() 287 | .should.be.equal( 288 | new BN(initialTokenBalance.toString()) 289 | .add(new BN(initialLockedTokens.toString())) 290 | .toString() 291 | ); 292 | }); 293 | }); 294 | }); 295 | 296 | describe('Change Lock', function() { 297 | const lockTokens = ether(2); 298 | const validity = duration.days(30); 299 | describe('Zero locked tokens', function() { 300 | it('4.20 reverts', async function() { 301 | await assertRevert(tc.reduceLock(member1, CLA, await duration.days(1))); 302 | }); 303 | }); 304 | describe('Non zero locked Tokens', function() { 305 | before(async function() { 306 | await tc.lock(CLA, lockTokens, validity, { 307 | from: member1 308 | }); 309 | }); 310 | it('4.21 Total locked balance of member at current time should not be 0', async function() { 311 | const now = await latestTime(); 312 | const totalLockedBalanceCurrently = parseFloat( 313 | await tc.totalLockedBalance(member1, now) 314 | ); 315 | totalLockedBalanceCurrently.should.not.be.equal(0); 316 | }); 317 | }); 318 | describe('Try to burn more than locked tockens of owner for a specific reason', function() { 319 | it('4.23 cannot burn, amount exceeded', async function() { 320 | await assertRevert(tc.burnLockedTokens(owner, CLA, lockTokens + 100)); 321 | }); 322 | }); 323 | describe('Try to release more than locked tockens of owner for a specific reason', function() { 324 | it('4.24 cannot release, amount exceeded', async function() { 325 | await assertRevert( 326 | tc.releaseLockedTokens(owner, CLA, lockTokens + 100) 327 | ); 328 | }); 329 | }); 330 | }); 331 | 332 | //contract block 333 | }); 334 | -------------------------------------------------------------------------------- /test/11_MCR.test.js: -------------------------------------------------------------------------------- 1 | const MCR = artifacts.require('MCR'); 2 | const Pool1 = artifacts.require('Pool1Mock'); 3 | const Pool2 = artifacts.require('Pool2'); 4 | const PoolData = artifacts.require('PoolDataMock'); 5 | const DAI = artifacts.require('MockDAI'); 6 | const NXMToken = artifacts.require('NXMToken'); 7 | const MemberRoles = artifacts.require('MemberRoles'); 8 | const NXMaster = artifacts.require('NXMaster'); 9 | const DSValue = artifacts.require('DSValueMock'); 10 | const QuotationDataMock = artifacts.require('QuotationDataMock'); 11 | const TokenFunctions = artifacts.require('TokenFunctionMock'); 12 | 13 | const { assertRevert } = require('./utils/assertRevert'); 14 | const { advanceBlock } = require('./utils/advanceToBlock'); 15 | const { ether, toHex, toWei } = require('./utils/ethTools'); 16 | const { increaseTimeTo, duration } = require('./utils/increaseTime'); 17 | const { latestTime } = require('./utils/latestTime'); 18 | const getValue = require('./utils/getMCRPerThreshold.js').getValue; 19 | 20 | const CA_ETH = '0x45544800'; 21 | const CA_DAI = '0x44414900'; 22 | 23 | let mcr; 24 | let pd; 25 | let tk; 26 | let p1; 27 | let p2; 28 | let mr; 29 | let nxms; 30 | let DSV; 31 | let qd; 32 | let tf; 33 | let balance_DAI; 34 | let balance_ETH; 35 | const BN = web3.utils.BN; 36 | 37 | const BigNumber = web3.BigNumber; 38 | require('chai') 39 | .use(require('chai-bignumber')(BigNumber)) 40 | .should(); 41 | 42 | contract('MCR', function([owner, notOwner]) { 43 | before(async function() { 44 | await advanceBlock(); 45 | mcr = await MCR.deployed(); 46 | tk = await NXMToken.deployed(); 47 | p1 = await Pool1.deployed(); 48 | pd = await PoolData.deployed(); 49 | cad = await DAI.deployed(); 50 | nxms = await NXMaster.deployed(); 51 | mr = await MemberRoles.at(await nxms.getLatestAddress('0x4d52')); 52 | p2 = await Pool2.deployed(); 53 | DSV = await DSValue.deployed(); 54 | qd = await QuotationDataMock.deployed(); 55 | tf = await TokenFunctions.deployed(); 56 | }); 57 | 58 | describe('Initial MCR cap test cases', function() { 59 | it('Testing new threshold condition with standard values', async function() { 60 | let thresholdValues = await mcr.getThresholdValues( 61 | new BN(toWei(7072).toString()), 62 | new BN(toWei(7060).toString()), 63 | new BN((2218).toString()), 64 | new BN((7).toString()) 65 | ); 66 | thresholdValues[0].toString().should.be.equal(new BN(9184).toString()); 67 | thresholdValues[1].toString().should.be.equal(new BN(12123).toString()); 68 | let thresholdValues1 = await mcr.getThresholdValues( 69 | new BN(toWei(7072).toString()), 70 | new BN(toWei(7060).toString()), 71 | new BN((20000).toString()), 72 | new BN((7).toString()) 73 | ); 74 | thresholdValues1[0].toString().should.be.equal(new BN(7072).toString()); 75 | thresholdValues1[1].toString().should.be.equal(new BN(12123).toString()); 76 | thresholdValues2 = await mcr.getThresholdValues( 77 | new BN('9127095013938829399629'.toString()), 78 | new BN(toWei(9127).toString()), 79 | new BN((4856).toString()), 80 | new BN((7).toString()) 81 | ); 82 | thresholdValues2[0].toString().should.be.equal(new BN(11853).toString()); 83 | thresholdValues2[1].toString().should.be.equal(new BN(15646).toString()); 84 | }); 85 | it('11.1 post mcr before launch should not affect initialMCRCap', async function() { 86 | let cap = await pd.capReached(); 87 | await mcr.addMCRData( 88 | 18000, 89 | toWei(100), 90 | toWei(2), 91 | ['0x455448', '0x444149'], 92 | [100, 65407], 93 | 20181011 94 | ); 95 | ((await mcr.variableMincap()) / 1e18) 96 | .toString() 97 | .should.be.equal((70).toString()); 98 | (await pd.capReached()).toString().should.be.equal(cap.toString()); 99 | }); 100 | describe('After launch', function() { 101 | before(async function() { 102 | await mr.addMembersBeforeLaunch([], []); 103 | (await mr.launched()).should.be.equal(true); 104 | }); 105 | 106 | it('11.2 After launch cap should not be set until it reached 100 for 1st time', async function() { 107 | await mcr.addMCRData( 108 | 1800, 109 | toWei(100), 110 | toWei(2), 111 | ['0x455448', '0x444149'], 112 | [100, 65407], 113 | 20181011 114 | ); 115 | ((await mcr.variableMincap()) / 1e18) 116 | .toString() 117 | .should.be.equal((70).toString()); 118 | (await pd.capReached()).toString().should.be.equal((0).toString()); 119 | }); 120 | 121 | it('11.4 After launch cap should be set to 1 if reached 100% for 1st time on 30th day', async function() { 122 | await mcr.addMCRData( 123 | 18000, 124 | toWei(100), 125 | toWei(2), 126 | ['0x455448', '0x444149'], 127 | [100, 65407], 128 | 20181011 129 | ); 130 | ((await mcr.variableMincap()) / 1e18) 131 | .toString() 132 | .should.be.equal((140.7).toString()); 133 | (await pd.capReached()).toString().should.be.equal((1).toString()); 134 | }); 135 | }); 136 | }); 137 | 138 | describe('Calculation of V(tp) and MCR(tp)', function() { 139 | let cal_vtp; 140 | let cal_mcrtp; 141 | 142 | before(async function() { 143 | await mcr.addMCRData( 144 | 18000, 145 | toWei(100), 146 | toWei(2), 147 | ['0x455448', '0x444149'], 148 | [100, 15517], 149 | 20190103 150 | ); 151 | ((await mcr.variableMincap()) / 1e18) 152 | .toString() 153 | .should.be.equal((212.107).toString()); 154 | await cad.transfer(p1.address, ether(600)); 155 | balance_DAI = await cad.balanceOf(p1.address); 156 | balance_ETH = await web3.eth.getBalance(p1.address); 157 | balance_ETH = new BN(balance_ETH.toString()).add( 158 | new BN((await p1.getInvestmentAssetBalance()).toString()) 159 | ); 160 | }); 161 | 162 | it('11.5 should return correct V(tp) price', async function() { 163 | const price_dai = await pd.getCAAvgRate(CA_DAI); 164 | cal_vtp = new BN(balance_DAI.toString()) 165 | .mul(new BN((100).toString())) 166 | .div(new BN(price_dai.toString())); 167 | cal_vtp = new BN(cal_vtp.toString()).add(new BN(balance_ETH.toString())); 168 | cal_vtp 169 | .toString() 170 | .should.be.equal((await mcr.calVtpAndMCRtp())[0].toString()); 171 | }); 172 | 173 | it('11.6 should return correct MCR(tp) price', async function() { 174 | const lastMCR = await pd.getLastMCR(); 175 | cal_mcrtp = new BN(cal_vtp.toString()) 176 | .mul(new BN(lastMCR[0].toString())) 177 | .div(new BN(lastMCR[2].toString())); 178 | cal_mcrtp 179 | .toString() 180 | .should.be.equal((await mcr.calVtpAndMCRtp())[1].toString()); 181 | }); 182 | }); 183 | 184 | describe('Token Price Calculation', function() { 185 | let tp_eth; 186 | let tp_dai; 187 | 188 | before(async function() { 189 | const tpd = await pd.getTokenPriceDetails(CA_ETH); 190 | // const tc = (await tk.totalSupply()).div(toWei(1)); 191 | const sf = parseFloat(tpd[0].toString()) / 100000; 192 | 193 | const C = tpd[1]; 194 | const Curr3DaysAvg = tpd[2]; 195 | const mcrtp = (await mcr.calVtpAndMCRtp())[1]; 196 | const mcrtpSquare = new BN(mcrtp.toString()) 197 | .mul(new BN(mcrtp.toString())) 198 | .div(new BN((100000000).toString())); 199 | 200 | const mcrEth = new BN((await pd.getLastMCREther()).toString()).div( 201 | new BN(toWei(1).toString()) 202 | ); 203 | const tp = 204 | sf + 205 | (parseFloat(mcrEth.toString()) / parseFloat(C.toString())) * 206 | parseFloat(mcrtpSquare.toString()) * 207 | parseFloat(mcrtpSquare.toString()); 208 | 209 | tp_eth = tp * (parseFloat(Curr3DaysAvg.toString()) / 100); 210 | tp_dai = 211 | tp * (parseFloat((await pd.getCAAvgRate(CA_DAI)).toString()) / 100); 212 | }); 213 | it('11.7 should return correct Token price in ETH', async function() { 214 | parseInt(tp_eth / 1000) 215 | .toString() 216 | .should.be.equal( 217 | parseInt( 218 | new BN((await mcr.calculateTokenPrice(CA_ETH)).toString()) 219 | .div(new BN(toWei(1).toString())) 220 | .toString() / 1000 221 | ).toString() 222 | ); 223 | }); 224 | it('11.8 should return correct Token price in DAI', async function() { 225 | parseInt(tp_dai / 1e6) 226 | .toString() 227 | .should.be.equal( 228 | parseInt( 229 | new BN((await mcr.calculateTokenPrice(CA_DAI)).toString()) 230 | .div(new BN(toWei(1).toString())) 231 | .toString() / 1e6 232 | ).toString() 233 | ); 234 | }); 235 | }); 236 | 237 | describe('Misc', function() { 238 | it('11.15 should not be able to change master address', async function() { 239 | await assertRevert( 240 | mcr.changeMasterAddress(mcr.address, { from: notOwner }) 241 | ); 242 | }); 243 | it('11.16 should not be able to add mcr data if not notarise', async function() { 244 | await assertRevert( 245 | mcr.addMCRData( 246 | 18000, 247 | toWei(100), 248 | toWei(2), 249 | ['0x455448', '0x444149'], 250 | [100, 65407], 251 | 20181011, 252 | { from: notOwner } 253 | ) 254 | ); 255 | }); 256 | it('11.17 add mcr when vf > vtp', async function() { 257 | await mcr.addMCRData( 258 | 18000, 259 | toWei(100), 260 | toWei(35.83333333333333), 261 | ['0x455448', '0x444149'], 262 | [100, 65407], 263 | 20181011, 264 | { from: owner } 265 | ); 266 | ((await mcr.variableMincap()) / 1e18) 267 | .toString() 268 | .should.be.equal((284.22807).toString()); 269 | }); 270 | it('11.18 getAllSumAssurance function should skip calcualation for currency with rate 0', async function() { 271 | await DSV.setRate(0); 272 | let allSA = await mcr.getAllSumAssurance(); 273 | (await qd.getTotalSumAssured(toHex('ETH'))) 274 | .toString() 275 | .should.be.equal(allSA.toString()); 276 | }); 277 | it('11.19 calVtpAndMCRtp function should skip calcualation for currency with rate 0', async function() { 278 | let vtp = await mcr.calVtpAndMCRtp(); 279 | CABalE = await web3.eth.getBalance(p1.address); 280 | CABalE2 = await web3.eth.getBalance(p2.address); 281 | vtp[0] 282 | .toString() 283 | .should.be.equal( 284 | new BN(CABalE.toString()).add(new BN(CABalE2.toString())).toString() 285 | ); 286 | }); 287 | it('11.20 mcrTp should be 0 if vFull is 0', async function() { 288 | await mcr.addMCRData( 289 | await getValue(0, pd, mcr), 290 | toWei(100), 291 | 0, 292 | ['0x455448', '0x444149'], 293 | [100, 65407], 294 | 20181011, 295 | { from: owner } 296 | ); 297 | ((await mcr.variableMincap()) / 1e18) 298 | .toString() 299 | .should.be.equal((284.22807).toString()); 300 | let vtp = await mcr.calVtpAndMCRtp(); 301 | 302 | (vtp[1] / 1).should.be.equal(0); 303 | }); 304 | it('11.21 mcr if vtp is 0', async function() { 305 | await tf.upgradeCapitalPool(cad.address); 306 | await p1.upgradeInvestmentPool(cad.address); 307 | await mcr.addMCRData( 308 | 18000, 309 | toWei(100), 310 | 0, 311 | ['0x455448', '0x444149'], 312 | [100, 65407], 313 | 20181011, 314 | { from: owner } 315 | ); 316 | ((await mcr.variableMincap()) / 1e18) 317 | .toString() 318 | .should.be.equal((357.0703507).toString()); 319 | let APIID = await pd.allAPIcall((await pd.getApilCallLength()) - 1); 320 | let timeINC = 321 | (await pd.getDateAddOfAPI(APIID)) / 1 + 322 | (await pd.mcrFailTime()) / 1 + 323 | 100; 324 | await increaseTimeTo(timeINC); 325 | await p1.__callback(APIID, ''); 326 | }); 327 | it('11.22 rebalancing trade if total risk balance is 0', async function() { 328 | await p1.sendEther({ from: owner, value: toWei(2) }); 329 | 330 | await p2.saveIADetails( 331 | ['0x455448', '0x444149'], 332 | [100, 15517], 333 | 20190103, 334 | true 335 | ); 336 | }); 337 | it('11.23 if mcr fails and retry after new mcr posted', async function() { 338 | await tf.upgradeCapitalPool(cad.address); 339 | await p1.upgradeInvestmentPool(cad.address); 340 | await mcr.addMCRData( 341 | 18000, 342 | toWei(100), 343 | 0, 344 | ['0x455448', '0x444149'], 345 | [100, 65407], 346 | 20181012, 347 | { from: owner } 348 | ); 349 | ((await mcr.variableMincap()) / 1e18) 350 | .toString() 351 | .should.be.equal((430.64105420699997).toString()); 352 | let APIID = await pd.allAPIcall((await pd.getApilCallLength()) - 1); 353 | await mcr.addMCRData( 354 | 18000, 355 | toWei(100), 356 | toWei(100), 357 | ['0x455448', '0x444149'], 358 | [100, 65407], 359 | 20181013, 360 | { from: owner } 361 | ); 362 | ((await mcr.variableMincap()) / 1e18) 363 | .toString() 364 | .should.be.equal((504.94746474907004).toString()); 365 | await p1.__callback(APIID, ''); // to cover else branch (if call comes before callback time) 366 | let timeINC = 367 | (await pd.getDateAddOfAPI(APIID)) / 1 + 368 | (await pd.mcrFailTime()) / 1 + 369 | 100; 370 | await increaseTimeTo(timeINC); 371 | await p1.__callback(APIID, ''); 372 | }); 373 | 374 | it('11.24 get orcalise call details', async function() { 375 | let APIID = await pd.allAPIcall((await pd.getApilCallLength()) - 1); 376 | let curr = await pd.getCurrOfApiId(APIID); 377 | let id = await pd.getApiCallIndex(1); 378 | let dateUPD = await pd.getDateUpdOfAPI(APIID); 379 | let details = await pd.getApiCallDetails(APIID); 380 | }); 381 | it('should not be able to update capital model parameters directly', async function() { 382 | await assertRevert(mcr.updateUintParameters('0x49434e', 12)); 383 | }); 384 | }); 385 | }); 386 | -------------------------------------------------------------------------------- /contracts/MCR.sol: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 NexusMutual.io 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see http://www.gnu.org/licenses/ */ 15 | 16 | pragma solidity 0.5.7; 17 | 18 | import "./PoolData.sol"; 19 | import "./QuotationData.sol"; 20 | import "./TokenData.sol"; 21 | import "./NXMToken.sol"; 22 | import "./Pool1.sol"; 23 | import "./MemberRoles.sol"; 24 | import "./ProposalCategory.sol"; 25 | 26 | 27 | contract MCR is Iupgradable { 28 | using SafeMath for uint; 29 | 30 | Pool1 internal p1; 31 | PoolData internal pd; 32 | NXMToken internal tk; 33 | QuotationData internal qd; 34 | MemberRoles internal mr; 35 | TokenData internal td; 36 | ProposalCategory internal proposalCategory; 37 | 38 | uint private constant DECIMAL1E18 = uint(10) ** 18; 39 | uint private constant DECIMAL1E05 = uint(10) ** 5; 40 | uint private constant DECIMAL1E19 = uint(10) ** 19; 41 | uint private constant minCapFactor = uint(10) ** 21; 42 | 43 | uint public variableMincap; 44 | uint public dynamicMincapThresholdx100 = 13000; 45 | uint public dynamicMincapIncrementx100 = 100; 46 | 47 | event MCREvent( 48 | uint indexed date, 49 | uint blockNumber, 50 | bytes4[] allCurr, 51 | uint[] allCurrRates, 52 | uint mcrEtherx100, 53 | uint mcrPercx100, 54 | uint vFull 55 | ); 56 | 57 | /** 58 | * @dev Adds new MCR data. 59 | * @param mcrP Minimum Capital Requirement in percentage. 60 | * @param vF Pool1 fund value in Ether used in the last full daily calculation of the Capital model. 61 | * @param onlyDate Date(yyyymmdd) at which MCR details are getting added. 62 | */ 63 | function addMCRData( 64 | uint mcrP, 65 | uint mcrE, 66 | uint vF, 67 | bytes4[] calldata curr, 68 | uint[] calldata _threeDayAvg, 69 | uint64 onlyDate 70 | ) 71 | external 72 | checkPause 73 | { 74 | require(proposalCategory.constructorCheck()); 75 | require(pd.isnotarise(msg.sender)); 76 | if (mr.launched() && pd.capReached() != 1) { 77 | 78 | if (mcrP >= 10000) 79 | pd.setCapReached(1); 80 | 81 | } 82 | uint len = pd.getMCRDataLength(); 83 | _addMCRData(len, onlyDate, curr, mcrE, mcrP, vF, _threeDayAvg); 84 | } 85 | 86 | /** 87 | * @dev Adds MCR Data for last failed attempt. 88 | */ 89 | function addLastMCRData(uint64 date) external checkPause onlyInternal { 90 | uint64 lastdate = uint64(pd.getLastMCRDate()); 91 | uint64 failedDate = uint64(date); 92 | if (failedDate >= lastdate) { 93 | uint mcrP; 94 | uint mcrE; 95 | uint vF; 96 | (mcrP, mcrE, vF, ) = pd.getLastMCR(); 97 | uint len = pd.getAllCurrenciesLen(); 98 | pd.pushMCRData(mcrP, mcrE, vF, date); 99 | for (uint j = 0; j < len; j++) { 100 | bytes4 currName = pd.getCurrenciesByIndex(j); 101 | pd.updateCAAvgRate(currName, pd.getCAAvgRate(currName)); 102 | } 103 | 104 | emit MCREvent(date, block.number, new bytes4[](0), new uint[](0), mcrE, mcrP, vF); 105 | // Oraclize call for next MCR calculation 106 | _callOracliseForMCR(); 107 | } 108 | } 109 | 110 | /** 111 | * @dev Iupgradable Interface to update dependent contract address 112 | */ 113 | function changeDependentContractAddress() public onlyInternal { 114 | qd = QuotationData(ms.getLatestAddress("QD")); 115 | p1 = Pool1(ms.getLatestAddress("P1")); 116 | pd = PoolData(ms.getLatestAddress("PD")); 117 | tk = NXMToken(ms.tokenAddress()); 118 | mr = MemberRoles(ms.getLatestAddress("MR")); 119 | td = TokenData(ms.getLatestAddress("TD")); 120 | proposalCategory = ProposalCategory(ms.getLatestAddress("PC")); 121 | } 122 | 123 | /** 124 | * @dev Gets total sum assured(in ETH). 125 | * @return amount of sum assured 126 | */ 127 | function getAllSumAssurance() public view returns(uint amount) { 128 | uint len = pd.getAllCurrenciesLen(); 129 | for (uint i = 0; i < len; i++) { 130 | bytes4 currName = pd.getCurrenciesByIndex(i); 131 | if (currName == "ETH") { 132 | amount = amount.add(qd.getTotalSumAssured(currName)); 133 | } else { 134 | if (pd.getCAAvgRate(currName) > 0) 135 | amount = amount.add((qd.getTotalSumAssured(currName).mul(100)).div(pd.getCAAvgRate(currName))); 136 | } 137 | } 138 | } 139 | 140 | /** 141 | * @dev Calculates V(Tp) and MCR%(Tp), i.e, Pool Fund Value in Ether 142 | * and MCR% used in the Token Price Calculation. 143 | * @return vtp Pool Fund Value in Ether used for the Token Price Model 144 | * @return mcrtp MCR% used in the Token Price Model. 145 | */ 146 | function _calVtpAndMCRtp(uint poolBalance) public view returns(uint vtp, uint mcrtp) { 147 | vtp = 0; 148 | IERC20 erc20; 149 | uint currTokens = 0; 150 | uint i; 151 | for (i = 1; i < pd.getAllCurrenciesLen(); i++) { 152 | bytes4 currency = pd.getCurrenciesByIndex(i); 153 | erc20 = IERC20(pd.getCurrencyAssetAddress(currency)); 154 | currTokens = erc20.balanceOf(address(p1)); 155 | if (pd.getCAAvgRate(currency) > 0) 156 | vtp = vtp.add((currTokens.mul(100)).div(pd.getCAAvgRate(currency))); 157 | } 158 | 159 | vtp = vtp.add(poolBalance).add(p1.getInvestmentAssetBalance()); 160 | uint mcrFullperc; 161 | uint vFull; 162 | (mcrFullperc, , vFull, ) = pd.getLastMCR(); 163 | if (vFull > 0) { 164 | mcrtp = (mcrFullperc.mul(vtp)).div(vFull); 165 | } 166 | } 167 | 168 | /** 169 | * @dev Calculates the Token Price of NXM in a given currency. 170 | * @param curr Currency name. 171 | 172 | */ 173 | function calculateStepTokenPrice( 174 | bytes4 curr, 175 | uint mcrtp 176 | ) 177 | public 178 | view 179 | onlyInternal 180 | returns(uint tokenPrice) 181 | { 182 | return _calculateTokenPrice(curr, mcrtp); 183 | } 184 | 185 | /** 186 | * @dev Calculates the Token Price of NXM in a given currency 187 | * with provided token supply for dynamic token price calculation 188 | * @param curr Currency name. 189 | */ 190 | function calculateTokenPrice (bytes4 curr) public view returns(uint tokenPrice) { 191 | uint mcrtp; 192 | (, mcrtp) = _calVtpAndMCRtp(address(p1).balance); 193 | return _calculateTokenPrice(curr, mcrtp); 194 | } 195 | 196 | function calVtpAndMCRtp() public view returns(uint vtp, uint mcrtp) { 197 | return _calVtpAndMCRtp(address(p1).balance); 198 | } 199 | 200 | function calculateVtpAndMCRtp(uint poolBalance) public view returns(uint vtp, uint mcrtp) { 201 | return _calVtpAndMCRtp(poolBalance); 202 | } 203 | 204 | function getThresholdValues(uint vtp, uint vF, uint totalSA, uint minCap) public view returns(uint lowerThreshold, uint upperThreshold) 205 | { 206 | minCap = (minCap.mul(minCapFactor)).add(variableMincap); 207 | uint lower = 0; 208 | if (vtp >= vF) { 209 | upperThreshold = vtp.mul(120).mul(100).div((minCap)); //Max Threshold = [MAX(Vtp, Vfull) x 120] / mcrMinCap 210 | } else { 211 | upperThreshold = vF.mul(120).mul(100).div((minCap)); 212 | } 213 | 214 | if (vtp > 0) { 215 | lower = totalSA.mul(DECIMAL1E18).mul(pd.shockParameter()).div(100); 216 | if(lower < minCap.mul(11).div(10)) 217 | lower = minCap.mul(11).div(10); 218 | } 219 | if (lower > 0) { //Min Threshold = [Vtp / MAX(TotalActiveSA x ShockParameter, mcrMinCap x 1.1)] x 100 220 | lowerThreshold = vtp.mul(100).mul(100).div(lower); 221 | } 222 | } 223 | 224 | /** 225 | * @dev Gets max numbers of tokens that can be sold at the moment. 226 | */ 227 | function getMaxSellTokens() public view returns(uint maxTokens) { 228 | uint baseMin = pd.getCurrencyAssetBaseMin("ETH"); 229 | uint maxTokensAccPoolBal; 230 | if (address(p1).balance > baseMin.mul(50).div(100)) { 231 | maxTokensAccPoolBal = address(p1).balance.sub( 232 | (baseMin.mul(50)).div(100)); 233 | } 234 | maxTokensAccPoolBal = (maxTokensAccPoolBal.mul(DECIMAL1E18)).div( 235 | (calculateTokenPrice("ETH").mul(975)).div(1000)); 236 | uint lastMCRPerc = pd.getLastMCRPerc(); 237 | if (lastMCRPerc > 10000) 238 | maxTokens = (((uint(lastMCRPerc).sub(10000)).mul(2000)).mul(DECIMAL1E18)).div(10000); 239 | if (maxTokens > maxTokensAccPoolBal) 240 | maxTokens = maxTokensAccPoolBal; 241 | } 242 | 243 | /** 244 | * @dev Gets Uint Parameters of a code 245 | * @param code whose details we want 246 | * @return string value of the code 247 | * @return associated amount (time or perc or value) to the code 248 | */ 249 | function getUintParameters(bytes8 code) external view returns(bytes8 codeVal, uint val) { 250 | codeVal = code; 251 | if (code == "DMCT") { 252 | val = dynamicMincapThresholdx100; 253 | 254 | } else if (code == "DMCI") { 255 | 256 | val = dynamicMincapIncrementx100; 257 | 258 | } 259 | 260 | } 261 | 262 | /** 263 | * @dev Updates Uint Parameters of a code 264 | * @param code whose details we want to update 265 | * @param val value to set 266 | */ 267 | function updateUintParameters(bytes8 code, uint val) public { 268 | require(ms.checkIsAuthToGoverned(msg.sender)); 269 | if (code == "DMCT") { 270 | dynamicMincapThresholdx100 = val; 271 | 272 | } else if (code == "DMCI") { 273 | 274 | dynamicMincapIncrementx100 = val; 275 | 276 | } 277 | else { 278 | revert("Invalid param code"); 279 | } 280 | 281 | } 282 | 283 | /** 284 | * @dev Calls oraclize query to calculate MCR details after 24 hours. 285 | */ 286 | function _callOracliseForMCR() internal { 287 | p1.mcrOraclise(pd.mcrTime()); 288 | } 289 | 290 | /** 291 | * @dev Calculates the Token Price of NXM in a given currency 292 | * with provided token supply for dynamic token price calculation 293 | * @param _curr Currency name. 294 | * @return tokenPrice Token price. 295 | */ 296 | function _calculateTokenPrice( 297 | bytes4 _curr, 298 | uint mcrtp 299 | ) 300 | internal 301 | view 302 | returns(uint tokenPrice) 303 | { 304 | uint getA; 305 | uint getC; 306 | uint getCAAvgRate; 307 | uint tokenExponentValue = td.tokenExponent(); 308 | // uint max = (mcrtp.mul(mcrtp).mul(mcrtp).mul(mcrtp)); 309 | uint max = mcrtp ** tokenExponentValue; 310 | uint dividingFactor = tokenExponentValue.mul(4); 311 | (getA, getC, getCAAvgRate) = pd.getTokenPriceDetails(_curr); 312 | uint mcrEth = pd.getLastMCREther(); 313 | getC = getC.mul(DECIMAL1E18); 314 | tokenPrice = (mcrEth.mul(DECIMAL1E18).mul(max).div(getC)).div(10 ** dividingFactor); 315 | tokenPrice = tokenPrice.add(getA.mul(DECIMAL1E18).div(DECIMAL1E05)); 316 | tokenPrice = tokenPrice.mul(getCAAvgRate * 10); 317 | tokenPrice = (tokenPrice).div(10**3); 318 | } 319 | 320 | /** 321 | * @dev Adds MCR Data. Checks if MCR is within valid 322 | * thresholds in order to rule out any incorrect calculations 323 | */ 324 | function _addMCRData( 325 | uint len, 326 | uint64 newMCRDate, 327 | bytes4[] memory curr, 328 | uint mcrE, 329 | uint mcrP, 330 | uint vF, 331 | uint[] memory _threeDayAvg 332 | ) 333 | internal 334 | { 335 | uint vtp = 0; 336 | uint lowerThreshold = 0; 337 | uint upperThreshold = 0; 338 | if (len > 1) { 339 | (vtp, ) = _calVtpAndMCRtp(address(p1).balance); 340 | (lowerThreshold, upperThreshold) = getThresholdValues(vtp, vF, getAllSumAssurance(), pd.minCap()); 341 | 342 | } 343 | if(mcrP > dynamicMincapThresholdx100) 344 | variableMincap = (variableMincap.mul(dynamicMincapIncrementx100.add(10000)).add(minCapFactor.mul(pd.minCap().mul(dynamicMincapIncrementx100)))).div(10000); 345 | 346 | 347 | // Explanation for above formula :- 348 | // actual formula -> variableMinCap = variableMinCap + (variableMinCap+minCap)*dynamicMincapIncrement/100 349 | // Implemented formula is simplified form of actual formula. 350 | // Let consider above formula as b = b + (a+b)*c/100 351 | // here, dynamicMincapIncrement is in x100 format. 352 | // so b+(a+b)*cx100/10000 can be written as => (10000.b + b.cx100 + a.cx100)/10000. 353 | // It can further simplify to (b.(10000+cx100) + a.cx100)/10000. 354 | if (len == 1 || (mcrP) >= lowerThreshold 355 | && (mcrP) <= upperThreshold) { 356 | vtp = pd.getLastMCRDate(); // due to stack to deep error,we are reusing already declared variable 357 | pd.pushMCRData(mcrP, mcrE, vF, newMCRDate); 358 | for (uint i = 0; i < curr.length; i++) { 359 | pd.updateCAAvgRate(curr[i], _threeDayAvg[i]); 360 | } 361 | emit MCREvent(newMCRDate, block.number, curr, _threeDayAvg, mcrE, mcrP, vF); 362 | // Oraclize call for next MCR calculation 363 | if (vtp < newMCRDate) { 364 | _callOracliseForMCR(); 365 | } 366 | } else { 367 | p1.mcrOracliseFail(newMCRDate, pd.mcrFailTime()); 368 | } 369 | } 370 | 371 | } 372 | --------------------------------------------------------------------------------